支持多格式模板填写和文档解析优化
- 实现 Word 文档表格模板解析功能,支持从 .docx 文件中提取字段定义 - 新增源文档字段提示词(hint)功能,提升数据提取准确性 - 支持多种方式指定源文档:MongoDB 文档 ID 列表或文件路径列表 - 增强模板字段类型推断机制,支持从提示词和示例值自动识别 - 实现 Excel 和 Word 格式导出功能,提供多种导出选项 - 重构模板填写服务,优化上下文构建和文档加载逻辑 - 更新前端 API 接口,支持传递源文档参数和字段提示词 -
This commit is contained in:
24
.gitignore
vendored
24
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
/.git/
|
/.git/
|
||||||
|
/.gitignore
|
||||||
/.idea/
|
/.idea/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/backend/venv/
|
/backend/venv/
|
||||||
@@ -18,11 +19,7 @@
|
|||||||
/frontend/.idea/
|
/frontend/.idea/
|
||||||
/frontend/.env
|
/frontend/.env
|
||||||
/frontend/*.log
|
/frontend/*.log
|
||||||
/技术路线.md
|
|
||||||
/开发路径.md
|
|
||||||
/开发日志_2026-03-16.md
|
|
||||||
/frontendTest/
|
|
||||||
/docs/
|
|
||||||
/frontend/src/api/
|
/frontend/src/api/
|
||||||
/frontend/src/api/index.js
|
/frontend/src/api/index.js
|
||||||
/frontend/src/api/index.ts
|
/frontend/src/api/index.ts
|
||||||
@@ -30,9 +27,22 @@
|
|||||||
/frontend/src/api/index.py
|
/frontend/src/api/index.py
|
||||||
/frontend/src/api/index.go
|
/frontend/src/api/index.go
|
||||||
/frontend/src/api/index.java
|
/frontend/src/api/index.java
|
||||||
|
|
||||||
|
/frontend - 副本/
|
||||||
|
|
||||||
/docs/
|
/docs/
|
||||||
/frontend - 副本/*
|
/frontendTest/
|
||||||
/supabase.txt
|
/supabase.txt
|
||||||
|
|
||||||
**/__pycache__/*
|
# 取消跟踪的文件 / Untracked files
|
||||||
|
比赛备赛规划.md
|
||||||
|
Q&A.xlsx
|
||||||
|
package.json
|
||||||
|
技术路线.md
|
||||||
|
开发路径.md
|
||||||
|
开发日志_2026-03-16.md
|
||||||
|
/logs/
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
**/__pycache__/**
|
||||||
**.pyc
|
**.pyc
|
||||||
|
|||||||
@@ -1,854 +0,0 @@
|
|||||||
diff --git a/backend/app/api/endpoints/templates.py b/backend/app/api/endpoints/templates.py
|
|
||||||
index 572d56e..706f281 100644
|
|
||||||
--- a/backend/app/api/endpoints/templates.py
|
|
||||||
+++ b/backend/app/api/endpoints/templates.py
|
|
||||||
@@ -13,7 +13,7 @@ import pandas as pd
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from app.services.template_fill_service import template_fill_service, TemplateField
|
|
||||||
-from app.services.excel_storage_service import excel_storage_service
|
|
||||||
+from app.services.file_service import file_service
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@@ -28,13 +28,15 @@ class TemplateFieldRequest(BaseModel):
|
|
||||||
name: str
|
|
||||||
field_type: str = "text"
|
|
||||||
required: bool = True
|
|
||||||
+ hint: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class FillRequest(BaseModel):
|
|
||||||
"""填写请求"""
|
|
||||||
template_id: str
|
|
||||||
template_fields: List[TemplateFieldRequest]
|
|
||||||
- source_doc_ids: Optional[List[str]] = None
|
|
||||||
+ source_doc_ids: Optional[List[str]] = None # MongoDB 文档 ID 列表
|
|
||||||
+ source_file_paths: Optional[List[str]] = None # 源文档文件路径列表
|
|
||||||
user_hint: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
@@ -71,7 +73,6 @@ async def upload_template(
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 保存文件
|
|
||||||
- from app.services.file_service import file_service
|
|
||||||
content = await file.read()
|
|
||||||
saved_path = file_service.save_uploaded_file(
|
|
||||||
content,
|
|
||||||
@@ -87,7 +88,7 @@ async def upload_template(
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
- "template_id": saved_path, # 使用文件路径作为ID
|
|
||||||
+ "template_id": saved_path,
|
|
||||||
"filename": file.filename,
|
|
||||||
"file_type": file_ext,
|
|
||||||
"fields": [
|
|
||||||
@@ -95,7 +96,8 @@ async def upload_template(
|
|
||||||
"cell": f.cell,
|
|
||||||
"name": f.name,
|
|
||||||
"field_type": f.field_type,
|
|
||||||
- "required": f.required
|
|
||||||
+ "required": f.required,
|
|
||||||
+ "hint": f.hint
|
|
||||||
}
|
|
||||||
for f in template_fields
|
|
||||||
],
|
|
||||||
@@ -135,7 +137,8 @@ async def extract_template_fields(
|
|
||||||
"cell": f.cell,
|
|
||||||
"name": f.name,
|
|
||||||
"field_type": f.field_type,
|
|
||||||
- "required": f.required
|
|
||||||
+ "required": f.required,
|
|
||||||
+ "hint": f.hint
|
|
||||||
}
|
|
||||||
for f in fields
|
|
||||||
]
|
|
||||||
@@ -153,7 +156,7 @@ async def fill_template(
|
|
||||||
"""
|
|
||||||
执行表格填写
|
|
||||||
|
|
||||||
- 根据提供的字段定义,从已上传的文档中检索信息并填写
|
|
||||||
+ 根据提供的字段定义,从源文档中检索信息并填写
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: 填写请求
|
|
||||||
@@ -168,7 +171,8 @@ async def fill_template(
|
|
||||||
cell=f.cell,
|
|
||||||
name=f.name,
|
|
||||||
field_type=f.field_type,
|
|
||||||
- required=f.required
|
|
||||||
+ required=f.required,
|
|
||||||
+ hint=f.hint
|
|
||||||
)
|
|
||||||
for f in request.template_fields
|
|
||||||
]
|
|
||||||
@@ -177,6 +181,7 @@ async def fill_template(
|
|
||||||
result = await template_fill_service.fill_template(
|
|
||||||
template_fields=fields,
|
|
||||||
source_doc_ids=request.source_doc_ids,
|
|
||||||
+ source_file_paths=request.source_file_paths,
|
|
||||||
user_hint=request.user_hint
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -194,6 +199,8 @@ async def export_filled_template(
|
|
||||||
"""
|
|
||||||
导出填写后的表格
|
|
||||||
|
|
||||||
+ 支持 Excel (.xlsx) 和 Word (.docx) 格式
|
|
||||||
+
|
|
||||||
Args:
|
|
||||||
request: 导出请求
|
|
||||||
|
|
||||||
@@ -201,25 +208,124 @@ async def export_filled_template(
|
|
||||||
文件流
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
- # 创建 DataFrame
|
|
||||||
- df = pd.DataFrame([request.filled_data])
|
|
||||||
+ if request.format == "xlsx":
|
|
||||||
+ return await _export_to_excel(request.filled_data, request.template_id)
|
|
||||||
+ elif request.format == "docx":
|
|
||||||
+ return await _export_to_word(request.filled_data, request.template_id)
|
|
||||||
+ else:
|
|
||||||
+ raise HTTPException(
|
|
||||||
+ status_code=400,
|
|
||||||
+ detail=f"不支持的导出格式: {request.format},仅支持 xlsx/docx"
|
|
||||||
+ )
|
|
||||||
|
|
||||||
- # 导出为 Excel
|
|
||||||
- output = io.BytesIO()
|
|
||||||
- with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
|
||||||
- df.to_excel(writer, index=False, sheet_name='填写结果')
|
|
||||||
+ except HTTPException:
|
|
||||||
+ raise
|
|
||||||
+ except Exception as e:
|
|
||||||
+ logger.error(f"导出失败: {str(e)}")
|
|
||||||
+ raise HTTPException(status_code=500, detail=f"导出失败: {str(e)}")
|
|
||||||
|
|
||||||
- output.seek(0)
|
|
||||||
|
|
||||||
- # 生成文件名
|
|
||||||
- filename = f"filled_template.{request.format}"
|
|
||||||
+async def _export_to_excel(filled_data: dict, template_id: str) -> StreamingResponse:
|
|
||||||
+ """导出为 Excel 格式"""
|
|
||||||
+ # 将字典转换为单行 DataFrame
|
|
||||||
+ df = pd.DataFrame([filled_data])
|
|
||||||
|
|
||||||
- return StreamingResponse(
|
|
||||||
- io.BytesIO(output.getvalue()),
|
|
||||||
- media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
- headers={"Content-Disposition": f"attachment; filename={filename}"}
|
|
||||||
- )
|
|
||||||
+ output = io.BytesIO()
|
|
||||||
+ with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
|
||||||
+ df.to_excel(writer, index=False, sheet_name='填写结果')
|
|
||||||
|
|
||||||
- except Exception as e:
|
|
||||||
- logger.error(f"导出失败: {str(e)}")
|
|
||||||
- raise HTTPException(status_code=500, detail=f"导出失败: {str(e)}")
|
|
||||||
+ output.seek(0)
|
|
||||||
+
|
|
||||||
+ filename = f"filled_template.xlsx"
|
|
||||||
+
|
|
||||||
+ return StreamingResponse(
|
|
||||||
+ io.BytesIO(output.getvalue()),
|
|
||||||
+ media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
+ headers={"Content-Disposition": f"attachment; filename={filename}"}
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+async def _export_to_word(filled_data: dict, template_id: str) -> StreamingResponse:
|
|
||||||
+ """导出为 Word 格式"""
|
|
||||||
+ from docx import Document
|
|
||||||
+ from docx.shared import Pt, RGBColor
|
|
||||||
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
||||||
+
|
|
||||||
+ doc = Document()
|
|
||||||
+
|
|
||||||
+ # 添加标题
|
|
||||||
+ title = doc.add_heading('填写结果', level=1)
|
|
||||||
+ title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
||||||
+
|
|
||||||
+ # 添加填写时间和模板信息
|
|
||||||
+ from datetime import datetime
|
|
||||||
+ info_para = doc.add_paragraph()
|
|
||||||
+ info_para.add_run(f"模板ID: {template_id}\n").bold = True
|
|
||||||
+ info_para.add_run(f"导出时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
||||||
+
|
|
||||||
+ doc.add_paragraph() # 空行
|
|
||||||
+
|
|
||||||
+ # 添加字段表格
|
|
||||||
+ table = doc.add_table(rows=1, cols=3)
|
|
||||||
+ table.style = 'Light Grid Accent 1'
|
|
||||||
+
|
|
||||||
+ # 表头
|
|
||||||
+ header_cells = table.rows[0].cells
|
|
||||||
+ header_cells[0].text = '字段名'
|
|
||||||
+ header_cells[1].text = '填写值'
|
|
||||||
+ header_cells[2].text = '状态'
|
|
||||||
+
|
|
||||||
+ for field_name, field_value in filled_data.items():
|
|
||||||
+ row_cells = table.add_row().cells
|
|
||||||
+ row_cells[0].text = field_name
|
|
||||||
+ row_cells[1].text = str(field_value) if field_value else ''
|
|
||||||
+ row_cells[2].text = '已填写' if field_value else '为空'
|
|
||||||
+
|
|
||||||
+ # 保存到 BytesIO
|
|
||||||
+ output = io.BytesIO()
|
|
||||||
+ doc.save(output)
|
|
||||||
+ output.seek(0)
|
|
||||||
+
|
|
||||||
+ filename = f"filled_template.docx"
|
|
||||||
+
|
|
||||||
+ return StreamingResponse(
|
|
||||||
+ io.BytesIO(output.getvalue()),
|
|
||||||
+ media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
+ headers={"Content-Disposition": f"attachment; filename={filename}"}
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+@router.post("/export/excel")
|
|
||||||
+async def export_to_excel(
|
|
||||||
+ filled_data: dict,
|
|
||||||
+ template_id: str = Query(..., description="模板ID")
|
|
||||||
+):
|
|
||||||
+ """
|
|
||||||
+ 专门导出为 Excel 格式
|
|
||||||
+
|
|
||||||
+ Args:
|
|
||||||
+ filled_data: 填写数据
|
|
||||||
+ template_id: 模板ID
|
|
||||||
+
|
|
||||||
+ Returns:
|
|
||||||
+ Excel 文件流
|
|
||||||
+ """
|
|
||||||
+ return await _export_to_excel(filled_data, template_id)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+@router.post("/export/word")
|
|
||||||
+async def export_to_word(
|
|
||||||
+ filled_data: dict,
|
|
||||||
+ template_id: str = Query(..., description="模板ID")
|
|
||||||
+):
|
|
||||||
+ """
|
|
||||||
+ 专门导出为 Word 格式
|
|
||||||
+
|
|
||||||
+ Args:
|
|
||||||
+ filled_data: 填写数据
|
|
||||||
+ template_id: 模板ID
|
|
||||||
+
|
|
||||||
+ Returns:
|
|
||||||
+ Word 文件流
|
|
||||||
+ """
|
|
||||||
+ return await _export_to_word(filled_data, template_id)
|
|
||||||
diff --git a/backend/app/core/document_parser/docx_parser.py b/backend/app/core/document_parser/docx_parser.py
|
|
||||||
index 75e79da..03c341d 100644
|
|
||||||
--- a/backend/app/core/document_parser/docx_parser.py
|
|
||||||
+++ b/backend/app/core/document_parser/docx_parser.py
|
|
||||||
@@ -161,3 +161,133 @@ class DocxParser(BaseParser):
|
|
||||||
fields[field_name] = match.group(1)
|
|
||||||
|
|
||||||
return fields
|
|
||||||
+
|
|
||||||
+ def parse_tables_for_template(
|
|
||||||
+ self,
|
|
||||||
+ file_path: str
|
|
||||||
+ ) -> Dict[str, Any]:
|
|
||||||
+ """
|
|
||||||
+ 解析 Word 文档中的表格,提取模板字段
|
|
||||||
+
|
|
||||||
+ 专门用于比赛场景:解析表格模板,识别需要填写的字段
|
|
||||||
+
|
|
||||||
+ Args:
|
|
||||||
+ file_path: Word 文件路径
|
|
||||||
+
|
|
||||||
+ Returns:
|
|
||||||
+ 包含表格字段信息的字典
|
|
||||||
+ """
|
|
||||||
+ from docx import Document
|
|
||||||
+ from docx.table import Table
|
|
||||||
+ from docx.oxml.ns import qn
|
|
||||||
+
|
|
||||||
+ doc = Document(file_path)
|
|
||||||
+
|
|
||||||
+ template_info = {
|
|
||||||
+ "tables": [],
|
|
||||||
+ "fields": [],
|
|
||||||
+ "field_count": 0
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ for table_idx, table in enumerate(doc.tables):
|
|
||||||
+ table_info = {
|
|
||||||
+ "table_index": table_idx,
|
|
||||||
+ "rows": [],
|
|
||||||
+ "headers": [],
|
|
||||||
+ "data_rows": [],
|
|
||||||
+ "field_hints": {} # 字段名称 -> 提示词/描述
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ # 提取表头(第一行)
|
|
||||||
+ if table.rows:
|
|
||||||
+ header_cells = [cell.text.strip() for cell in table.rows[0].cells]
|
|
||||||
+ table_info["headers"] = header_cells
|
|
||||||
+
|
|
||||||
+ # 提取数据行
|
|
||||||
+ for row_idx, row in enumerate(table.rows[1:], 1):
|
|
||||||
+ row_data = [cell.text.strip() for cell in row.cells]
|
|
||||||
+ table_info["data_rows"].append(row_data)
|
|
||||||
+ table_info["rows"].append({
|
|
||||||
+ "row_index": row_idx,
|
|
||||||
+ "cells": row_data
|
|
||||||
+ })
|
|
||||||
+
|
|
||||||
+ # 尝试从第二列/第三列提取提示词
|
|
||||||
+ # 比赛模板通常格式为:字段名 | 提示词 | 填写值
|
|
||||||
+ if len(table.rows[0].cells) >= 2:
|
|
||||||
+ for row_idx, row in enumerate(table.rows[1:], 1):
|
|
||||||
+ cells = [cell.text.strip() for cell in row.cells]
|
|
||||||
+ if len(cells) >= 2 and cells[0]:
|
|
||||||
+ # 第一列是字段名
|
|
||||||
+ field_name = cells[0]
|
|
||||||
+ # 第二列可能是提示词或描述
|
|
||||||
+ hint = cells[1] if len(cells) > 1 else ""
|
|
||||||
+ table_info["field_hints"][field_name] = hint
|
|
||||||
+
|
|
||||||
+ template_info["fields"].append({
|
|
||||||
+ "table_index": table_idx,
|
|
||||||
+ "row_index": row_idx,
|
|
||||||
+ "field_name": field_name,
|
|
||||||
+ "hint": hint,
|
|
||||||
+ "expected_value": cells[2] if len(cells) > 2 else ""
|
|
||||||
+ })
|
|
||||||
+
|
|
||||||
+ template_info["tables"].append(table_info)
|
|
||||||
+
|
|
||||||
+ template_info["field_count"] = len(template_info["fields"])
|
|
||||||
+ return template_info
|
|
||||||
+
|
|
||||||
+ def extract_template_fields_from_docx(
|
|
||||||
+ self,
|
|
||||||
+ file_path: str
|
|
||||||
+ ) -> List[Dict[str, Any]]:
|
|
||||||
+ """
|
|
||||||
+ 从 Word 文档中提取模板字段定义
|
|
||||||
+
|
|
||||||
+ 适用于比赛评分表格:表格第一列是字段名,第二列是提示词/填写示例
|
|
||||||
+
|
|
||||||
+ Args:
|
|
||||||
+ file_path: Word 文件路径
|
|
||||||
+
|
|
||||||
+ Returns:
|
|
||||||
+ 字段定义列表
|
|
||||||
+ """
|
|
||||||
+ template_info = self.parse_tables_for_template(file_path)
|
|
||||||
+
|
|
||||||
+ fields = []
|
|
||||||
+ for field in template_info["fields"]:
|
|
||||||
+ fields.append({
|
|
||||||
+ "cell": f"T{field['table_index']}R{field['row_index']}", # TableXRowY 格式
|
|
||||||
+ "name": field["field_name"],
|
|
||||||
+ "hint": field["hint"],
|
|
||||||
+ "table_index": field["table_index"],
|
|
||||||
+ "row_index": field["row_index"],
|
|
||||||
+ "field_type": self._infer_field_type_from_hint(field["hint"]),
|
|
||||||
+ "required": True
|
|
||||||
+ })
|
|
||||||
+
|
|
||||||
+ return fields
|
|
||||||
+
|
|
||||||
+ def _infer_field_type_from_hint(self, hint: str) -> str:
|
|
||||||
+ """
|
|
||||||
+ 从提示词推断字段类型
|
|
||||||
+
|
|
||||||
+ Args:
|
|
||||||
+ hint: 字段提示词
|
|
||||||
+
|
|
||||||
+ Returns:
|
|
||||||
+ 字段类型 (text/number/date)
|
|
||||||
+ """
|
|
||||||
+ hint_lower = hint.lower()
|
|
||||||
+
|
|
||||||
+ # 日期关键词
|
|
||||||
+ date_keywords = ["年", "月", "日", "日期", "时间", "出生"]
|
|
||||||
+ if any(kw in hint for kw in date_keywords):
|
|
||||||
+ return "date"
|
|
||||||
+
|
|
||||||
+ # 数字关键词
|
|
||||||
+ number_keywords = ["数量", "金额", "人数", "面积", "增长", "比率", "%", "率"]
|
|
||||||
+ if any(kw in hint_lower for kw in number_keywords):
|
|
||||||
+ return "number"
|
|
||||||
+
|
|
||||||
+ return "text"
|
|
||||||
diff --git a/backend/app/services/template_fill_service.py b/backend/app/services/template_fill_service.py
|
|
||||||
index 2612354..94930fb 100644
|
|
||||||
--- a/backend/app/services/template_fill_service.py
|
|
||||||
+++ b/backend/app/services/template_fill_service.py
|
|
||||||
@@ -4,13 +4,12 @@
|
|
||||||
从非结构化文档中检索信息并填写到表格模板
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
-from dataclasses import dataclass
|
|
||||||
+from dataclasses import dataclass, field
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from app.core.database import mongodb
|
|
||||||
-from app.services.rag_service import rag_service
|
|
||||||
from app.services.llm_service import llm_service
|
|
||||||
-from app.services.excel_storage_service import excel_storage_service
|
|
||||||
+from app.core.document_parser import ParserFactory
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@@ -22,6 +21,17 @@ class TemplateField:
|
|
||||||
name: str # 字段名称
|
|
||||||
field_type: str = "text" # 字段类型: text/number/date
|
|
||||||
required: bool = True
|
|
||||||
+ hint: str = "" # 字段提示词
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+@dataclass
|
|
||||||
+class SourceDocument:
|
|
||||||
+ """源文档"""
|
|
||||||
+ doc_id: str
|
|
||||||
+ filename: str
|
|
||||||
+ doc_type: str
|
|
||||||
+ content: str = ""
|
|
||||||
+ structured_data: Dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
@@ -38,12 +48,12 @@ class TemplateFillService:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.llm = llm_service
|
|
||||||
- self.rag = rag_service
|
|
||||||
|
|
||||||
async def fill_template(
|
|
||||||
self,
|
|
||||||
template_fields: List[TemplateField],
|
|
||||||
source_doc_ids: Optional[List[str]] = None,
|
|
||||||
+ source_file_paths: Optional[List[str]] = None,
|
|
||||||
user_hint: Optional[str] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
@@ -51,7 +61,8 @@ class TemplateFillService:
|
|
||||||
|
|
||||||
Args:
|
|
||||||
template_fields: 模板字段列表
|
|
||||||
- source_doc_ids: 源文档ID列表,不指定则从所有文档检索
|
|
||||||
+ source_doc_ids: 源文档 MongoDB ID 列表
|
|
||||||
+ source_file_paths: 源文档文件路径列表
|
|
||||||
user_hint: 用户提示(如"请从合同文档中提取")
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
@@ -60,28 +71,23 @@ class TemplateFillService:
|
|
||||||
filled_data = {}
|
|
||||||
fill_details = []
|
|
||||||
|
|
||||||
+ # 1. 加载源文档内容
|
|
||||||
+ source_docs = await self._load_source_documents(source_doc_ids, source_file_paths)
|
|
||||||
+
|
|
||||||
+ if not source_docs:
|
|
||||||
+ logger.warning("没有找到源文档,填表结果将全部为空")
|
|
||||||
+
|
|
||||||
+ # 2. 对每个字段进行提取
|
|
||||||
for field in template_fields:
|
|
||||||
try:
|
|
||||||
- # 1. 从 RAG 检索相关上下文
|
|
||||||
- rag_results = await self._retrieve_context(field.name, user_hint)
|
|
||||||
-
|
|
||||||
- if not rag_results:
|
|
||||||
- # 如果没有检索到结果,尝试直接询问 LLM
|
|
||||||
- result = FillResult(
|
|
||||||
- field=field.name,
|
|
||||||
- value="",
|
|
||||||
- source="未找到相关数据",
|
|
||||||
- confidence=0.0
|
|
||||||
- )
|
|
||||||
- else:
|
|
||||||
- # 2. 构建 Prompt 让 LLM 提取信息
|
|
||||||
- result = await self._extract_field_value(
|
|
||||||
- field=field,
|
|
||||||
- rag_context=rag_results,
|
|
||||||
- user_hint=user_hint
|
|
||||||
- )
|
|
||||||
-
|
|
||||||
- # 3. 存储结果
|
|
||||||
+ # 从源文档中提取字段值
|
|
||||||
+ result = await self._extract_field_value(
|
|
||||||
+ field=field,
|
|
||||||
+ source_docs=source_docs,
|
|
||||||
+ user_hint=user_hint
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+ # 存储结果
|
|
||||||
filled_data[field.name] = result.value
|
|
||||||
fill_details.append({
|
|
||||||
"field": field.name,
|
|
||||||
@@ -107,75 +113,113 @@ class TemplateFillService:
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"filled_data": filled_data,
|
|
||||||
- "fill_details": fill_details
|
|
||||||
+ "fill_details": fill_details,
|
|
||||||
+ "source_doc_count": len(source_docs)
|
|
||||||
}
|
|
||||||
|
|
||||||
- async def _retrieve_context(
|
|
||||||
+ async def _load_source_documents(
|
|
||||||
self,
|
|
||||||
- field_name: str,
|
|
||||||
- user_hint: Optional[str] = None
|
|
||||||
- ) -> List[Dict[str, Any]]:
|
|
||||||
+ source_doc_ids: Optional[List[str]] = None,
|
|
||||||
+ source_file_paths: Optional[List[str]] = None
|
|
||||||
+ ) -> List[SourceDocument]:
|
|
||||||
"""
|
|
||||||
- 从 RAG 检索相关上下文
|
|
||||||
+ 加载源文档内容
|
|
||||||
|
|
||||||
Args:
|
|
||||||
- field_name: 字段名称
|
|
||||||
- user_hint: 用户提示
|
|
||||||
+ source_doc_ids: MongoDB 文档 ID 列表
|
|
||||||
+ source_file_paths: 源文档文件路径列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- 检索结果列表
|
|
||||||
+ 源文档列表
|
|
||||||
"""
|
|
||||||
- # 构建查询文本
|
|
||||||
- query = field_name
|
|
||||||
- if user_hint:
|
|
||||||
- query = f"{user_hint} {field_name}"
|
|
||||||
-
|
|
||||||
- # 检索相关文档片段
|
|
||||||
- results = self.rag.retrieve(query=query, top_k=5)
|
|
||||||
-
|
|
||||||
- return results
|
|
||||||
+ source_docs = []
|
|
||||||
+
|
|
||||||
+ # 1. 从 MongoDB 加载文档
|
|
||||||
+ if source_doc_ids:
|
|
||||||
+ for doc_id in source_doc_ids:
|
|
||||||
+ try:
|
|
||||||
+ doc = await mongodb.get_document(doc_id)
|
|
||||||
+ if doc:
|
|
||||||
+ source_docs.append(SourceDocument(
|
|
||||||
+ doc_id=doc_id,
|
|
||||||
+ filename=doc.get("metadata", {}).get("original_filename", "unknown"),
|
|
||||||
+ doc_type=doc.get("doc_type", "unknown"),
|
|
||||||
+ content=doc.get("content", ""),
|
|
||||||
+ structured_data=doc.get("structured_data", {})
|
|
||||||
+ ))
|
|
||||||
+ logger.info(f"从MongoDB加载文档: {doc_id}")
|
|
||||||
+ except Exception as e:
|
|
||||||
+ logger.error(f"从MongoDB加载文档失败 {doc_id}: {str(e)}")
|
|
||||||
+
|
|
||||||
+ # 2. 从文件路径加载文档
|
|
||||||
+ if source_file_paths:
|
|
||||||
+ for file_path in source_file_paths:
|
|
||||||
+ try:
|
|
||||||
+ parser = ParserFactory.get_parser(file_path)
|
|
||||||
+ result = parser.parse(file_path)
|
|
||||||
+ if result.success:
|
|
||||||
+ source_docs.append(SourceDocument(
|
|
||||||
+ doc_id=file_path,
|
|
||||||
+ filename=result.metadata.get("filename", file_path.split("/")[-1]),
|
|
||||||
+ doc_type=result.metadata.get("extension", "unknown").replace(".", ""),
|
|
||||||
+ content=result.data.get("content", ""),
|
|
||||||
+ structured_data=result.data.get("structured_data", {})
|
|
||||||
+ ))
|
|
||||||
+ logger.info(f"从文件加载文档: {file_path}")
|
|
||||||
+ except Exception as e:
|
|
||||||
+ logger.error(f"从文件加载文档失败 {file_path}: {str(e)}")
|
|
||||||
+
|
|
||||||
+ return source_docs
|
|
||||||
|
|
||||||
async def _extract_field_value(
|
|
||||||
self,
|
|
||||||
field: TemplateField,
|
|
||||||
- rag_context: List[Dict[str, Any]],
|
|
||||||
+ source_docs: List[SourceDocument],
|
|
||||||
user_hint: Optional[str] = None
|
|
||||||
) -> FillResult:
|
|
||||||
"""
|
|
||||||
- 使用 LLM 从上下文中提取字段值
|
|
||||||
+ 使用 LLM 从源文档中提取字段值
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field: 字段定义
|
|
||||||
- rag_context: RAG 检索到的上下文
|
|
||||||
+ source_docs: 源文档列表
|
|
||||||
user_hint: 用户提示
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
提取结果
|
|
||||||
"""
|
|
||||||
+ if not source_docs:
|
|
||||||
+ return FillResult(
|
|
||||||
+ field=field.name,
|
|
||||||
+ value="",
|
|
||||||
+ source="无源文档",
|
|
||||||
+ confidence=0.0
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
# 构建上下文文本
|
|
||||||
- context_text = "\n\n".join([
|
|
||||||
- f"【文档 {i+1}】\n{doc['content']}"
|
|
||||||
- for i, doc in enumerate(rag_context)
|
|
||||||
- ])
|
|
||||||
+ context_text = self._build_context_text(source_docs, max_length=8000)
|
|
||||||
+
|
|
||||||
+ # 构建提示词
|
|
||||||
+ hint_text = field.hint if field.hint else f"请提取{field.name}的信息"
|
|
||||||
+ if user_hint:
|
|
||||||
+ hint_text = f"{user_hint}。{hint_text}"
|
|
||||||
|
|
||||||
- # 构建 Prompt
|
|
||||||
- prompt = f"""你是一个数据提取专家。请根据以下文档内容,提取指定字段的信息。
|
|
||||||
+ prompt = f"""你是一个专业的数据提取专家。请根据以下文档内容,提取指定字段的信息。
|
|
||||||
|
|
||||||
需要提取的字段:
|
|
||||||
- 字段名称:{field.name}
|
|
||||||
- 字段类型:{field.field_type}
|
|
||||||
+- 填写提示:{hint_text}
|
|
||||||
- 是否必填:{'是' if field.required else '否'}
|
|
||||||
|
|
||||||
-{'用户提示:' + user_hint if user_hint else ''}
|
|
||||||
-
|
|
||||||
参考文档内容:
|
|
||||||
{context_text}
|
|
||||||
|
|
||||||
请严格按照以下 JSON 格式输出,不要添加任何解释:
|
|
||||||
{{
|
|
||||||
"value": "提取到的值,如果没有找到则填写空字符串",
|
|
||||||
- "source": "数据来源的文档描述",
|
|
||||||
- "confidence": 0.0到1.0之间的置信度
|
|
||||||
+ "source": "数据来源的文档描述(如:来自xxx文档)",
|
|
||||||
+ "confidence": 0.0到1.0之间的置信度,表示对提取结果的信心程度"
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
@@ -226,6 +270,54 @@ class TemplateFillService:
|
|
||||||
confidence=0.0
|
|
||||||
)
|
|
||||||
|
|
||||||
+ def _build_context_text(self, source_docs: List[SourceDocument], max_length: int = 8000) -> str:
|
|
||||||
+ """
|
|
||||||
+ 构建上下文文本
|
|
||||||
+
|
|
||||||
+ Args:
|
|
||||||
+ source_docs: 源文档列表
|
|
||||||
+ max_length: 最大字符数
|
|
||||||
+
|
|
||||||
+ Returns:
|
|
||||||
+ 上下文文本
|
|
||||||
+ """
|
|
||||||
+ contexts = []
|
|
||||||
+ total_length = 0
|
|
||||||
+
|
|
||||||
+ for doc in source_docs:
|
|
||||||
+ # 优先使用结构化数据(表格),其次使用文本内容
|
|
||||||
+ doc_content = ""
|
|
||||||
+
|
|
||||||
+ if doc.structured_data and doc.structured_data.get("tables"):
|
|
||||||
+ # 如果有表格数据,优先使用
|
|
||||||
+ tables = doc.structured_data.get("tables", [])
|
|
||||||
+ for table in tables:
|
|
||||||
+ if isinstance(table, dict):
|
|
||||||
+ rows = table.get("rows", [])
|
|
||||||
+ if rows:
|
|
||||||
+ doc_content += f"\n【文档: {doc.filename} 表格数据】\n"
|
|
||||||
+ for row in rows[:20]: # 限制每表最多20行
|
|
||||||
+ if isinstance(row, list):
|
|
||||||
+ doc_content += " | ".join(str(cell) for cell in row) + "\n"
|
|
||||||
+ elif isinstance(row, dict):
|
|
||||||
+ doc_content += " | ".join(str(v) for v in row.values()) + "\n"
|
|
||||||
+ elif doc.content:
|
|
||||||
+ doc_content = doc.content[:5000] # 限制文本长度
|
|
||||||
+
|
|
||||||
+ if doc_content:
|
|
||||||
+ doc_context = f"【文档: {doc.filename} ({doc.doc_type})】\n{doc_content}"
|
|
||||||
+ if total_length + len(doc_context) <= max_length:
|
|
||||||
+ contexts.append(doc_context)
|
|
||||||
+ total_length += len(doc_context)
|
|
||||||
+ else:
|
|
||||||
+ # 如果超出长度,截断
|
|
||||||
+ remaining = max_length - total_length
|
|
||||||
+ if remaining > 100:
|
|
||||||
+ contexts.append(doc_context[:remaining])
|
|
||||||
+ break
|
|
||||||
+
|
|
||||||
+ return "\n\n".join(contexts) if contexts else "(源文档内容为空)"
|
|
||||||
+
|
|
||||||
async def get_template_fields_from_file(
|
|
||||||
self,
|
|
||||||
file_path: str,
|
|
||||||
@@ -236,7 +328,7 @@ class TemplateFillService:
|
|
||||||
|
|
||||||
Args:
|
|
||||||
file_path: 模板文件路径
|
|
||||||
- file_type: 文件类型
|
|
||||||
+ file_type: 文件类型 (xlsx/xls/docx)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
字段列表
|
|
||||||
@@ -245,43 +337,108 @@ class TemplateFillService:
|
|
||||||
|
|
||||||
try:
|
|
||||||
if file_type in ["xlsx", "xls"]:
|
|
||||||
- # 从 Excel 读取表头
|
|
||||||
- import pandas as pd
|
|
||||||
- df = pd.read_excel(file_path, nrows=5)
|
|
||||||
+ fields = await self._get_template_fields_from_excel(file_path)
|
|
||||||
+ elif file_type == "docx":
|
|
||||||
+ fields = await self._get_template_fields_from_docx(file_path)
|
|
||||||
|
|
||||||
- for idx, col in enumerate(df.columns):
|
|
||||||
- # 获取单元格位置 (A, B, C, ...)
|
|
||||||
- cell = self._column_to_cell(idx)
|
|
||||||
+ except Exception as e:
|
|
||||||
+ logger.error(f"提取模板字段失败: {str(e)}")
|
|
||||||
|
|
||||||
- fields.append(TemplateField(
|
|
||||||
- cell=cell,
|
|
||||||
- name=str(col),
|
|
||||||
- field_type=self._infer_field_type(df[col]),
|
|
||||||
- required=True
|
|
||||||
- ))
|
|
||||||
+ return fields
|
|
||||||
|
|
||||||
- elif file_type == "docx":
|
|
||||||
- # 从 Word 表格读取
|
|
||||||
- from docx import Document
|
|
||||||
- doc = Document(file_path)
|
|
||||||
-
|
|
||||||
- for table_idx, table in enumerate(doc.tables):
|
|
||||||
- for row_idx, row in enumerate(table.rows):
|
|
||||||
- for col_idx, cell in enumerate(row.cells):
|
|
||||||
- cell_text = cell.text.strip()
|
|
||||||
- if cell_text:
|
|
||||||
- fields.append(TemplateField(
|
|
||||||
- cell=self._column_to_cell(col_idx),
|
|
||||||
- name=cell_text,
|
|
||||||
- field_type="text",
|
|
||||||
- required=True
|
|
||||||
- ))
|
|
||||||
+ async def _get_template_fields_from_excel(self, file_path: str) -> List[TemplateField]:
|
|
||||||
+ """从 Excel 模板提取字段"""
|
|
||||||
+ fields = []
|
|
||||||
+
|
|
||||||
+ try:
|
|
||||||
+ import pandas as pd
|
|
||||||
+ df = pd.read_excel(file_path, nrows=5)
|
|
||||||
+
|
|
||||||
+ for idx, col in enumerate(df.columns):
|
|
||||||
+ cell = self._column_to_cell(idx)
|
|
||||||
+ col_str = str(col)
|
|
||||||
+
|
|
||||||
+ fields.append(TemplateField(
|
|
||||||
+ cell=cell,
|
|
||||||
+ name=col_str,
|
|
||||||
+ field_type=self._infer_field_type_from_value(df[col].iloc[0] if len(df) > 0 else ""),
|
|
||||||
+ required=True,
|
|
||||||
+ hint=""
|
|
||||||
+ ))
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
- logger.error(f"提取模板字段失败: {str(e)}")
|
|
||||||
+ logger.error(f"从Excel提取字段失败: {str(e)}")
|
|
||||||
|
|
||||||
return fields
|
|
||||||
|
|
||||||
+ async def _get_template_fields_from_docx(self, file_path: str) -> List[TemplateField]:
|
|
||||||
+ """从 Word 模板提取字段"""
|
|
||||||
+ fields = []
|
|
||||||
+
|
|
||||||
+ try:
|
|
||||||
+ from docx import Document
|
|
||||||
+
|
|
||||||
+ doc = Document(file_path)
|
|
||||||
+
|
|
||||||
+ for table_idx, table in enumerate(doc.tables):
|
|
||||||
+ for row_idx, row in enumerate(table.rows):
|
|
||||||
+ cells = [cell.text.strip() for cell in row.cells]
|
|
||||||
+
|
|
||||||
+ # 假设第一列是字段名
|
|
||||||
+ if cells and cells[0]:
|
|
||||||
+ field_name = cells[0]
|
|
||||||
+ hint = cells[1] if len(cells) > 1 else ""
|
|
||||||
+
|
|
||||||
+ # 跳过空行或标题行
|
|
||||||
+ if field_name and field_name not in ["", "字段名", "名称", "项目"]:
|
|
||||||
+ fields.append(TemplateField(
|
|
||||||
+ cell=f"T{table_idx}R{row_idx}",
|
|
||||||
+ name=field_name,
|
|
||||||
+ field_type=self._infer_field_type_from_hint(hint),
|
|
||||||
+ required=True,
|
|
||||||
+ hint=hint
|
|
||||||
+ ))
|
|
||||||
+
|
|
||||||
+ except Exception as e:
|
|
||||||
+ logger.error(f"从Word提取字段失败: {str(e)}")
|
|
||||||
+
|
|
||||||
+ return fields
|
|
||||||
+
|
|
||||||
+ def _infer_field_type_from_hint(self, hint: str) -> str:
|
|
||||||
+ """从提示词推断字段类型"""
|
|
||||||
+ hint_lower = hint.lower()
|
|
||||||
+
|
|
||||||
+ date_keywords = ["年", "月", "日", "日期", "时间", "出生"]
|
|
||||||
+ if any(kw in hint for kw in date_keywords):
|
|
||||||
+ return "date"
|
|
||||||
+
|
|
||||||
+ number_keywords = ["数量", "金额", "人数", "面积", "增长", "比率", "%", "率", "总计", "合计"]
|
|
||||||
+ if any(kw in hint_lower for kw in number_keywords):
|
|
||||||
+ return "number"
|
|
||||||
+
|
|
||||||
+ return "text"
|
|
||||||
+
|
|
||||||
+ def _infer_field_type_from_value(self, value: Any) -> str:
|
|
||||||
+ """从示例值推断字段类型"""
|
|
||||||
+ if value is None or value == "":
|
|
||||||
+ return "text"
|
|
||||||
+
|
|
||||||
+ value_str = str(value)
|
|
||||||
+
|
|
||||||
+ # 检查日期模式
|
|
||||||
+ import re
|
|
||||||
+ if re.search(r'\d{4}[年/-]\d{1,2}[月/-]\d{1,2}', value_str):
|
|
||||||
+ return "date"
|
|
||||||
+
|
|
||||||
+ # 检查数值
|
|
||||||
+ try:
|
|
||||||
+ float(value_str.replace(',', '').replace('%', ''))
|
|
||||||
+ return "number"
|
|
||||||
+ except ValueError:
|
|
||||||
+ pass
|
|
||||||
+
|
|
||||||
+ return "text"
|
|
||||||
+
|
|
||||||
def _column_to_cell(self, col_idx: int) -> str:
|
|
||||||
"""将列索引转换为单元格列名 (0 -> A, 1 -> B, ...)"""
|
|
||||||
result = ""
|
|
||||||
@@ -290,17 +447,6 @@ class TemplateFillService:
|
|
||||||
col_idx = col_idx // 26 - 1
|
|
||||||
return result
|
|
||||||
|
|
||||||
- def _infer_field_type(self, series) -> str:
|
|
||||||
- """推断字段类型"""
|
|
||||||
- import pandas as pd
|
|
||||||
-
|
|
||||||
- if pd.api.types.is_numeric_dtype(series):
|
|
||||||
- return "number"
|
|
||||||
- elif pd.api.types.is_datetime64_any_dtype(series):
|
|
||||||
- return "date"
|
|
||||||
- else:
|
|
||||||
- return "text"
|
|
||||||
-
|
|
||||||
|
|
||||||
# ==================== 全局单例 ====================
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
diff --git a/frontend/src/db/backend-api.ts b/frontend/src/db/backend-api.ts
|
|
||||||
index 8944353..94ac852 100644
|
|
||||||
--- a/frontend/src/db/backend-api.ts
|
|
||||||
+++ b/frontend/src/db/backend-api.ts
|
|
||||||
@@ -92,6 +92,7 @@ export interface TemplateField {
|
|
||||||
name: string;
|
|
||||||
field_type: string;
|
|
||||||
required: boolean;
|
|
||||||
+ hint?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格填写结果
|
|
||||||
@@ -625,7 +626,10 @@ export const backendApi = {
|
|
||||||
*/
|
|
||||||
async fillTemplate(
|
|
||||||
templateId: string,
|
|
||||||
- templateFields: TemplateField[]
|
|
||||||
+ templateFields: TemplateField[],
|
|
||||||
+ sourceDocIds?: string[],
|
|
||||||
+ sourceFilePaths?: string[],
|
|
||||||
+ userHint?: string
|
|
||||||
): Promise<FillResult> {
|
|
||||||
const url = `${BACKEND_BASE_URL}/templates/fill`;
|
|
||||||
|
|
||||||
@@ -636,6 +640,9 @@ export const backendApi = {
|
|
||||||
body: JSON.stringify({
|
|
||||||
template_id: templateId,
|
|
||||||
template_fields: templateFields,
|
|
||||||
+ source_doc_ids: sourceDocIds || [],
|
|
||||||
+ source_file_paths: sourceFilePaths || [],
|
|
||||||
+ user_hint: userHint || null,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
diff --git a/frontend/src/pages/TemplateFill.tsx b/frontend/src/pages/TemplateFill.tsx
|
|
||||||
index 8c330a9..f9a4a39 100644
|
|
||||||
--- a/frontend/src/pages/TemplateFill.tsx
|
|
||||||
+++ b/frontend/src/pages/TemplateFill.tsx
|
|
||||||
@@ -128,8 +128,12 @@ const TemplateFill: React.FC = () => {
|
|
||||||
setStep('filling');
|
|
||||||
|
|
||||||
try {
|
|
||||||
- // 调用后端填表接口
|
|
||||||
- const result = await backendApi.fillTemplate('temp-template-id', templateFields);
|
|
||||||
+ // 调用后端填表接口,传递选中的文档ID
|
|
||||||
+ const result = await backendApi.fillTemplate(
|
|
||||||
+ 'temp-template-id',
|
|
||||||
+ templateFields,
|
|
||||||
+ selectedDocs // 传递源文档ID列表
|
|
||||||
+ );
|
|
||||||
setFilledResult(result);
|
|
||||||
setStep('preview');
|
|
||||||
toast.success('表格填写完成');
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
diff --git "a/\346\257\224\350\265\233\345\244\207\350\265\233\350\247\204\345\210\222.md" "b/\346\257\224\350\265\233\345\244\207\350\265\233\350\247\204\345\210\222.md"
|
|
||||||
index bcb48fd..440a12d 100644
|
|
||||||
--- "a/\346\257\224\350\265\233\345\244\207\350\265\233\350\247\204\345\210\222.md"
|
|
||||||
+++ "b/\346\257\224\350\265\233\345\244\207\350\265\233\350\247\204\345\210\222.md"
|
|
||||||
@@ -50,7 +50,7 @@
|
|
||||||
| `prompt_service.py` | ✅ 已完成 | Prompt 模板管理 |
|
|
||||||
| `text_analysis_service.py` | ✅ 已完成 | 文本分析 |
|
|
||||||
| `chart_generator_service.py` | ✅ 已完成 | 图表生成服务 |
|
|
||||||
-| `template_fill_service.py` | ❌ 未完成 | 模板填写服务 |
|
|
||||||
+| `template_fill_service.py` | ✅ 已完成 | 模板填写服务,支持直接读取源文档进行填表 |
|
|
||||||
|
|
||||||
### 2.2 API 接口 (`backend/app/api/endpoints/`)
|
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@
|
|
||||||
| `ai_analyze.py` | `/api/v1/analyze/*` | ✅ AI 分析(Excel、Markdown、流式) |
|
|
||||||
| `rag.py` | `/api/v1/rag/*` | ⚠️ RAG 检索(当前返回空) |
|
|
||||||
| `tasks.py` | `/api/v1/tasks/*` | ✅ 异步任务状态查询 |
|
|
||||||
-| `templates.py` | `/api/v1/templates/*` | ✅ 模板管理 |
|
|
||||||
+| `templates.py` | `/api/v1/templates/*` | ✅ 模板管理 (含 Word 导出) |
|
|
||||||
| `visualization.py` | `/api/v1/visualization/*` | ✅ 可视化图表 |
|
|
||||||
| `health.py` | `/api/v1/health` | ✅ 健康检查 |
|
|
||||||
|
|
||||||
@@ -78,8 +78,8 @@
|
|
||||||
|------|----------|------|
|
|
||||||
| Excel (.xlsx/.xls) | ✅ 已完成 | pandas + XML 回退解析 |
|
|
||||||
| Markdown (.md) | ✅ 已完成 | 正则 + AI 分章节 |
|
|
||||||
-| Word (.docx) | ❌ 未完成 | 尚未实现 |
|
|
||||||
-| Text (.txt) | ❌ 未完成 | 尚未实现 |
|
|
||||||
+| Word (.docx) | ✅ 已完成 | python-docx 解析,支持表格提取和字段识别 |
|
|
||||||
+| Text (.txt) | ✅ 已完成 | chardet 编码检测,支持文本清洗和结构化提取 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@
|
|
||||||
|
|
||||||
### 3.1 模板填写模块(最优先)
|
|
||||||
|
|
||||||
-**这是比赛的核心评测功能,必须完成。**
|
|
||||||
+**当前状态**:✅ 已完成
|
|
||||||
|
|
||||||
```
|
|
||||||
用户上传模板表格(Word/Excel)
|
|
||||||
@@ -103,30 +103,34 @@ AI 根据字段提示词从源数据中提取信息
|
|
||||||
返回填写完成的表格
|
|
||||||
```
|
|
||||||
|
|
||||||
-**需要实现**:
|
|
||||||
-- [ ] `template_fill_service.py` - 模板填写核心服务
|
|
||||||
-- [ ] Word 模板解析 (`docx_parser.py` 需新建)
|
|
||||||
-- [ ] Text 模板解析 (`txt_parser.py` 需新建)
|
|
||||||
-- [ ] 模板字段识别与提示词提取
|
|
||||||
-- [ ] 多文档数据聚合与冲突处理
|
|
||||||
-- [ ] 结果导出为 Word/Excel
|
|
||||||
+**已完成实现**:
|
|
||||||
+- [x] `template_fill_service.py` - 模板填写核心服务
|
|
||||||
+- [x] Word 模板解析 (`docx_parser.py` - parse_tables_for_template, extract_template_fields_from_docx)
|
|
||||||
+- [x] Text 模板解析 (`txt_parser.py` - 已完成)
|
|
||||||
+- [x] 模板字段识别与提示词提取
|
|
||||||
+- [x] 多文档数据聚合与冲突处理
|
|
||||||
+- [x] 结果导出为 Word/Excel
|
|
||||||
|
|
||||||
### 3.2 Word 文档解析
|
|
||||||
|
|
||||||
-**当前状态**:仅有框架,尚未实现具体解析逻辑
|
|
||||||
+**当前状态**:✅ 已完成
|
|
||||||
|
|
||||||
-**需要实现**:
|
|
||||||
-- [ ] `docx_parser.py` - Word 文档解析器
|
|
||||||
-- [ ] 提取段落文本
|
|
||||||
-- [ ] 提取表格内容
|
|
||||||
-- [ ] 提取关键信息(标题、列表等)
|
|
||||||
+**已实现功能**:
|
|
||||||
+- [x] `docx_parser.py` - Word 文档解析器
|
|
||||||
+- [x] 提取段落文本
|
|
||||||
+- [x] 提取表格内容
|
|
||||||
+- [x] 提取关键信息(标题、列表等)
|
|
||||||
+- [x] 表格模板字段提取 (`parse_tables_for_template`, `extract_template_fields_from_docx`)
|
|
||||||
+- [x] 字段类型推断 (`_infer_field_type_from_hint`)
|
|
||||||
|
|
||||||
### 3.3 Text 文档解析
|
|
||||||
|
|
||||||
-**需要实现**:
|
|
||||||
-- [ ] `txt_parser.py` - 文本文件解析器
|
|
||||||
-- [ ] 编码自动检测
|
|
||||||
-- [ ] 文本清洗
|
|
||||||
+**当前状态**:✅ 已完成
|
|
||||||
+
|
|
||||||
+**已实现功能**:
|
|
||||||
+- [x] `txt_parser.py` - 文本文件解析器
|
|
||||||
+- [x] 编码自动检测 (chardet)
|
|
||||||
+- [x] 文本清洗
|
|
||||||
|
|
||||||
### 3.4 文档模板匹配(已有框架)
|
|
||||||
|
|
||||||
@@ -215,5 +219,122 @@ docs/test/
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
-*文档版本: v1.0*
|
|
||||||
-*最后更新: 2026-04-08*
|
|
||||||
\ No newline at end of file
|
|
||||||
+*文档版本: v1.1*
|
|
||||||
+*最后更新: 2026-04-08*
|
|
||||||
+
|
|
||||||
+---
|
|
||||||
+
|
|
||||||
+## 八、技术实现细节
|
|
||||||
+
|
|
||||||
+### 8.1 模板填表流程(已实现)
|
|
||||||
+
|
|
||||||
+#### 流程图
|
|
||||||
+```
|
|
||||||
+┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
||||||
+│ 上传模板 │ ──► │ 选择数据源 │ ──► │ AI 智能填表 │
|
|
||||||
+└─────────────┘ └─────────────┘ └─────────────┘
|
|
||||||
+ │
|
|
||||||
+ ▼
|
|
||||||
+ ┌─────────────┐
|
|
||||||
+ │ 导出结果 │
|
|
||||||
+ └─────────────┘
|
|
||||||
+```
|
|
||||||
+
|
|
||||||
+#### 核心组件
|
|
||||||
+
|
|
||||||
+| 组件 | 文件 | 说明 |
|
|
||||||
+|------|------|------|
|
|
||||||
+| 模板上传 | `templates.py` `/templates/upload` | 接收模板文件,提取字段 |
|
|
||||||
+| 字段提取 | `template_fill_service.py` | 从 Word/Excel 表格提取字段定义 |
|
|
||||||
+| 文档解析 | `docx_parser.py`, `xlsx_parser.py`, `txt_parser.py` | 解析源文档内容 |
|
|
||||||
+| 智能填表 | `template_fill_service.py` `fill_template()` | 使用 LLM 从源文档提取信息 |
|
|
||||||
+| 结果导出 | `templates.py` `/templates/export` | 导出为 Excel 或 Word |
|
|
||||||
+
|
|
||||||
+### 8.2 源文档加载方式
|
|
||||||
+
|
|
||||||
+模板填表服务支持两种方式加载源文档:
|
|
||||||
+
|
|
||||||
+1. **通过 MongoDB 文档 ID**:`source_doc_ids`
|
|
||||||
+ - 文档已上传并存入 MongoDB
|
|
||||||
+ - 服务直接查询 MongoDB 获取文档内容
|
|
||||||
+
|
|
||||||
+2. **通过文件路径**:`source_file_paths`
|
|
||||||
+ - 直接读取本地文件
|
|
||||||
+ - 使用对应的解析器解析内容
|
|
||||||
+
|
|
||||||
+### 8.3 Word 表格模板解析
|
|
||||||
+
|
|
||||||
+比赛评分表格通常是 Word 格式,`docx_parser.py` 提供了专门的解析方法:
|
|
||||||
+
|
|
||||||
+```python
|
|
||||||
+# 提取表格模板字段
|
|
||||||
+fields = docx_parser.extract_template_fields_from_docx(file_path)
|
|
||||||
+
|
|
||||||
+# 返回格式
|
|
||||||
+# [
|
|
||||||
+# {
|
|
||||||
+# "cell": "T0R1", # 表格0,行1
|
|
||||||
+# "name": "字段名",
|
|
||||||
+# "hint": "提示词",
|
|
||||||
+# "field_type": "text/number/date",
|
|
||||||
+# "required": True
|
|
||||||
+# },
|
|
||||||
+# ...
|
|
||||||
+# ]
|
|
||||||
+```
|
|
||||||
+
|
|
||||||
+### 8.4 字段类型推断
|
|
||||||
+
|
|
||||||
+系统支持从提示词自动推断字段类型:
|
|
||||||
+
|
|
||||||
+| 关键词 | 推断类型 | 示例 |
|
|
||||||
+|--------|----------|------|
|
|
||||||
+| 年、月、日、日期、时间、出生 | date | 出生日期 |
|
|
||||||
+| 数量、金额、比率、%、率、合计 | number | 增长比率 |
|
|
||||||
+| 其他 | text | 姓名、地址 |
|
|
||||||
+
|
|
||||||
+### 8.5 API 接口
|
|
||||||
+
|
|
||||||
+#### POST `/api/v1/templates/fill`
|
|
||||||
+
|
|
||||||
+填写请求:
|
|
||||||
+```json
|
|
||||||
+{
|
|
||||||
+ "template_id": "模板ID",
|
|
||||||
+ "template_fields": [
|
|
||||||
+ {"cell": "A1", "name": "姓名", "field_type": "text", "required": true, "hint": "提取人员姓名"}
|
|
||||||
+ ],
|
|
||||||
+ "source_doc_ids": ["mongodb_doc_id_1", "mongodb_doc_id_2"],
|
|
||||||
+ "source_file_paths": [],
|
|
||||||
+ "user_hint": "请从合同文档中提取"
|
|
||||||
+}
|
|
||||||
+```
|
|
||||||
+
|
|
||||||
+响应:
|
|
||||||
+```json
|
|
||||||
+{
|
|
||||||
+ "success": true,
|
|
||||||
+ "filled_data": {"姓名": "张三"},
|
|
||||||
+ "fill_details": [
|
|
||||||
+ {
|
|
||||||
+ "field": "姓名",
|
|
||||||
+ "cell": "A1",
|
|
||||||
+ "value": "张三",
|
|
||||||
+ "source": "来自:合同文档.docx",
|
|
||||||
+ "confidence": 0.95
|
|
||||||
+ }
|
|
||||||
+ ],
|
|
||||||
+ "source_doc_count": 2
|
|
||||||
+}
|
|
||||||
+```
|
|
||||||
+
|
|
||||||
+#### POST `/api/v1/templates/export`
|
|
||||||
+
|
|
||||||
+导出请求:
|
|
||||||
+```json
|
|
||||||
+{
|
|
||||||
+ "template_id": "模板ID",
|
|
||||||
+ "filled_data": {"姓名": "张三", "金额": "10000"},
|
|
||||||
+ "format": "xlsx" // 或 "docx"
|
|
||||||
+}
|
|
||||||
+```
|
|
||||||
\ No newline at end of file
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
RAG 服务临时禁用说明
|
|
||||||
========================
|
|
||||||
日期: 2026-04-08
|
|
||||||
|
|
||||||
修改内容:
|
|
||||||
----------
|
|
||||||
应需求,RAG 向量检索功能已临时禁用,具体如下:
|
|
||||||
|
|
||||||
1. 修改文件: backend/app/services/rag_service.py
|
|
||||||
|
|
||||||
2. 关键变更:
|
|
||||||
- 在 RAGService.__init__ 中添加 self._disabled = True 标志
|
|
||||||
- index_field() - 添加 _disabled 检查,跳过实际索引操作并记录日志
|
|
||||||
- index_document_content() - 添加 _disabled 检查,跳过实际索引操作并记录日志
|
|
||||||
- retrieve() - 添加 _disabled 检查,返回空列表并记录日志
|
|
||||||
- get_vector_count() - 添加 _disabled 检查,返回 0 并记录日志
|
|
||||||
- clear() - 添加 _disabled 检查,跳过实际清空操作并记录日志
|
|
||||||
|
|
||||||
3. 行为变更:
|
|
||||||
- 所有 RAG 索引构建操作会被记录到日志 ([RAG DISABLED] 前缀)
|
|
||||||
- 所有 RAG 检索操作返回空结果
|
|
||||||
- 向量计数始终返回 0
|
|
||||||
- 实际向量数据库操作被跳过
|
|
||||||
|
|
||||||
4. 恢复方式:
|
|
||||||
- 将 RAGService.__init__ 中的 self._disabled = True 改为 self._disabled = False
|
|
||||||
- 重新启动服务即可恢复 RAG 功能
|
|
||||||
|
|
||||||
目的:
|
|
||||||
------
|
|
||||||
保留 RAG 索引构建功能的前端界面和代码结构,暂不实际调用向量数据库 API,
|
|
||||||
待后续需要时再启用。
|
|
||||||
|
|
||||||
影响范围:
|
|
||||||
---------
|
|
||||||
- /api/v1/rag/search - RAG 搜索接口 (返回空结果)
|
|
||||||
- /api/v1/rag/status - RAG 状态接口 (返回 vector_count=0)
|
|
||||||
- /api/v1/rag/rebuild - RAG 重建接口 (仅记录日志)
|
|
||||||
- Excel/文档上传时的 RAG 索引构建 (仅记录日志)
|
|
||||||
|
|
||||||
========================
|
|
||||||
后续补充 (2026-04-08):
|
|
||||||
========================
|
|
||||||
修改文件: backend/app/services/table_rag_service.py
|
|
||||||
|
|
||||||
关键变更:
|
|
||||||
- 在 TableRAGService.__init__ 中添加 self._disabled = True 标志
|
|
||||||
- build_table_rag_index() - RAG 索引部分被跳过,仅记录日志
|
|
||||||
- index_document_table() - RAG 索引部分被跳过,仅记录日志
|
|
||||||
|
|
||||||
行为变更:
|
|
||||||
- Excel 上传时,MySQL 存储仍然正常进行
|
|
||||||
- AI 字段描述仍然正常生成(调用 LLM)
|
|
||||||
- 只有向量数据库索引操作被跳过
|
|
||||||
|
|
||||||
恢复方式:
|
|
||||||
- 将 TableRAGService.__init__ 中的 self._disabled = True 改为 self._disabled = False
|
|
||||||
- 或将 rag_service.py 中的 self._disabled = True 改为 self._disabled = False
|
|
||||||
- 两者需同时改为 False 才能完全恢复 RAG 功能
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
# 模板填表功能变更日志
|
|
||||||
|
|
||||||
**变更日期**: 2026-04-08
|
|
||||||
**变更类型**: 功能完善
|
|
||||||
**变更内容**: Word 表格解析和模板填表功能
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 变更概述
|
|
||||||
|
|
||||||
本次变更完善了 Word 表格解析、表格模板构建和填写功能,实现了从源文档(MongoDB/文件)读取数据并智能填表的核心流程。
|
|
||||||
|
|
||||||
### 涉及文件
|
|
||||||
|
|
||||||
| 文件 | 变更行数 | 说明 |
|
|
||||||
|------|----------|------|
|
|
||||||
| backend/app/api/endpoints/templates.py | +156 | API 端点完善,添加 Word 导出 |
|
|
||||||
| backend/app/core/document_parser/docx_parser.py | +130 | Word 表格解析增强 |
|
|
||||||
| backend/app/services/template_fill_service.py | +340 | 核心填表服务重写 |
|
|
||||||
| frontend/src/db/backend-api.ts | +9 | 前端 API 更新 |
|
|
||||||
| frontend/src/pages/TemplateFill.tsx | +8 | 前端页面更新 |
|
|
||||||
| 比赛备赛规划.md | +169 | 文档更新 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 详细变更
|
|
||||||
|
|
||||||
### 1. backend/app/core/document_parser/docx_parser.py
|
|
||||||
|
|
||||||
**新增方法**:
|
|
||||||
|
|
||||||
- `parse_tables_for_template(file_path)` - 解析 Word 文档中的表格,提取模板字段
|
|
||||||
- `extract_template_fields_from_docx(file_path)` - 从 Word 文档提取模板字段定义
|
|
||||||
- `_infer_field_type_from_hint(hint)` - 从提示词推断字段类型
|
|
||||||
|
|
||||||
**功能说明**:
|
|
||||||
- 专门用于比赛场景:解析表格模板,识别需要填写的字段
|
|
||||||
- 支持从表格第一列提取字段名,第二列提取提示词/描述
|
|
||||||
- 自动推断字段类型(text/number/date)
|
|
||||||
|
|
||||||
### 2. backend/app/services/template_fill_service.py
|
|
||||||
|
|
||||||
**重构内容**:
|
|
||||||
|
|
||||||
- 不再依赖 RAG 服务,直接从 MongoDB 或文件读取源文档
|
|
||||||
- 新增 `SourceDocument` 数据类
|
|
||||||
- 完善 `fill_template()` 方法,支持 `source_doc_ids` 和 `source_file_paths`
|
|
||||||
- 新增 `_load_source_documents()` - 加载源文档内容
|
|
||||||
- 新增 `_extract_field_value()` - 使用 LLM 提取字段值
|
|
||||||
- 新增 `_build_context_text()` - 构建上下文(优先使用表格数据)
|
|
||||||
- 完善 `_get_template_fields_from_docx()` - Word 模板字段提取
|
|
||||||
|
|
||||||
**核心流程**:
|
|
||||||
```
|
|
||||||
1. 加载源文档(MongoDB 或文件)
|
|
||||||
2. 对每个字段调用 LLM 提取值
|
|
||||||
3. 返回填写结果
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. backend/app/api/endpoints/templates.py
|
|
||||||
|
|
||||||
**新增内容**:
|
|
||||||
|
|
||||||
- `FillRequest` 添加 `source_doc_ids`, `source_file_paths`, `user_hint` 字段
|
|
||||||
- `ExportRequest` 添加 `format` 字段
|
|
||||||
- `_export_to_word()` - 导出为 Word 格式
|
|
||||||
- `/templates/export/excel` - 专门导出 Excel
|
|
||||||
- `/templates/export/word` - 专门导出 Word
|
|
||||||
|
|
||||||
### 4. frontend/src/db/backend-api.ts
|
|
||||||
|
|
||||||
**更新内容**:
|
|
||||||
|
|
||||||
- `TemplateField` 接口添加 `hint` 字段
|
|
||||||
- `fillTemplate()` 方法添加 `sourceDocIds`, `sourceFilePaths`, `userHint` 参数
|
|
||||||
|
|
||||||
### 5. frontend/src/pages/TemplateFill.tsx
|
|
||||||
|
|
||||||
**更新内容**:
|
|
||||||
|
|
||||||
- `handleFillTemplate()` 传递 `selectedDocs` 作为 `sourceDocIds` 参数
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API 接口变更
|
|
||||||
|
|
||||||
### POST /api/v1/templates/fill
|
|
||||||
|
|
||||||
**请求体**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"template_id": "模板ID",
|
|
||||||
"template_fields": [
|
|
||||||
{
|
|
||||||
"cell": "A1",
|
|
||||||
"name": "姓名",
|
|
||||||
"field_type": "text",
|
|
||||||
"required": true,
|
|
||||||
"hint": "提取人员姓名"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_doc_ids": ["mongodb_doc_id"],
|
|
||||||
"source_file_paths": [],
|
|
||||||
"user_hint": "请从xxx文档中提取"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"filled_data": {"姓名": "张三"},
|
|
||||||
"fill_details": [...],
|
|
||||||
"source_doc_count": 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### POST /api/v1/templates/export
|
|
||||||
|
|
||||||
**新增支持 format=dicx**,可导出为 Word 格式
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 技术细节
|
|
||||||
|
|
||||||
### 字段类型推断
|
|
||||||
|
|
||||||
| 关键词 | 推断类型 |
|
|
||||||
|--------|----------|
|
|
||||||
| 年、月、日、日期、时间、出生 | date |
|
|
||||||
| 数量、金额、比率、%、率、合计 | number |
|
|
||||||
| 其他 | text |
|
|
||||||
|
|
||||||
### 上下文构建
|
|
||||||
|
|
||||||
源文档内容构建优先级:
|
|
||||||
1. 结构化数据(表格数据)
|
|
||||||
2. 原始文本内容(限制 5000 字符)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相关文档
|
|
||||||
|
|
||||||
- [比赛备赛规划.md](../比赛备赛规划.md) - 已更新功能状态和技术实现细节
|
|
||||||
558
比赛备赛规划.md
558
比赛备赛规划.md
@@ -1,558 +0,0 @@
|
|||||||
# 比赛备赛规划文档
|
|
||||||
|
|
||||||
## 一、赛题核心理解
|
|
||||||
|
|
||||||
### 1.1 赛题名称
|
|
||||||
**A23 - 基于大语言模型的文档理解与多源数据融合**
|
|
||||||
参赛院校:金陵科技学院
|
|
||||||
|
|
||||||
### 1.2 核心任务
|
|
||||||
1. **文档解析**:解析 docx/md/xlsx/txt 四种格式的源数据文档
|
|
||||||
2. **模板填写**:根据模板表格要求,从源文档中提取数据填写到 Word/Excel 模板
|
|
||||||
3. **准确率与速度**:准确率优先,速度作为辅助评分因素
|
|
||||||
|
|
||||||
### 1.3 评分规则
|
|
||||||
| 要素 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| 准确率 | 填写结果与样例表格对比的正确率 |
|
|
||||||
| 响应时间 | 从导入文档到得到结果的时间 ≤ 90s × 文档数量 |
|
|
||||||
| 评测方式 | 赛方提供空表格模板 + 样例表格(人工填写),系统自动填写后对比 |
|
|
||||||
|
|
||||||
### 1.4 关键Q&A摘录
|
|
||||||
|
|
||||||
| 问题 | 解答要点 |
|
|
||||||
|------|----------|
|
|
||||||
| Q2: 模板与文档的关系 | 前2个表格只涉及1份文档;第3-4个涉及多份文档;第5个涉及大部分文档(从易到难) |
|
|
||||||
| Q5: 响应时间定义 | 从导入文档到最终得到结果的时间 ≤ 90s × 文档数量 |
|
|
||||||
| Q7: 需要读取哪些文件 | 每个模板只读取指定的数据文件,不需要读取全部 |
|
|
||||||
| Q10: 部署方式 | 不要求部署到服务器,本地部署即可 |
|
|
||||||
| Q14: 模板匹配 | 模板已指定数据文件,不需要算法匹配 |
|
|
||||||
| Q16: 数据库存储 | 可跳过,不强制要求 |
|
|
||||||
| Q20: 创新点 | 不用管,随意发挥 |
|
|
||||||
| Q21: 填写依据 | 按照测试表格模板给的提示词进行填写 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、已完成功能清单
|
|
||||||
|
|
||||||
### 2.1 后端服务 (`backend/app/services/`)
|
|
||||||
|
|
||||||
| 服务文件 | 功能状态 | 说明 |
|
|
||||||
|----------|----------|------|
|
|
||||||
| `file_service.py` | ✅ 已完成 | 文件上传、保存、类型识别 |
|
|
||||||
| `excel_storage_service.py` | ✅ 已完成 | Excel 存储到 MySQL,支持 XML 回退解析 |
|
|
||||||
| `table_rag_service.py` | ⚠️ 已禁用 | RAG 索引构建(当前禁用,仅记录日志) |
|
|
||||||
| `llm_service.py` | ✅ 已完成 | LLM 调用、流式输出、多模型支持 |
|
|
||||||
| `markdown_ai_service.py` | ✅ 已完成 | Markdown AI 分析、分章节提取、流式输出、图表生成 |
|
|
||||||
| `excel_ai_service.py` | ✅ 已完成 | Excel AI 分析 |
|
|
||||||
| `visualization_service.py` | ✅ 已完成 | 图表生成(matplotlib) |
|
|
||||||
| `rag_service.py` | ⚠️ 已禁用 | FAISS 向量检索(当前禁用) |
|
|
||||||
| `prompt_service.py` | ✅ 已完成 | Prompt 模板管理 |
|
|
||||||
| `text_analysis_service.py` | ✅ 已完成 | 文本分析 |
|
|
||||||
| `chart_generator_service.py` | ✅ 已完成 | 图表生成服务 |
|
|
||||||
| `template_fill_service.py` | ✅ 已完成 | 模板填写服务,支持多行提取、直接从结构化数据提取、JSON容错、Word文档表格处理 |
|
|
||||||
|
|
||||||
### 2.2 API 接口 (`backend/app/api/endpoints/`)
|
|
||||||
|
|
||||||
| 接口文件 | 路由 | 功能状态 |
|
|
||||||
|----------|------|----------|
|
|
||||||
| `upload.py` | `/api/v1/upload/document` | ✅ 文档上传与解析 |
|
|
||||||
| `documents.py` | `/api/v1/documents/*` | ✅ 文档管理(列表、删除、搜索) |
|
|
||||||
| `ai_analyze.py` | `/api/v1/analyze/*` | ✅ AI 分析(Excel、Markdown、流式) |
|
|
||||||
| `rag.py` | `/api/v1/rag/*` | ⚠️ RAG 检索(当前返回空) |
|
|
||||||
| `tasks.py` | `/api/v1/tasks/*` | ✅ 异步任务状态查询 |
|
|
||||||
| `templates.py` | `/api/v1/templates/*` | ✅ 模板管理(含多行导出、Word导出、Word结构化字段解析) |
|
|
||||||
| `visualization.py` | `/api/v1/visualization/*` | ✅ 可视化图表 |
|
|
||||||
| `health.py` | `/api/v1/health` | ✅ 健康检查 |
|
|
||||||
|
|
||||||
### 2.3 前端页面 (`frontend/src/pages/`)
|
|
||||||
|
|
||||||
| 页面文件 | 功能 | 状态 |
|
|
||||||
|----------|------|------|
|
|
||||||
| `Documents.tsx` | 主文档管理页面 | ✅ 已完成 |
|
|
||||||
| `TemplateFill.tsx` | 智能填表页面 | ✅ 已完成 |
|
|
||||||
| `ExcelParse.tsx` | Excel 解析页面 | ✅ 已完成 |
|
|
||||||
|
|
||||||
### 2.4 文档解析能力
|
|
||||||
|
|
||||||
| 格式 | 解析状态 | 说明 |
|
|
||||||
|------|----------|------|
|
|
||||||
| Excel (.xlsx/.xls) | ✅ 已完成 | pandas + XML 回退解析,支持多sheet |
|
|
||||||
| Markdown (.md) | ✅ 已完成 | 正则 + AI 分章节 |
|
|
||||||
| Word (.docx) | ✅ 已完成 | python-docx 解析,支持表格提取和字段识别 |
|
|
||||||
| Text (.txt) | ✅ 已完成 | chardet 编码检测,支持文本清洗和结构化提取 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、核心功能实现详情
|
|
||||||
|
|
||||||
### 3.1 模板填写模块(✅ 已完成)
|
|
||||||
|
|
||||||
**核心流程**:
|
|
||||||
```
|
|
||||||
上传模板表格(Word/Excel)
|
|
||||||
↓
|
|
||||||
解析模板,提取需要填写的字段和提示词
|
|
||||||
↓
|
|
||||||
根据源文档ID列表读取源数据(MongoDB或文件)
|
|
||||||
↓
|
|
||||||
优先从结构化数据直接提取(Excel rows)
|
|
||||||
↓
|
|
||||||
无法直接提取时使用 LLM 从文本中提取
|
|
||||||
↓
|
|
||||||
将提取的数据填入原始模板对应位置(保持模板格式)
|
|
||||||
↓
|
|
||||||
导出填写完成的表格(Excel/Word)
|
|
||||||
```
|
|
||||||
|
|
||||||
**关键特性**:
|
|
||||||
- **原始模板填充**:直接打开原始模板文件,填充数据到原表格/单元格
|
|
||||||
- **多行数据支持**:每个字段可提取多个值,导出时自动扩展行数
|
|
||||||
- **结构化数据优先**:直接从 Excel rows 提取,无需 LLM
|
|
||||||
- **JSON 容错**:支持 LLM 返回的损坏/截断 JSON
|
|
||||||
- **Markdown 清理**:自动清理 LLM 返回的 markdown 格式
|
|
||||||
|
|
||||||
### 3.2 Word 文档解析(✅ 已完成)
|
|
||||||
|
|
||||||
**已实现功能**:
|
|
||||||
- `docx_parser.py` - Word 文档解析器
|
|
||||||
- 提取段落文本
|
|
||||||
- 提取表格内容(支持比赛表格格式:字段名 | 提示词 | 填写值)
|
|
||||||
- `parse_tables_for_template()` - 解析表格模板,提取字段
|
|
||||||
- `extract_template_fields_from_docx()` - 提取模板字段定义
|
|
||||||
- `_infer_field_type_from_hint()` - 从提示词推断字段类型
|
|
||||||
- **API 端点**:`/api/v1/templates/parse-word-structure` - 上传 Word 文档,提取结构化字段并存入 MongoDB
|
|
||||||
- **API 端点**:`/api/v1/templates/word-fields/{doc_id}` - 获取已存文档的模板字段信息
|
|
||||||
|
|
||||||
### 3.3 Text 文档解析(✅ 已完成)
|
|
||||||
|
|
||||||
**已实现功能**:
|
|
||||||
- `txt_parser.py` - 文本文件解析器
|
|
||||||
- 编码自动检测 (chardet)
|
|
||||||
- 文本清洗(去除控制字符、规范化空白)
|
|
||||||
- 结构化数据提取(邮箱、URL、电话、日期、金额)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、参赛材料准备
|
|
||||||
|
|
||||||
### 4.1 必交材料
|
|
||||||
|
|
||||||
| 材料 | 要求 | 当前状态 | 行动项 |
|
|
||||||
|------|------|----------|--------|
|
|
||||||
| 项目概要介绍 | PPT 格式 | ❌ 待制作 | 制作 PPT |
|
|
||||||
| 项目简介 PPT | - | ❌ 待制作 | 制作 PPT |
|
|
||||||
| 项目详细方案 | 文档 | ⚠️ 部分完成 | 完善文档 |
|
|
||||||
| 项目演示视频 | - | ❌ 待制作 | 录制演示视频 |
|
|
||||||
| 训练素材说明 | 来源说明 | ⚠️ 已有素材 | 整理素材文档 |
|
|
||||||
| 关键模块设计文档 | 概要设计 | ⚠️ 已有部分 | 完善文档 |
|
|
||||||
| 可运行 Demo | 核心代码 | ✅ 已完成 | 打包可运行版本 |
|
|
||||||
|
|
||||||
### 4.2 Demo 提交要求
|
|
||||||
|
|
||||||
根据 Q&A:
|
|
||||||
- 可以只提交核心代码,不需要完整运行环境
|
|
||||||
- 现场答辩可使用自带笔记本电脑
|
|
||||||
- 需要提供部署和运行说明(README)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、测试验证计划
|
|
||||||
|
|
||||||
### 5.1 使用现有测试数据
|
|
||||||
|
|
||||||
```
|
|
||||||
docs/test/
|
|
||||||
├── 2023年文化和旅游发展统计公报.md
|
|
||||||
├── 2024年卫生健康事业发展统计公报.md
|
|
||||||
├── 第三次全国工业普查主要数据公报.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 模板填写测试流程
|
|
||||||
|
|
||||||
1. 准备一个 Word/Excel 模板表格
|
|
||||||
2. 指定源数据文档
|
|
||||||
3. 上传模板和文档
|
|
||||||
4. 执行模板填写
|
|
||||||
5. 检查填写结果准确率
|
|
||||||
6. 记录响应时间
|
|
||||||
|
|
||||||
### 5.3 性能目标
|
|
||||||
|
|
||||||
| 指标 | 目标 | 当前状态 |
|
|
||||||
|------|------|----------|
|
|
||||||
| 信息提取准确率 | ≥80% | 需测试验证 |
|
|
||||||
| 单次响应时间 | ≤90s × 文档数 | 需测试验证 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、工作计划(建议)
|
|
||||||
|
|
||||||
### 第一优先级:端到端测试
|
|
||||||
- 使用真实测试数据进行准确率测试
|
|
||||||
- 验证多行数据导出是否正确
|
|
||||||
- 测试 Word 模板解析是否正常
|
|
||||||
|
|
||||||
### 第二优先级:Demo 打包与文档
|
|
||||||
- 制作项目演示 PPT
|
|
||||||
- 录制演示视频
|
|
||||||
- 完善 README 部署文档
|
|
||||||
|
|
||||||
### 第三优先级:优化
|
|
||||||
- 优化响应时间
|
|
||||||
- 完善错误处理
|
|
||||||
- 增加更多测试用例
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 七、注意事项
|
|
||||||
|
|
||||||
1. **创新点**:根据 Q&A,不必纠结创新点数量限制
|
|
||||||
2. **数据库**:不强制要求数据库存储,可跳过
|
|
||||||
3. **部署**:本地部署即可,不需要公网服务器
|
|
||||||
4. **评测数据**:初赛仅使用目前提供的数据
|
|
||||||
5. **RAG 功能**:当前已临时禁用,不影响核心评测功能(因为使用直接文件读取)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*文档版本: v1.5*
|
|
||||||
*最后更新: 2026-04-09*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 八、技术实现细节
|
|
||||||
|
|
||||||
### 8.1 模板填表流程
|
|
||||||
|
|
||||||
#### 流程图
|
|
||||||
```
|
|
||||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
||||||
│ 上传模板 │ ──► │ 选择数据源 │ ──► │ 智能填表 │
|
|
||||||
└─────────────┘ └─────────────┘ └─────────────┘
|
|
||||||
│
|
|
||||||
┌─────────────────────────┼─────────────────────────┐
|
|
||||||
│ │ │
|
|
||||||
▼ ▼ ▼
|
|
||||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
|
||||||
│ 结构化数据提取 │ │ LLM 提取 │ │ 导出结果 │
|
|
||||||
│ (直接读rows) │ │ (文本理解) │ │ (Excel/Word) │
|
|
||||||
└───────────────┘ └───────────────┘ └───────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 核心组件
|
|
||||||
|
|
||||||
| 组件 | 文件 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| 模板上传 | `templates.py` `/templates/upload` | 接收模板文件,提取字段 |
|
|
||||||
| 字段提取 | `template_fill_service.py` | 从 Word/Excel 表格提取字段定义 |
|
|
||||||
| 文档解析 | `docx_parser.py`, `xlsx_parser.py`, `txt_parser.py` | 解析源文档内容 |
|
|
||||||
| 智能填表 | `template_fill_service.py` `fill_template()` | 结构化提取 + LLM 提取 |
|
|
||||||
| 多行支持 | `template_fill_service.py` `FillResult` | values 数组支持 |
|
|
||||||
| JSON 容错 | `template_fill_service.py` `_fix_json()` | 修复损坏的 JSON |
|
|
||||||
| 结果导出 | `templates.py` `/templates/export` | 多行 Excel + Word 导出 |
|
|
||||||
|
|
||||||
### 8.2 源文档加载方式
|
|
||||||
|
|
||||||
模板填表服务支持两种方式加载源文档:
|
|
||||||
|
|
||||||
1. **通过 MongoDB 文档 ID**:`source_doc_ids`
|
|
||||||
- 文档已上传并存入 MongoDB
|
|
||||||
- 服务直接查询 MongoDB 获取文档内容
|
|
||||||
|
|
||||||
2. **通过文件路径**:`source_file_paths`
|
|
||||||
- 直接读取本地文件
|
|
||||||
- 使用对应的解析器解析内容
|
|
||||||
|
|
||||||
### 8.3 Word 表格模板解析
|
|
||||||
|
|
||||||
比赛评分表格通常是 Word 格式,`docx_parser.py` 提供了专门的解析方法:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 提取表格模板字段
|
|
||||||
from docx_parser import DocxParser
|
|
||||||
parser = DocxParser()
|
|
||||||
fields = parser.extract_template_fields_from_docx(file_path)
|
|
||||||
|
|
||||||
# 返回格式
|
|
||||||
# [
|
|
||||||
# {
|
|
||||||
# "cell": "T0R1", # 表格0,行1
|
|
||||||
# "name": "字段名",
|
|
||||||
# "hint": "提示词",
|
|
||||||
# "field_type": "text/number/date",
|
|
||||||
# "required": True
|
|
||||||
# },
|
|
||||||
# ...
|
|
||||||
# ]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.4 字段类型推断
|
|
||||||
|
|
||||||
系统支持从提示词自动推断字段类型:
|
|
||||||
|
|
||||||
| 关键词 | 推断类型 | 示例 |
|
|
||||||
|--------|----------|------|
|
|
||||||
| 年、月、日、日期、时间、出生 | date | 出生日期 |
|
|
||||||
| 数量、金额、比率、%、率、合计 | number | 增长比率 |
|
|
||||||
| 其他 | text | 姓名、地址 |
|
|
||||||
|
|
||||||
### 8.5 API 接口
|
|
||||||
|
|
||||||
#### POST `/api/v1/templates/upload`
|
|
||||||
|
|
||||||
上传模板文件,提取字段定义。
|
|
||||||
|
|
||||||
**响应**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"template_id": "/path/to/saved/template.docx",
|
|
||||||
"filename": "模板.docx",
|
|
||||||
"file_type": "docx",
|
|
||||||
"fields": [
|
|
||||||
{"cell": "A1", "name": "姓名", "field_type": "text", "required": true, "hint": "提取人员姓名"}
|
|
||||||
],
|
|
||||||
"field_count": 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### POST `/api/v1/templates/fill`
|
|
||||||
|
|
||||||
填写请求:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"template_id": "模板ID",
|
|
||||||
"template_fields": [
|
|
||||||
{"cell": "A1", "name": "姓名", "field_type": "text", "required": true, "hint": "提取人员姓名"}
|
|
||||||
],
|
|
||||||
"source_doc_ids": ["mongodb_doc_id_1", "mongodb_doc_id_2"],
|
|
||||||
"source_file_paths": [],
|
|
||||||
"user_hint": "请从xxx文档中提取"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应(含多行支持)**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"filled_data": {
|
|
||||||
"姓名": ["张三", "李四", "王五"],
|
|
||||||
"年龄": ["25", "30", "28"]
|
|
||||||
},
|
|
||||||
"fill_details": [
|
|
||||||
{
|
|
||||||
"field": "姓名",
|
|
||||||
"cell": "A1",
|
|
||||||
"values": ["张三", "李四", "王五"],
|
|
||||||
"value": "张三",
|
|
||||||
"source": "结构化数据直接提取",
|
|
||||||
"confidence": 1.0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source_doc_count": 2,
|
|
||||||
"max_rows": 3
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### POST `/api/v1/templates/export`
|
|
||||||
|
|
||||||
导出请求(创建新文件):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"template_id": "模板ID",
|
|
||||||
"filled_data": {"姓名": ["张三", "李四"], "金额": ["10000", "20000"]},
|
|
||||||
"format": "xlsx"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### POST `/api/v1/templates/fill-and-export`
|
|
||||||
|
|
||||||
**填充原始模板并导出**(推荐用于比赛)
|
|
||||||
|
|
||||||
直接打开原始模板文件,将数据填入模板的表格/单元格中,然后导出。**保持原始模板格式不变**。
|
|
||||||
|
|
||||||
**请求**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"template_path": "/path/to/original/template.docx",
|
|
||||||
"filled_data": {
|
|
||||||
"姓名": ["张三", "李四", "王五"],
|
|
||||||
"年龄": ["25", "30", "28"]
|
|
||||||
},
|
|
||||||
"format": "docx"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**:填充后的 Word/Excel 文件(文件流)
|
|
||||||
|
|
||||||
**特点**:
|
|
||||||
- 打开原始模板文件
|
|
||||||
- 根据表头行匹配字段名到列索引
|
|
||||||
- 将数据填入对应列的单元格
|
|
||||||
- 多行数据自动扩展表格行数
|
|
||||||
- 保持原始模板格式和样式
|
|
||||||
|
|
||||||
#### POST `/api/v1/templates/parse-word-structure`
|
|
||||||
|
|
||||||
**上传 Word 文档并提取结构化字段**(比赛专用)
|
|
||||||
|
|
||||||
上传 Word 文档,从表格模板中提取字段定义(字段名、提示词、字段类型)并存入 MongoDB。
|
|
||||||
|
|
||||||
**请求**:multipart/form-data
|
|
||||||
- file: Word 文件
|
|
||||||
|
|
||||||
**响应**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"doc_id": "mongodb_doc_id",
|
|
||||||
"filename": "模板.docx",
|
|
||||||
"file_path": "/path/to/saved/template.docx",
|
|
||||||
"field_count": 5,
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"cell": "T0R1",
|
|
||||||
"name": "字段名",
|
|
||||||
"hint": "提示词",
|
|
||||||
"field_type": "text",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tables": [...],
|
|
||||||
"metadata": {
|
|
||||||
"paragraph_count": 10,
|
|
||||||
"table_count": 1,
|
|
||||||
"word_count": 500,
|
|
||||||
"has_tables": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### GET `/api/v1/templates/word-fields/{doc_id}`
|
|
||||||
|
|
||||||
**获取 Word 文档模板字段信息**
|
|
||||||
|
|
||||||
根据 doc_id 获取已上传的 Word 文档的模板字段信息。
|
|
||||||
|
|
||||||
**响应**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"doc_id": "mongodb_doc_id",
|
|
||||||
"filename": "模板.docx",
|
|
||||||
"fields": [...],
|
|
||||||
"tables": [...],
|
|
||||||
"field_count": 5,
|
|
||||||
"metadata": {...}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.6 多行数据处理
|
|
||||||
|
|
||||||
**FillResult 数据结构**:
|
|
||||||
```python
|
|
||||||
@dataclass
|
|
||||||
class FillResult:
|
|
||||||
field: str
|
|
||||||
values: List[Any] = None # 支持多个值(数组)
|
|
||||||
value: Any = "" # 保留兼容(第一个值)
|
|
||||||
source: str = "" # 来源文档
|
|
||||||
confidence: float = 1.0 # 置信度
|
|
||||||
```
|
|
||||||
|
|
||||||
**导出逻辑**:
|
|
||||||
- 计算所有字段的最大行数
|
|
||||||
- 遍历每一行,取对应索引的值
|
|
||||||
- 不足的行填空字符串
|
|
||||||
|
|
||||||
### 8.7 JSON 容错处理
|
|
||||||
|
|
||||||
当 LLM 返回的 JSON 损坏或被截断时,系统会:
|
|
||||||
|
|
||||||
1. 清理 markdown 代码块标记(```json, ```)
|
|
||||||
2. 尝试配对括号找到完整的 JSON
|
|
||||||
3. 移除末尾多余的逗号
|
|
||||||
4. 使用正则表达式提取 values 数组
|
|
||||||
5. 备选方案:直接提取所有引号内的字符串
|
|
||||||
|
|
||||||
### 8.8 结构化数据优先提取
|
|
||||||
|
|
||||||
对于 Excel 等有 `rows` 结构的文档,系统会:
|
|
||||||
|
|
||||||
1. 直接从 `structured_data.rows` 中查找匹配列
|
|
||||||
2. 使用模糊匹配(字段名包含或被包含)
|
|
||||||
3. 提取该列的所有行值
|
|
||||||
4. 无需调用 LLM,速度更快,准确率更高
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 内部逻辑
|
|
||||||
if structured.get("rows"):
|
|
||||||
columns = structured.get("columns", [])
|
|
||||||
values = _extract_column_values(rows, columns, field_name)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 九、依赖说明
|
|
||||||
|
|
||||||
### Python 依赖
|
|
||||||
|
|
||||||
```
|
|
||||||
# requirements.txt 中需要包含
|
|
||||||
fastapi>=0.104.0
|
|
||||||
uvicorn>=0.24.0
|
|
||||||
motor>=3.3.0 # MongoDB 异步驱动
|
|
||||||
sqlalchemy>=2.0.0 # MySQL ORM
|
|
||||||
pandas>=2.0.0 # Excel 处理
|
|
||||||
openpyxl>=3.1.0 # Excel 写入
|
|
||||||
python-docx>=0.8.0 # Word 处理
|
|
||||||
chardet>=4.0.0 # 编码检测
|
|
||||||
httpx>=0.25.0 # HTTP 客户端
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前端依赖
|
|
||||||
|
|
||||||
```
|
|
||||||
# package.json 中需要包含
|
|
||||||
react>=18.0.0
|
|
||||||
react-dropzone>=14.0.0
|
|
||||||
lucide-react>=0.300.0
|
|
||||||
sonner>=1.0.0 # toast 通知
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 十、启动说明
|
|
||||||
|
|
||||||
### 后端启动
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
.\venv\Scripts\Activate.ps1 # 或 Activate.bat
|
|
||||||
pip install -r requirements.txt # 确保依赖完整
|
|
||||||
.\venv\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前端启动
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境变量
|
|
||||||
|
|
||||||
在 `backend/.env` 中配置:
|
|
||||||
```
|
|
||||||
MONGODB_URL=mongodb://localhost:27017
|
|
||||||
MONGODB_DB_NAME=document_system
|
|
||||||
MYSQL_HOST=localhost
|
|
||||||
MYSQL_PORT=3306
|
|
||||||
MYSQL_USER=root
|
|
||||||
MYSQL_PASSWORD=your_password
|
|
||||||
MYSQL_DATABASE=document_system
|
|
||||||
LLM_API_KEY=your_api_key
|
|
||||||
LLM_BASE_URL=https://api.minimax.chat
|
|
||||||
LLM_MODEL_NAME=MiniMax-Text-01
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user