refactor(server): steward 意图改用声明式注册表编排

- 新增 steward_intent_registry,IntentDescriptor 统一描述意图的识别关键词、动作步骤构建、字段白名单与副作用集合,替代分散的 if/else
- 新增 steward_intent_bootstrap 注册 expense_application 等意图;新增 steward_query_executors 提供差旅标准查询的无副作用执行与城市/席别标签化输出
- action_contracts/action_executor/graph_planner/intent_agent/model_plan_builder/planner_extraction/fallback 适配注册表,识别与执行分发自动从注册表取数
- 新增 intent_registry/query_executors 测试,更新 intent_agent 测试
This commit is contained in:
caoxiaozhu
2026-06-25 11:50:02 +08:00
parent d321005044
commit eaada4bc57
15 changed files with 1023 additions and 54 deletions

View File

@@ -15,6 +15,11 @@ from app.schemas.steward import (
from app.schemas.user_agent import UserAgentRequest
from app.services.attachment_association_jobs import AttachmentAssociationJobRunner
from app.services.expense_claims import ExpenseClaimService
from app.services.steward_intent_registry import (
all_noop_actions,
all_side_effect_actions,
resolve_intent_by_action,
)
from app.services.user_agent import UserAgentService
from app.services.user_agent_application_dates import resolve_application_days_from_time_range
@@ -31,8 +36,8 @@ SUPPORTED_ACTIONS = {
"link_existing_application",
"associate_attachments",
}
APPLICATION_SIDE_EFFECT_ACTIONS = {"save_application_draft", "submit_application"}
REIMBURSEMENT_SIDE_EFFECT_ACTIONS = {"create_reimbursement_draft", "link_existing_application"}
APPLICATION_SIDE_EFFECT_ACTIONS = {"save_application_draft", "submit_application", "run_duplicate_precheck"}
REIMBURSEMENT_SIDE_EFFECT_ACTIONS = {"create_reimbursement_draft", "link_existing_application", "associate_attachments"}
NOOP_ACTIONS = {
"fill_application_fields",
"build_application_preview",
@@ -83,7 +88,8 @@ class StewardActionExecutor:
) -> StewardActionExecuteResponse:
action_type = self._normalize_action_type(request.action_type)
trace = [self._trace("received", action_type=action_type, plan_id=request.plan_id)]
if action_type not in SUPPORTED_ACTIONS:
supported = SUPPORTED_ACTIONS | all_side_effect_actions() | all_noop_actions()
if action_type not in supported:
return self._blocked(
action_type,
f"不支持的小财管家动作:{action_type or '空动作'}",
@@ -91,7 +97,8 @@ class StewardActionExecutor:
)
task = request.task
if task is None and action_type not in NOOP_ACTIONS:
noop_actions = NOOP_ACTIONS | all_noop_actions()
if task is None and action_type not in noop_actions:
return self._blocked(
action_type,
"动作缺少任务快照,无法安全执行。",
@@ -107,7 +114,7 @@ class StewardActionExecutor:
trace=[*trace, self._trace("blocked", reason="missing_fields")],
)
if action_type in NOOP_ACTIONS:
if action_type in noop_actions:
return StewardActionExecuteResponse(
action_type=action_type,
status="succeeded",
@@ -118,6 +125,13 @@ class StewardActionExecutor:
},
trace=[*trace, self._trace("completed", mode="noop")],
)
# 优先走注册表:查到 action 所属意图的 executor 即委托执行
intent = resolve_intent_by_action(action_type)
if intent is not None and intent.executor is not None:
return intent.executor(self, request, current_user, trace)
# 兼容回退:注册表未命中时按旧逻辑分发
if action_type == "run_duplicate_precheck":
return self._run_duplicate_precheck(request, current_user, trace)
if action_type in APPLICATION_SIDE_EFFECT_ACTIONS:
@@ -133,6 +147,30 @@ class StewardActionExecutor:
trace=[*trace, self._trace("blocked", reason="unwired_action")],
)
def _dispatch_application_action(
self,
request: StewardActionExecuteRequest,
current_user: CurrentUserContext,
trace: list[dict[str, Any]],
) -> StewardActionExecuteResponse:
"""registry 入口:分发申请类副作用动作。"""
action_type = self._normalize_action_type(request.action_type)
if action_type == "run_duplicate_precheck":
return self._run_duplicate_precheck(request, current_user, trace)
return self._execute_application_action(request, current_user, action_type, trace)
def _dispatch_reimbursement_action(
self,
request: StewardActionExecuteRequest,
current_user: CurrentUserContext,
trace: list[dict[str, Any]],
) -> StewardActionExecuteResponse:
"""registry 入口:分发报销类副作用动作。"""
action_type = self._normalize_action_type(request.action_type)
if action_type == "associate_attachments":
return self._execute_associate_attachments_action(request, current_user, trace)
return self._execute_reimbursement_action(request, current_user, action_type, trace)
def _run_duplicate_precheck(
self,
request: StewardActionExecuteRequest,