135 lines
7.0 KiB
Markdown
135 lines
7.0 KiB
Markdown
# 第 2a 步:使用 `wrap` 进行仪表化
|
||
|
||
> 完整的 `wrap()` API 参考见 `wrap-api.md`。
|
||
|
||
**目标**:在数据边界添加 `wrap()` 调用,以便评估工具能够(1)用受控输入替代真实的外部依赖进行注入,以及(2)捕获输出用于评分。
|
||
|
||
---
|
||
|
||
## 数据流分析
|
||
|
||
从 LLM 调用点出发,沿着代码向前和向后追溯,找到:
|
||
|
||
- **依赖输入**:来自外部系统(数据库、API、缓存、文件系统、网络请求)的数据
|
||
- **应用输出**:输出给用户或外部系统的数据
|
||
- **中间状态**:与评估相关的内部决策(路由、工具调用)
|
||
|
||
你**不需要**包装 LLM 调用的参数或响应——这些已被 OpenInference 自动仪表化捕获。
|
||
|
||
## 添加 `wrap()` 调用
|
||
|
||
对于找到的每个数据点,在应用代码中添加 `wrap()` 调用:
|
||
|
||
```python
|
||
import pixie
|
||
|
||
# 外部依赖数据——函数形式(在评估模式下阻止真实调用)
|
||
profile = pixie.wrap(db.get_profile, purpose="input", name="customer_profile",
|
||
description="从数据库获取的客户资料")(user_id)
|
||
|
||
# 外部依赖数据——函数形式(在评估模式下阻止真实调用)
|
||
history = pixie.wrap(redis.get_history, purpose="input", name="conversation_history",
|
||
description="从 Redis 获取的对话历史")(session_id)
|
||
|
||
# 应用输出——用户收到的内容
|
||
response = pixie.wrap(response_text, purpose="output", name="response",
|
||
description="助手对用户的响应")
|
||
|
||
# 中间状态——与评估相关的内部决策
|
||
selected_agent = pixie.wrap(selected_agent, purpose="state", name="routing_decision",
|
||
description="被选中处理此请求的智能体")
|
||
```
|
||
|
||
### 值与函数包装的对比
|
||
|
||
```python
|
||
# 值形式:包装一个数据值(结果已计算完毕)
|
||
profile = pixie.wrap(db.get_profile(user_id), purpose="input", name="customer_profile")
|
||
|
||
# 函数形式:包装可调用对象——在评估模式下不会调用原函数;
|
||
# 而是直接返回注册表中的值。
|
||
profile = pixie.wrap(db.get_profile, purpose="input", name="customer_profile")(user_id)
|
||
```
|
||
|
||
**关键:对于 `purpose="input"` 的外部调用包装,始终使用函数形式**——HTTP 请求、数据库查询、API 调用、文件读取、缓存查找。函数形式可以阻止真实调用在评估模式下执行,从而直接返回数据集中的值,而无需发起实时网络请求或数据库查询。值形式仍然会先执行真实调用,之后再替换结果——这会浪费时间、产生不稳定的测试,并使评估依赖于外部服务的可用性。
|
||
|
||
`purpose="input"` 下唯一可接受值形式的情况是:被包装的值是一个本地计算(无 I/O、无副作用)且重新计算代价很低。
|
||
|
||
### 放置规则
|
||
|
||
1. **在数据边界处包装**——在数据进入或离开应用的位置,而不是在工具函数内部深处。
|
||
2. **名称必须在整个应用中唯一**(用作注册表键名和数据集字段名)。
|
||
3. **名称使用 `lower_snake_case`**(小写蛇形命名法)。
|
||
4. **不要改变函数的接口**——`wrap()` 是纯附加的,返回相同类型。
|
||
|
||
### 按用途放置
|
||
|
||
#### `purpose="input"`——外部数据进入的位置
|
||
|
||
在**外部数据进入应用的边界**处放置输入包装,而不是在中间处理阶段。在管道架构(获取→处理→提取→格式化)中:
|
||
|
||
- **正确**:在 HTTP 请求边界处使用**函数形式** `wrap(fetch_page, purpose="input", name="fetched_page")(url)`——在评估模式下,请求被完全跳过并返回数据集中的值;在跟踪模式下,执行真实请求并捕获结果。
|
||
- **错误**:使用值形式 `wrap(html_content, purpose="input", name="fetched_page")`——评估模式下 HTTP 请求仍然会执行(浪费时间且产生不稳定测试),之后才替换结果。
|
||
- **错误**:在解析之后包装 `wrap(processed_chunks, purpose="input", name="chunks")`——评估模式会绕过解析和分块逻辑。
|
||
|
||
**原则**:`wrap(purpose="input")` 替换的是**最小外部依赖**,同时**最大限度地执行内部逻辑**。尽可能将边界向上游推进。对于外部调用的输入包装,**始终使用函数形式**——这样能阻止真实调用在评估模式下执行。
|
||
|
||
#### `purpose="output"`——处理后的数据退出的位置
|
||
|
||
从 LLM 响应开始**向下游**追踪,找到数据离开应用的位置——发送给用户、写入存储、在 UI 中渲染,或传递给外部系统。在该出口边界处进行包装。
|
||
|
||
- 不要包装原始 LLM 响应——这些已被 OpenInference 自动仪表化作为 `llm_span` 条目捕获。
|
||
- 包装应用的**最终处理结果**——即应用对 LLM 输出进行后处理、格式化或转换后的最终结果。
|
||
- 如果应用有多个输出通道(例如,给用户的响应 AND 写入数据库的副作用),分别包装每个通道。
|
||
|
||
```python
|
||
# 经过应用格式化管道后的最终响应
|
||
response = pixie.wrap(formatted_response, purpose="output", name="response",
|
||
description="发送给用户的最终响应")
|
||
|
||
# 副作用输出——写入外部存储的数据
|
||
pixie.wrap(saved_record, purpose="output", name="saved_summary",
|
||
description="保存到数据库的摘要记录")
|
||
```
|
||
|
||
**原则**:输出包装仅用于观察——它们捕获应用产生的内容,供评估者评分。在评估运行期间永远不会被模拟或注入。
|
||
|
||
#### `purpose="state"`——与评估相关的内部决策
|
||
|
||
某些评估标准需要评判应用的内部推理——不仅仅是输入或输出了什么,还包括应用**如何**做出决策。当某个评估标准需要此类数据,且该数据在输入或输出中不可见时,包装内部状态。
|
||
|
||
常见示例:
|
||
|
||
- **智能体路由**:选择了哪个子智能体或工具来处理请求
|
||
- **计划/步骤决策**:智能体选择执行哪些步骤
|
||
- **记忆更新**:智能体在工作记忆中添加或移除了什么
|
||
- **检索结果**:在输入 LLM 之前检索了哪些文档/分块
|
||
|
||
```python
|
||
# 智能体路由决策
|
||
selected_agent = pixie.wrap(selected_agent, purpose="state", name="routing_decision",
|
||
description="被选中处理此请求的智能体")
|
||
|
||
# 输入 LLM 的检索上下文
|
||
pixie.wrap(retrieved_chunks, purpose="state", name="retrieved_context",
|
||
description="RAG 在 LLM 调用前检索到的文档分块")
|
||
```
|
||
|
||
**原则**:只包装评估标准实际需要的状态。不要包装每个变量——状态包装仅用于评估者必须看到但不在应用输入或输出中出现的内部数据。
|
||
|
||
### 覆盖检查
|
||
|
||
添加完所有 `wrap()` 调用后,逐一检查 `pixie_qa/02-eval-criteria.md` 中的每条评估标准,确认:
|
||
|
||
1. 每条评判**输入了什么**的标准,都有对应的 `input` 或 `entry` 包装。
|
||
2. 每条评判**输出了什么**的标准,都有对应的 `output` 包装。
|
||
3. 每条评判**应用如何决策**的标准,都有对应的 `state` 包装。
|
||
|
||
如果某条标准需要的数据尚未被捕获,立即添加该包装——不要推迟。
|
||
|
||
---
|
||
|
||
## 输出
|
||
|
||
修改后的应用源代码文件,在数据边界处添加了 `wrap()` 调用。
|