426 lines
11 KiB
Markdown
426 lines
11 KiB
Markdown
# Agent 组员入门易懂版说明
|
||
|
||
> 📌 **接入请以 `docs/14-Agent组员统一接入说明书.md` 为准**(唯一推荐入口,含完整工具清单与权限口径)。
|
||
> 本文是同一套教学序列的**入门易懂版**(与 `11` / `15` 内容高度重合),保留用于新人快速上手;
|
||
> 两者冲突时以 `14` 为准。
|
||
|
||
|
||
> 适用对象:第一次接入本项目的客服、投顾、风控和运营开发人员,以及协助编码的 AI。
|
||
> 阅读目标:看完后,能够在不破坏底座的前提下写出一个业务 Agent。
|
||
|
||
## 1. 先用一句话理解这个项目
|
||
|
||
你们要做的是“业务 Agent”,底座已经负责公共工作。
|
||
|
||
可以把系统想成一个机场:
|
||
|
||
```text
|
||
Controller = 机场入口和检票口
|
||
AgentFactory = 登机调度台
|
||
BaseAgent = 统一登机流程
|
||
你的 Agent = 具体航班
|
||
ToolExecutor = 受控的机场服务柜台
|
||
Worker = 真正执行飞行任务的工作人员
|
||
```
|
||
|
||
你只需要设计“这个航班要去哪里、提供什么业务服务”,不需要重新建机场、重新做安检或自己买飞机。
|
||
|
||
## 2. 你真正需要写的代码
|
||
|
||
一个业务 Agent 通常只需要三部分:
|
||
|
||
1. 定义 Agent 的名字、角色和意图;
|
||
2. 写一个继承 `BaseAgent` 的类;
|
||
3. 在公共工厂注册一行代码。
|
||
|
||
最小结构如下:
|
||
|
||
```text
|
||
app/service/agent/implementations/your_agent.py # 业务 Agent
|
||
tests/unit/service/test_your_agent.py # 业务测试
|
||
app/service/agent/bootstrap.py # 注册一行
|
||
```
|
||
|
||
不要复制 `BaseAgent`,不要复制模型调用,不要复制工具权限代码。
|
||
|
||
## 3. 第一步:定义 Agent
|
||
|
||
```python
|
||
from app.core.contracts import AgentDefinition
|
||
|
||
|
||
class AdvisorAgent(BaseAgent):
|
||
definition = AgentDefinition(
|
||
agent_type="advisor",
|
||
version="1.0.0",
|
||
allowed_roles=("customer", "advisor", "operator", "admin"),
|
||
allowed_portals=("api",),
|
||
allowed_tools=("query_fund_quote",),
|
||
supported_intents=("fund_quote", "general"),
|
||
)
|
||
```
|
||
|
||
### 每一项是什么意思
|
||
|
||
| 字段 | 简单理解 | 示例 |
|
||
|---|---|---|
|
||
| `agent_type` | Agent 的唯一名字 | `advisor` |
|
||
| `version` | 业务 Agent 版本 | `1.0.0` |
|
||
| `allowed_roles` | 哪些角色可以使用 | `customer`、`advisor` |
|
||
| `allowed_portals` | 从哪里进入 | `api` |
|
||
| `allowed_tools` | 代码允许使用哪些工具 | `query_fund_quote` |
|
||
| `supported_intents` | 这个 Agent 能处理哪些问题类型 | `fund_quote`、`general` |
|
||
|
||
注意:`allowed_tools` 是最大权限。配置中心只能减少工具,不能增加工具。
|
||
|
||
## 4. 第二步:写 `handle()`
|
||
|
||
```python
|
||
from app.core.contracts import AgentRequest, CoreResult, RequestContext
|
||
from app.service.agent.base import BaseAgent
|
||
|
||
|
||
class AdvisorAgent(BaseAgent):
|
||
definition = AgentDefinition(
|
||
agent_type="advisor",
|
||
version="1.0.0",
|
||
allowed_roles=("customer", "advisor", "operator", "admin"),
|
||
allowed_portals=("api",),
|
||
allowed_tools=("query_fund_quote",),
|
||
supported_intents=("fund_quote", "general"),
|
||
)
|
||
|
||
async def handle(
|
||
self,
|
||
request: AgentRequest,
|
||
context: RequestContext,
|
||
) -> CoreResult:
|
||
return CoreResult(text=f"你刚才说的是:{request.message}")
|
||
```
|
||
|
||
### `request` 和 `context` 的区别
|
||
|
||
`request` 是用户说的话,例如:
|
||
|
||
```text
|
||
请查询基金 159511 的行情
|
||
```
|
||
|
||
`context` 是系统验证后的身份信息,例如:
|
||
|
||
```text
|
||
user_id、roles、permissions、trace_id、customer_ids
|
||
```
|
||
|
||
身份、客户范围和权限必须相信 `context`,不要相信用户自己在消息里写的客户编号。
|
||
|
||
## 5. 第三步:注册 Agent
|
||
|
||
在 `app/service/agent/bootstrap.py` 中添加:
|
||
|
||
```python
|
||
factory.register(
|
||
AdvisorAgent.definition,
|
||
lambda _context: AdvisorAgent(AdvisorAgent.definition),
|
||
)
|
||
```
|
||
|
||
这行代码的意思是:
|
||
|
||
```text
|
||
当系统收到 agent_type=advisor 时,
|
||
请使用 AdvisorAgent.definition 检查权限,
|
||
然后创建一个 AdvisorAgent 实例。
|
||
```
|
||
|
||
为什么必须在这里注册?因为 HTTP 服务和 Worker 都从这里拿 AgentFactory。如果你在别的地方注册,
|
||
可能出现“接口能找到,Worker 找不到”的问题。
|
||
|
||
## 6. 系统会自动做什么
|
||
|
||
你写完 `handle()` 后,系统会自动完成:
|
||
|
||
```text
|
||
检查请求
|
||
→ 检查 JWT、角色和权限
|
||
→ 读取配置
|
||
→ 读取客户记忆
|
||
→ 判断用户意图
|
||
→ 执行你的 handle()
|
||
→ 检查违规内容和敏感信息
|
||
→ 保存结果、审计和事件
|
||
```
|
||
|
||
所以你不需要在 `handle()` 里重复写:
|
||
|
||
- JWT 验证;
|
||
- 角色判断;
|
||
- 数据库 Session;
|
||
- 记忆读取;
|
||
- 模型密钥读取;
|
||
- 工具权限判断;
|
||
- 审计日志;
|
||
- SSE 推送。
|
||
|
||
## 7. 如何使用基金行情工具
|
||
|
||
底座已经提供:
|
||
|
||
```text
|
||
工具名:query_fund_quote
|
||
权限码:fund:quote:read
|
||
```
|
||
|
||
### 7.1 先在 AgentDefinition 声明
|
||
|
||
```python
|
||
allowed_tools=("query_fund_quote",)
|
||
supported_intents=("fund_quote", "general")
|
||
```
|
||
|
||
### 7.2 在 `handle()` 中调用
|
||
|
||
```python
|
||
async def handle(
|
||
self,
|
||
request: AgentRequest,
|
||
context: RequestContext,
|
||
) -> CoreResult:
|
||
quotes = await self.call_tool(
|
||
"query_fund_quote",
|
||
{
|
||
"fund_codes": ["159511"],
|
||
"limit": 20,
|
||
},
|
||
intent="fund_quote",
|
||
context=context,
|
||
)
|
||
return CoreResult(text=f"行情查询结果:{quotes}")
|
||
```
|
||
|
||
### 7.3 为什么必须用 `call_tool()`
|
||
|
||
因为 `call_tool()` 会自动检查:
|
||
|
||
```text
|
||
工具是否存在
|
||
→ 当前意图是否允许用它
|
||
→ 当前角色是否允许用它
|
||
→ 当前用户是否有权限
|
||
→ 参数是否正确
|
||
→ 是否超时
|
||
→ 是否写入审计
|
||
```
|
||
|
||
不要这样写:
|
||
|
||
```python
|
||
import httpx
|
||
response = httpx.get("https://api.fund.eastmoney.com/...")
|
||
```
|
||
|
||
也不要这样写:
|
||
|
||
```python
|
||
from hq import get_southern_fund_market
|
||
```
|
||
|
||
外部行情必须由底座统一处理。
|
||
|
||
### 7.4 如何理解行情结果
|
||
|
||
重点字段:
|
||
|
||
```text
|
||
nav 基金净值
|
||
nav_date 净值日期
|
||
daily_change 日涨幅
|
||
quote_source eastmoney / cache / degraded
|
||
is_intraday 是否盘中
|
||
degraded 是否降级
|
||
```
|
||
|
||
当 `degraded=true` 时,应该说“当前数据可能不是最新行情”,不能说“已经成交”或“保证收益”。
|
||
|
||
## 8. 如何使用适当性校验
|
||
|
||
投顾 Agent 不要自己写风险等级比较:
|
||
|
||
```python
|
||
if customer_level >= product_level:
|
||
allowed = True
|
||
```
|
||
|
||
必须使用公共工具:
|
||
|
||
```python
|
||
decision = await self.call_tool(
|
||
"check_suitability",
|
||
{
|
||
"customer_risk_level": 3,
|
||
"product_risk_level": 3,
|
||
"product_requires_disclosure": True,
|
||
},
|
||
intent="risk_check",
|
||
context=context,
|
||
)
|
||
```
|
||
|
||
返回结果主要看:
|
||
|
||
```text
|
||
allowed 是否允许
|
||
reason_code 原因
|
||
required_disclosure 是否需要风险揭示
|
||
requires_confirmation 是否需要二次确认
|
||
requires_recording 是否需要双录
|
||
```
|
||
|
||
风险不匹配或测评过期时,必须停止高风险流程并给出安全提示。
|
||
|
||
## 9. 如何调用模型
|
||
|
||
大多数 Agent 不需要自己调用模型,因为底座已经自动完成意图分类。
|
||
|
||
只有业务确实需要生成一段说明文字时,才使用:
|
||
|
||
```python
|
||
result = await self.generate_with_model(
|
||
approved_endpoints,
|
||
"请根据以下行情生成简短风险说明:...",
|
||
)
|
||
```
|
||
|
||
这里的 `approved_endpoints` 必须来自底座路由结果,不能自己写模型地址。
|
||
|
||
禁止:
|
||
|
||
- 自己读取 API Key;
|
||
- 自己创建 OpenAI 或其他供应商客户端;
|
||
- 把模型原始回答直接返回;
|
||
- 让模型决定是否下单或修改持仓。
|
||
|
||
## 10. 配置中心怎么配
|
||
|
||
工具白名单的配置格式:
|
||
|
||
```text
|
||
namespace: agent_tools
|
||
config_key: advisor:fund_quote
|
||
value_json: {"allowed_tools": ["query_fund_quote"]}
|
||
```
|
||
|
||
配置只能小于等于代码中的 `allowed_tools`。例如代码没有声明 `query_fund_quote`,仅靠数据库配置
|
||
不能让 Agent 获得它。
|
||
|
||
行情运行配置使用:
|
||
|
||
```text
|
||
namespace: fund_market
|
||
config_key: default
|
||
```
|
||
|
||
配置缺失或格式错误时,底座会使用安全默认值,不要在业务代码里直接读取配置表。
|
||
|
||
## 11. 常见错误和解决办法
|
||
|
||
### 错误一:`AGENT_TYPE_NOT_FOUND`
|
||
|
||
原因:没有在 `bootstrap.py` 注册 Agent。
|
||
|
||
解决:补充 `factory.register(...)`,再运行注册表契约测试。
|
||
|
||
### 错误二:`FORBIDDEN`
|
||
|
||
原因可能是:角色不在 `allowed_roles`、入口不在 `allowed_portals`、缺少权限或工具没有配置到当前意图。
|
||
|
||
解决:检查代码声明和已发布配置,不要在 Agent 中绕过检查。
|
||
|
||
### 错误三:`RECOVERABLE_ERROR`
|
||
|
||
常见原因:模型、行情、Redis 等外部依赖暂时不可用。让 Worker 按策略重试,不要把供应商异常原文展示给用户。
|
||
|
||
### 错误四:工具找不到
|
||
|
||
确认三件事:
|
||
|
||
```text
|
||
AgentDefinition.allowed_tools 是否声明
|
||
当前 supported_intents 是否包含调用意图
|
||
agent_tools/<agent_type>:<intent> 是否发布了工具白名单
|
||
```
|
||
|
||
### 错误五:引用校验失败
|
||
|
||
不要手写 `SourceReference`。只引用 `self.memories` 或本次 `call_tool()` 返回的数据。
|
||
|
||
## 12. 测试怎么写
|
||
|
||
最少要测试:
|
||
|
||
```text
|
||
正常请求
|
||
未授权角色
|
||
未授权入口
|
||
低置信意图
|
||
模型输出错误
|
||
工具参数错误
|
||
工具超时
|
||
行情降级
|
||
适当性不通过
|
||
禁止表达和敏感信息
|
||
```
|
||
|
||
提交前执行:
|
||
|
||
```powershell
|
||
python -m pytest -q -p no:cacheprovider
|
||
python -m ruff check app tests tools alembic
|
||
python -m mypy app
|
||
python tools/check_authoritative_docs.py
|
||
python tools/audit_schema.py
|
||
```
|
||
|
||
## 13. 哪些事情绝对不能做
|
||
|
||
- 不要覆盖 `BaseAgent.execute()`;
|
||
- 不要自己实现鉴权、意图分类、工具权限或审计;
|
||
- 不要直接访问 MySQL、Redis、Milvus、Neo4j;
|
||
- 不要直接调用东方财富接口;
|
||
- 不要代客下单、确认成交或修改持仓;
|
||
- 不要把场外运营数据写进场内交易表;
|
||
- 不要修改已有表名、字段名、类型和既有含义;
|
||
- 不要提交 `.env`、私钥、Token 或 API Key。
|
||
|
||
## 14. 开发完成后的交接模板
|
||
|
||
```text
|
||
Agent 名称:
|
||
支持意图:
|
||
新增工具:
|
||
修改文件:
|
||
配置项:
|
||
测试命令:
|
||
测试结果:
|
||
数据库是否变化:
|
||
是否涉及场外流程:
|
||
已知限制:
|
||
```
|
||
|
||
如果数据库有需求,必须额外说明:新增了哪些表/字段、如何证明没有修改旧表和旧字段,以及迁移回滚方式。
|
||
|
||
## 15. 最后记住
|
||
|
||
你的业务 Agent 越简单越好:
|
||
|
||
```text
|
||
声明能力
|
||
→ 接收 request/context
|
||
→ 调用公共工具
|
||
→ 组织业务结果
|
||
→ 返回 CoreResult
|
||
```
|
||
|
||
安全、权限、模型、记忆、审计、恢复和外部依赖由底座负责。不要为了“方便”绕过底座,否则代码无法通过
|
||
契约测试,也会给其他组员和生产运行带来不一致行为。
|