diff --git a/server/src/app/api/v1/router.py b/server/src/app/api/v1/router.py index a7ccbc0..ac359e1 100644 --- a/server/src/app/api/v1/router.py +++ b/server/src/app/api/v1/router.py @@ -8,6 +8,7 @@ from app.api.v1.endpoints.bootstrap import router as bootstrap_router from app.api.v1.endpoints.employees import router as employees_router from app.api.v1.endpoints.health import router as health_router from app.api.v1.endpoints.knowledge import router as knowledge_router +from app.api.v1.endpoints.ocr import router as ocr_router from app.api.v1.endpoints.ontology import router as ontology_router from app.api.v1.endpoints.orchestrator import router as orchestrator_router from app.api.v1.endpoints.reimbursements import router as reimbursements_router @@ -21,6 +22,7 @@ router.include_router(agent_assets_router, tags=["agent-assets"]) router.include_router(agent_runs_router, tags=["agent-runs"]) router.include_router(audit_logs_router, tags=["audit-logs"]) router.include_router(knowledge_router, tags=["knowledge"]) +router.include_router(ocr_router, tags=["ocr"]) router.include_router(ontology_router, tags=["ontology"]) router.include_router(orchestrator_router, tags=["orchestrator"]) router.include_router(employees_router, prefix="/employees", tags=["employees"]) diff --git a/server/src/app/schemas/user_agent.py b/server/src/app/schemas/user_agent.py index 7db7291..a0d17ad 100644 --- a/server/src/app/schemas/user_agent.py +++ b/server/src/app/schemas/user_agent.py @@ -29,6 +29,9 @@ class UserAgentDraftPayload(BaseModel): title: str = Field(description="草稿标题。") body: str = Field(description="草稿正文。") confirmation_required: bool = Field(default=True, description="是否需要人工确认。") + claim_id: str | None = Field(default=None, description="关联的报销草稿 ID。") + claim_no: str | None = Field(default=None, description="关联的报销草稿单号。") + status: str | None = Field(default=None, description="当前报销草稿状态。") class UserAgentRequest(BaseModel): diff --git a/server/src/app/services/ontology.py b/server/src/app/services/ontology.py index bd50486..86e412e 100644 --- a/server/src/app/services/ontology.py +++ b/server/src/app/services/ontology.py @@ -666,6 +666,8 @@ class SemanticOntologyService: "entry_source": payload.context_json.get("entry_source"), "attachment_names": payload.context_json.get("attachment_names", []), "attachment_count": payload.context_json.get("attachment_count", 0), + "ocr_summary": payload.context_json.get("ocr_summary", ""), + "ocr_documents": payload.context_json.get("ocr_documents", []), "request_context": payload.context_json.get("request_context"), "role_codes": payload.context_json.get("role_codes", []), }, @@ -690,6 +692,7 @@ class SemanticOntologyService: "即使没有明确说“生成草稿”,也优先使用 expense + draft。" "出现“客户”不等于应收,出现“供应商”不等于应付,必须结合动作词和业务目标判断。" "只有明确查询、统计、列出、多少、明细、对比时才优先使用 query 或 compare。" + "附件名称和 OCR 摘要只作为辅助证据,不能编造未出现的事实。" "信息不足时 clarification_required=true,并给出一句简短中文追问。" "missing_slots 使用简短 snake_case,例如 expense_type, amount, " "customer_name, participants, attachments。" diff --git a/server/src/app/services/orchestrator.py b/server/src/app/services/orchestrator.py index 31c1f3a..f1c7f67 100644 --- a/server/src/app/services/orchestrator.py +++ b/server/src/app/services/orchestrator.py @@ -32,6 +32,7 @@ from app.schemas.orchestrator import ( ) from app.schemas.user_agent import UserAgentRequest, UserAgentResponse from app.services.agent_assets import AgentAssetService +from app.services.expense_claims import ExpenseClaimService from app.services.agent_foundation import AgentFoundationService from app.services.agent_runs import AgentRunService from app.services.ontology import SemanticOntologyService @@ -61,6 +62,7 @@ class OrchestratorService: def __init__(self, db: Session) -> None: self.db = db self.asset_service = AgentAssetService(db) + self.expense_claim_service = ExpenseClaimService(db) self.run_service = AgentRunService(db) self.ontology_service = SemanticOntologyService(db) self.user_agent_service = UserAgentService(db) @@ -475,23 +477,43 @@ class OrchestratorService: failed_tool_count=1 if degraded else 0, ) + tool_type = AgentToolType.LLM.value + tool_name = "user_agent.draft_placeholder" + executor = lambda: { + "message": ( + f"已生成 {ontology.scenario} 场景草稿," + "占位能力后续由 Day 5 User Agent 接管。" + ), + "draft_only": True, + } + fallback_factory = lambda exc: { + "message": f"草稿生成暂时不可用,请稍后再试:{exc}", + "degraded": True, + } + + if ontology.scenario == "expense": + tool_type = AgentToolType.DATABASE.value + tool_name = "database.expense_claims.upsert_draft" + executor = lambda: self.expense_claim_service.upsert_draft_from_ontology( + run_id=run_id, + user_id=payload.user_id, + message=payload.message or "", + ontology=ontology, + context_json=payload.context_json, + ) + fallback_factory = lambda exc: { + "message": f"报销草稿落库失败,请稍后再试:{exc}", + "degraded": True, + } + tool_payload, degraded = self._invoke_tool( run_id=run_id, - tool_type=AgentToolType.LLM.value, - tool_name="user_agent.draft_placeholder", + tool_type=tool_type, + tool_name=tool_name, request_json=self._build_ontology_json(ontology), context_json=payload.context_json, - executor=lambda: { - "message": ( - f"已生成 {ontology.scenario} 场景草稿," - "占位能力后续由 Day 5 User Agent 接管。" - ), - "draft_only": True, - }, - fallback_factory=lambda exc: { - "message": f"草稿生成暂时不可用,请稍后再试:{exc}", - "degraded": True, - }, + executor=executor, + fallback_factory=fallback_factory, ) result = self._build_user_agent_result( self.user_agent_service.respond( diff --git a/server/src/app/services/user_agent.py b/server/src/app/services/user_agent.py index 624345e..d06f7e2 100644 --- a/server/src/app/services/user_agent.py +++ b/server/src/app/services/user_agent.py @@ -142,8 +142,11 @@ class UserAgentService: return self._build_implicit_expense_draft_guidance(payload) attachment_names = self._resolve_attachment_names(payload) + ocr_summary = str(payload.context_json.get("ocr_summary") or "").strip() attachment_hint = "" - if attachment_names: + if ocr_summary: + attachment_hint = f" 我已读取附件 OCR 摘要:{ocr_summary}" + elif attachment_names: attachment_hint = ( f" 我已带入 {len(attachment_names)} 份附件名称,但目前还不能直接读取附件内容," "仍需要你补充关键信息。" @@ -238,6 +241,8 @@ class UserAgentService: "request_context": payload.context_json.get("request_context"), "attachment_count": payload.context_json.get("attachment_count"), "attachment_names": self._resolve_attachment_names(payload), + "ocr_summary": payload.context_json.get("ocr_summary", ""), + "ocr_documents": payload.context_json.get("ocr_documents", []), }, "tool_payload": payload.tool_payload, "citations": [item.model_dump(mode="json") for item in citations], @@ -348,7 +353,11 @@ class UserAgentService: def _build_draft_payload(self, payload: UserAgentRequest) -> UserAgentDraftPayload: scenario_label = SCENARIO_LABELS.get(payload.ontology.scenario, "业务") subject = self._resolve_subject(payload) + claim_no = str(payload.tool_payload.get("claim_no") or "").strip() or None + claim_status = str(payload.tool_payload.get("status") or "").strip() or None title = f"{scenario_label}处理意见草稿" + if claim_no: + title = f"{scenario_label}草稿 {claim_no}" body = ( f"主题:{subject}\n" "结论:已根据当前语义解析结果生成草稿,尚未自动执行。\n" @@ -360,6 +369,9 @@ class UserAgentService: title=title, body=body, confirmation_required=True, + claim_id=str(payload.tool_payload.get("claim_id") or "").strip() or None, + claim_no=claim_no, + status=claim_status, ) def _build_suggested_actions(