|
|
@@ -1,4 +1,8 @@
|
|
|
-"""智保通第一阶段 Agent 可调用的确定性业务工具。"""
|
|
|
+"""智保通第一阶段 Agent 可调用的确定性业务工具。
|
|
|
+
|
|
|
+工具是模型与真实业务之间的唯一通道。模型负责决定“何时需要什么信息”,
|
|
|
+工具负责参数校验、身份检查、调用领域服务并返回可信的结构化结果。
|
|
|
+"""
|
|
|
|
|
|
from typing import Any, Literal, Protocol
|
|
|
|
|
|
@@ -21,12 +25,16 @@ from zbt.harness.tooling import ToolDefinition, ToolExecutionContext, ToolRegist
|
|
|
|
|
|
|
|
|
class AttributionMetricsProvider(Protocol):
|
|
|
+ """推广指标查询协议,便于测试时替换实现。"""
|
|
|
+
|
|
|
def performance(self, admin_user_id: str | None = None) -> dict[str, Any]: ...
|
|
|
|
|
|
def order_ids(self, admin_user_id: str | None = None) -> set[str]: ...
|
|
|
|
|
|
|
|
|
class EmptyAttributionMetrics:
|
|
|
+ """未装配推广模块时使用的空实现,保证工具仍可稳定返回。"""
|
|
|
+
|
|
|
def performance(self, admin_user_id: str | None = None) -> dict[str, Any]:
|
|
|
del admin_user_id
|
|
|
return {
|
|
|
@@ -44,6 +52,8 @@ class EmptyAttributionMetrics:
|
|
|
|
|
|
|
|
|
class ToolArguments(BaseModel):
|
|
|
+ """所有工具参数的基类;拒绝模型偷偷增加未声明字段。"""
|
|
|
+
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
|
|
|
|
|
@@ -80,6 +90,11 @@ def build_agent_tool_registry(
|
|
|
enrollment: EnrollmentService,
|
|
|
attribution: AttributionMetricsProvider | None = None,
|
|
|
) -> ToolRegistry:
|
|
|
+ """创建并注册第一阶段全部 Agent 工具。
|
|
|
+
|
|
|
+ 嵌套函数是真正的工具处理器;函数末尾的 ``definitions`` 声明哪些人格可见。
|
|
|
+ """
|
|
|
+
|
|
|
registry = ToolRegistry()
|
|
|
metrics = attribution or EmptyAttributionMetrics()
|
|
|
|
|
|
@@ -87,6 +102,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """查询真实在售产品,推荐产品前必须先调用。"""
|
|
|
+
|
|
|
del context
|
|
|
args = _arguments(arguments, ListProductsArgs)
|
|
|
products = catalog.list_available(category=args.category)
|
|
|
@@ -99,6 +116,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """按业务规则进行资格校验和保费测算,而不是让模型计算金额。"""
|
|
|
+
|
|
|
args = _arguments(arguments, CalculateQuoteArgs)
|
|
|
user = context.h5_user
|
|
|
if user is None:
|
|
|
@@ -108,6 +127,7 @@ def build_agent_tool_registry(
|
|
|
args.product_code,
|
|
|
args.plan_code,
|
|
|
)
|
|
|
+ # 资格不通过是可展示的业务结果;其他错误继续向上抛出。
|
|
|
try:
|
|
|
quote = enrollment.create_quote(
|
|
|
user,
|
|
|
@@ -176,6 +196,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """生成受控的投保页面入口,不在对话中收集证件等敏感信息。"""
|
|
|
+
|
|
|
if context.h5_user is None:
|
|
|
raise AppError("AGENT_CUSTOMER_REQUIRED", "投保引导需要H5用户身份", 401)
|
|
|
args = _arguments(arguments, PrepareEnrollmentArgs)
|
|
|
@@ -206,6 +228,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """只查询当前 H5 用户自己的订单,并裁剪为安全展示字段。"""
|
|
|
+
|
|
|
del arguments
|
|
|
user = context.h5_user
|
|
|
if user is None:
|
|
|
@@ -230,6 +254,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """只查询当前 H5 用户自己的电子保单。"""
|
|
|
+
|
|
|
del arguments
|
|
|
user = context.h5_user
|
|
|
if user is None:
|
|
|
@@ -254,6 +280,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """计算运营总览;SELF 数据范围只聚合当前运营人员归属的数据。"""
|
|
|
+
|
|
|
del arguments
|
|
|
orders = enrollment.list_orders()
|
|
|
policies = enrollment.list_all_policies()
|
|
|
@@ -285,6 +313,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """按后台账号的数据范围返回近期订单。"""
|
|
|
+
|
|
|
args = _arguments(arguments, RecentItemsArgs)
|
|
|
result = enrollment.list_orders()
|
|
|
if context.admin_user and context.admin_user.data_scope == "SELF":
|
|
|
@@ -312,6 +342,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """按后台账号的数据范围返回近期保单。"""
|
|
|
+
|
|
|
args = _arguments(arguments, RecentItemsArgs)
|
|
|
result = enrollment.list_all_policies()
|
|
|
if context.admin_user and context.admin_user.data_scope == "SELF":
|
|
|
@@ -339,6 +371,8 @@ def build_agent_tool_registry(
|
|
|
context: ToolExecutionContext,
|
|
|
arguments: BaseModel,
|
|
|
) -> HarnessToolResult:
|
|
|
+ """查询推广访问、线索、归因订单和保费指标。"""
|
|
|
+
|
|
|
del arguments
|
|
|
admin = context.admin_user
|
|
|
if admin is None:
|
|
|
@@ -373,6 +407,8 @@ def build_agent_tool_registry(
|
|
|
actions=[AgentAction(type="open_attribution", label="查看推广明细")],
|
|
|
)
|
|
|
|
|
|
+ # 工具定义同时声明参数 Schema、自然语言说明、人格范围和副作用级别。
|
|
|
+ # Agent Kernel 只会把当前人格有权使用的定义交给模型。
|
|
|
definitions = (
|
|
|
ToolDefinition(
|
|
|
name="list_available_products",
|
|
|
@@ -460,6 +496,8 @@ def build_agent_tool_registry(
|
|
|
|
|
|
|
|
|
def _arguments(value: BaseModel, expected: type[ToolArguments]) -> Any:
|
|
|
+ """确保包装层传入了预期的参数模型。"""
|
|
|
+
|
|
|
if not isinstance(value, expected):
|
|
|
raise AppError("AGENT_TOOL_ARGUMENTS_INVALID", "工具参数类型错误", 500)
|
|
|
return value
|
|
|
@@ -470,6 +508,8 @@ def _resolve_product_plan(
|
|
|
product_code: str,
|
|
|
plan_code: str,
|
|
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
|
+ """从真实在售目录中解析产品和计划,不接受模型虚构的编码。"""
|
|
|
+
|
|
|
product = next(
|
|
|
(item for item in catalog.list_available() if item["product_code"] == product_code),
|
|
|
None,
|
|
|
@@ -486,6 +526,8 @@ def _resolve_product_plan(
|
|
|
|
|
|
|
|
|
def _safe_order_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
|
+ """只保留允许交给模型和前端的订单字段。"""
|
|
|
+
|
|
|
return {
|
|
|
"order_id": item["order_id"],
|
|
|
"order_no": item["order_no"],
|
|
|
@@ -501,6 +543,8 @@ def _safe_order_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
|
|
|
|
|
|
|
def _safe_policy_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
|
+ """只保留允许交给模型和前端的保单字段。"""
|
|
|
+
|
|
|
return {
|
|
|
"policy_id": item["policy_id"],
|
|
|
"policy_no": item["policy_no"],
|
|
|
@@ -522,5 +566,7 @@ def _filter_business_result(
|
|
|
allowed_ids: set[str],
|
|
|
id_field: str,
|
|
|
) -> dict[str, Any]:
|
|
|
+ """按照允许的业务 ID 过滤列表,并重新计算总数。"""
|
|
|
+
|
|
|
items = [item for item in result["items"] if str(item.get(id_field, "")) in allowed_ids]
|
|
|
return {"items": items, "total": len(items)}
|