refactor(backend): update and add service layers
- services/ontology.py: update ontology service - services/orchestrator.py: update orchestrator service - services/user_agent.py: update user agent service - services/settings.py: update settings service - services/expense_claims.py: update expense claims service - services/agent_conversations.py: add new agent conversations service
This commit is contained in:
@@ -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.agent_conversations import AgentConversationService
|
||||
from app.services.expense_claims import ExpenseClaimService
|
||||
from app.services.agent_foundation import AgentFoundationService
|
||||
from app.services.agent_runs import AgentRunService
|
||||
@@ -62,6 +63,7 @@ class OrchestratorService:
|
||||
def __init__(self, db: Session) -> None:
|
||||
self.db = db
|
||||
self.asset_service = AgentAssetService(db)
|
||||
self.conversation_service = AgentConversationService(db)
|
||||
self.expense_claim_service = ExpenseClaimService(db)
|
||||
self.run_service = AgentRunService(db)
|
||||
self.ontology_service = SemanticOntologyService(db)
|
||||
@@ -69,10 +71,28 @@ class OrchestratorService:
|
||||
|
||||
def run(self, payload: OrchestratorRequest) -> OrchestratorResponse:
|
||||
AgentFoundationService(self.db).ensure_foundation_ready()
|
||||
context_json = dict(payload.context_json or {})
|
||||
conversation_id = str(payload.conversation_id or "").strip() or None
|
||||
conversation = None
|
||||
if payload.source == AgentRunSource.USER_MESSAGE.value:
|
||||
conversation = self.conversation_service.get_or_create_conversation(
|
||||
conversation_id=conversation_id,
|
||||
user_id=payload.user_id,
|
||||
source=payload.source,
|
||||
context_json=context_json,
|
||||
)
|
||||
conversation_id = conversation.conversation_id
|
||||
context_json = self.conversation_service.hydrate_context_json(
|
||||
conversation=conversation,
|
||||
context_json=context_json,
|
||||
)
|
||||
|
||||
route_json: dict[str, Any] = {
|
||||
"orchestrated_by": AgentName.ORCHESTRATOR.value,
|
||||
"stage": "created",
|
||||
}
|
||||
if conversation_id:
|
||||
route_json["conversation_id"] = conversation_id
|
||||
run = self.run_service.create_run(
|
||||
agent=AgentName.ORCHESTRATOR.value,
|
||||
source=payload.source,
|
||||
@@ -87,15 +107,27 @@ class OrchestratorService:
|
||||
|
||||
try:
|
||||
message, task_asset = self._resolve_message(payload)
|
||||
if conversation is not None:
|
||||
self.conversation_service.append_message(
|
||||
conversation_id=conversation.conversation_id,
|
||||
role="user",
|
||||
content=message,
|
||||
run_id=run.run_id,
|
||||
message_json={
|
||||
"attachment_names": context_json.get("attachment_names", []),
|
||||
"attachment_count": context_json.get("attachment_count", 0),
|
||||
"ocr_summary": context_json.get("ocr_summary", ""),
|
||||
},
|
||||
)
|
||||
ontology = self.ontology_service.parse_for_run(
|
||||
OntologyParseRequest(
|
||||
query=message,
|
||||
user_id=payload.user_id,
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
),
|
||||
run_id=run.run_id,
|
||||
)
|
||||
if payload.context_json.get("simulate_orchestrator_exception"):
|
||||
if context_json.get("simulate_orchestrator_exception"):
|
||||
raise RuntimeError("simulated orchestrator exception")
|
||||
selected_agent, route_reason = self._select_agent(payload, ontology)
|
||||
capabilities = self._select_capabilities(
|
||||
@@ -159,6 +191,7 @@ class OrchestratorService:
|
||||
capabilities=capabilities,
|
||||
requires_confirmation=requires_confirmation,
|
||||
task_asset=task_asset,
|
||||
context_json=context_json,
|
||||
)
|
||||
else:
|
||||
outcome = self._execute_user_agent(
|
||||
@@ -167,6 +200,7 @@ class OrchestratorService:
|
||||
ontology=ontology,
|
||||
capabilities=capabilities,
|
||||
requires_confirmation=requires_confirmation,
|
||||
context_json=context_json,
|
||||
)
|
||||
|
||||
final_status = (
|
||||
@@ -176,10 +210,19 @@ class OrchestratorService:
|
||||
and ontology.permission.level == AgentPermissionLevel.APPROVAL_REQUIRED.value
|
||||
else outcome.status
|
||||
)
|
||||
response_status = self._normalize_response_status(final_status)
|
||||
result_message = (
|
||||
str(outcome.result.get("message", "")).strip()
|
||||
or "Orchestrator 执行完成。"
|
||||
)
|
||||
trace_summary = OrchestratorTraceSummary(
|
||||
scenario=ontology.scenario,
|
||||
intent=ontology.intent,
|
||||
tool_count=outcome.tool_count,
|
||||
failed_tool_count=outcome.failed_tool_count,
|
||||
selected_capability_codes=selected_capability_codes,
|
||||
degraded=outcome.degraded,
|
||||
)
|
||||
self.run_service.update_run(
|
||||
run.run_id,
|
||||
agent=selected_agent or AgentName.ORCHESTRATOR.value,
|
||||
@@ -195,22 +238,51 @@ class OrchestratorService:
|
||||
error_message=None,
|
||||
finished_at=datetime.now(UTC),
|
||||
)
|
||||
if conversation is not None and conversation_id:
|
||||
draft_payload = outcome.result.get("draft_payload")
|
||||
self.conversation_service.update_state(
|
||||
conversation_id=conversation_id,
|
||||
run_id=run.run_id,
|
||||
scenario=ontology.scenario,
|
||||
intent=ontology.intent,
|
||||
context_json=context_json,
|
||||
draft_payload=draft_payload if isinstance(draft_payload, dict) else None,
|
||||
)
|
||||
self.conversation_service.append_message(
|
||||
conversation_id=conversation_id,
|
||||
role="assistant",
|
||||
content=result_message,
|
||||
run_id=run.run_id,
|
||||
message_json={
|
||||
"status": final_status,
|
||||
"scenario": ontology.scenario,
|
||||
"intent": ontology.intent,
|
||||
"attachment_names": context_json.get("attachment_names", []),
|
||||
"attachment_count": context_json.get("attachment_count", 0),
|
||||
"draft_payload": draft_payload if isinstance(draft_payload, dict) else None,
|
||||
"orchestrator_payload": {
|
||||
"run_id": run.run_id,
|
||||
"conversation_id": conversation_id,
|
||||
"selected_agent": selected_agent,
|
||||
"route_reason": route_reason,
|
||||
"permission_level": ontology.permission.level,
|
||||
"status": response_status,
|
||||
"requires_confirmation": requires_confirmation,
|
||||
"trace_summary": trace_summary.model_dump(),
|
||||
"result": outcome.result,
|
||||
},
|
||||
},
|
||||
)
|
||||
return OrchestratorResponse(
|
||||
run_id=run.run_id,
|
||||
conversation_id=conversation_id,
|
||||
selected_agent=selected_agent,
|
||||
route_reason=route_reason,
|
||||
permission_level=ontology.permission.level,
|
||||
status=self._normalize_response_status(final_status),
|
||||
status=response_status,
|
||||
result=outcome.result,
|
||||
requires_confirmation=requires_confirmation,
|
||||
trace_summary=OrchestratorTraceSummary(
|
||||
scenario=ontology.scenario,
|
||||
intent=ontology.intent,
|
||||
tool_count=outcome.tool_count,
|
||||
failed_tool_count=outcome.failed_tool_count,
|
||||
selected_capability_codes=selected_capability_codes,
|
||||
degraded=outcome.degraded,
|
||||
),
|
||||
trace_summary=trace_summary,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception("Orchestrator run failed run_id=%s", run.run_id)
|
||||
@@ -223,8 +295,25 @@ class OrchestratorService:
|
||||
error_message=str(exc),
|
||||
finished_at=datetime.now(UTC),
|
||||
)
|
||||
if conversation is not None and conversation_id:
|
||||
self.conversation_service.update_state(
|
||||
conversation_id=conversation_id,
|
||||
run_id=run.run_id,
|
||||
scenario=None,
|
||||
intent=None,
|
||||
context_json=context_json,
|
||||
draft_payload=None,
|
||||
)
|
||||
self.conversation_service.append_message(
|
||||
conversation_id=conversation_id,
|
||||
role="assistant",
|
||||
content=f"Orchestrator 执行失败:{exc}",
|
||||
run_id=run.run_id,
|
||||
message_json={"status": AgentRunStatus.FAILED.value},
|
||||
)
|
||||
return OrchestratorResponse(
|
||||
run_id=run.run_id,
|
||||
conversation_id=conversation_id,
|
||||
selected_agent=None,
|
||||
route_reason="orchestrator_exception",
|
||||
permission_level=AgentPermissionLevel.READ.value,
|
||||
@@ -336,6 +425,7 @@ class OrchestratorService:
|
||||
ontology: OntologyParseResult,
|
||||
capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
|
||||
requires_confirmation: bool,
|
||||
context_json: dict[str, Any],
|
||||
) -> ExecutionOutcome:
|
||||
selected_capability_codes = self._flatten_capability_codes(capabilities)
|
||||
if requires_confirmation:
|
||||
@@ -347,7 +437,7 @@ class OrchestratorService:
|
||||
"message": payload.message,
|
||||
"permission_level": ontology.permission.level,
|
||||
},
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
executor=lambda: {
|
||||
"confirmation_title": "操作需要确认",
|
||||
"message": f"{ontology.permission.reason} 当前仅返回确认摘要,不直接执行动作。",
|
||||
@@ -372,7 +462,7 @@ class OrchestratorService:
|
||||
tool_type=AgentToolType.DATABASE.value,
|
||||
tool_name=self._database_tool_name(ontology.scenario),
|
||||
request_json=self._build_ontology_json(ontology),
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
executor=lambda: self._build_database_answer(ontology),
|
||||
fallback_factory=lambda exc: {
|
||||
"message": f"数据库查询暂时不可用,已返回降级说明:{exc}",
|
||||
@@ -386,7 +476,7 @@ class OrchestratorService:
|
||||
user_id=payload.user_id,
|
||||
message=payload.message or "",
|
||||
ontology=ontology,
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
tool_payload=tool_payload,
|
||||
selected_capability_codes=selected_capability_codes,
|
||||
degraded=degraded,
|
||||
@@ -409,7 +499,7 @@ class OrchestratorService:
|
||||
tool_type=AgentToolType.DATABASE.value,
|
||||
tool_name="knowledge.search",
|
||||
request_json=self._build_ontology_json(ontology),
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
executor=lambda: self._build_knowledge_answer(ontology, capabilities),
|
||||
fallback_factory=lambda exc: {
|
||||
"message": f"知识检索暂时不可用,建议稍后重试:{exc}",
|
||||
@@ -423,7 +513,7 @@ class OrchestratorService:
|
||||
user_id=payload.user_id,
|
||||
message=payload.message or "",
|
||||
ontology=ontology,
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
tool_payload=tool_payload,
|
||||
selected_capability_codes=selected_capability_codes,
|
||||
degraded=degraded,
|
||||
@@ -446,7 +536,7 @@ class OrchestratorService:
|
||||
tool_type=AgentToolType.RULE_ENGINE.value,
|
||||
tool_name=self._rule_tool_name(capabilities),
|
||||
request_json=self._build_ontology_json(ontology),
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
executor=lambda: self._build_rule_answer(ontology),
|
||||
fallback_factory=lambda exc: {
|
||||
"message": f"规则检查暂时不可用,已返回人工复核建议:{exc}",
|
||||
@@ -460,7 +550,7 @@ class OrchestratorService:
|
||||
user_id=payload.user_id,
|
||||
message=payload.message or "",
|
||||
ontology=ontology,
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
tool_payload=tool_payload,
|
||||
selected_capability_codes=selected_capability_codes,
|
||||
degraded=degraded,
|
||||
@@ -499,7 +589,7 @@ class OrchestratorService:
|
||||
user_id=payload.user_id,
|
||||
message=payload.message or "",
|
||||
ontology=ontology,
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
)
|
||||
fallback_factory = lambda exc: {
|
||||
"message": f"报销草稿落库失败,请稍后再试:{exc}",
|
||||
@@ -511,7 +601,7 @@ class OrchestratorService:
|
||||
tool_type=tool_type,
|
||||
tool_name=tool_name,
|
||||
request_json=self._build_ontology_json(ontology),
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
executor=executor,
|
||||
fallback_factory=fallback_factory,
|
||||
)
|
||||
@@ -522,7 +612,7 @@ class OrchestratorService:
|
||||
user_id=payload.user_id,
|
||||
message=payload.message or "",
|
||||
ontology=ontology,
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
tool_payload=tool_payload,
|
||||
selected_capability_codes=selected_capability_codes,
|
||||
degraded=degraded,
|
||||
@@ -548,6 +638,7 @@ class OrchestratorService:
|
||||
capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
|
||||
requires_confirmation: bool,
|
||||
task_asset: AgentAssetRead | None,
|
||||
context_json: dict[str, Any],
|
||||
) -> ExecutionOutcome:
|
||||
if requires_confirmation:
|
||||
return ExecutionOutcome(
|
||||
@@ -566,7 +657,7 @@ class OrchestratorService:
|
||||
tool_type=AgentToolType.RULE_ENGINE.value,
|
||||
tool_name=self._rule_tool_name(capabilities),
|
||||
request_json=self._build_ontology_json(ontology),
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
executor=lambda: self._build_rule_answer(ontology),
|
||||
fallback_factory=lambda exc: {
|
||||
"message": f"规则巡检失败,已降级为待人工复核:{exc}",
|
||||
@@ -581,7 +672,7 @@ class OrchestratorService:
|
||||
"task_code": task_asset.code if task_asset is not None else "",
|
||||
"scenario": ontology.scenario,
|
||||
},
|
||||
context_json=payload.context_json,
|
||||
context_json=context_json,
|
||||
executor=lambda: self._build_mcp_answer(task_asset, ontology),
|
||||
fallback_factory=lambda exc: {
|
||||
"message": f"MCP 调用失败,已使用缓存快照降级:{exc}",
|
||||
@@ -806,6 +897,8 @@ class OrchestratorService:
|
||||
}
|
||||
if response.draft_payload is not None:
|
||||
result["draft_payload"] = response.draft_payload.model_dump()
|
||||
if response.review_payload is not None:
|
||||
result["review_payload"] = response.review_payload.model_dump()
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user