refactor(backend): update services and register OCR router

- router.py: register ocr_router with OCR tag
- ontology.py: update ontology service logic
- orchestrator.py: update orchestrator service logic
- user_agent.py: update user agent schema and service
- schemas/user_agent.py: update user agent data schemas
This commit is contained in:
caoxiaozhu
2026-05-12 03:03:15 +00:00
parent 33826929ba
commit ca29025063
5 changed files with 56 additions and 14 deletions

View File

@@ -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"])

View File

@@ -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):

View File

@@ -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。"

View File

@@ -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(

View File

@@ -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(