笔记
Agent的学习记录
背景
我自己通过Vibe coding做了一个产品,主要是用于问卷生成系统和直接导出的,既往在流行病学,临床研究中也会使用到譬如EpiData,问卷星等程序,但我们以结果为导向,我们要的是直接可以用的格式化数据,比如BMI,这个数据我们在问卷里只会使用身高和体重等,但我们内置了一套计算转化的工具,可以最后直接生成;另外,我们还有直接生成基线表的工具,可以直接将格式化的数据导出为一个更直接的数据展现方式。在完成基本功能的实现后,我开始思考,从0生成问卷可能还是太麻烦了,我们可不可以通过设计一个Agent使得问卷生成变得更加简单,我们只需要通过对话的方式就能完成问卷的生成和实现,于是乎,开始有了这个学习的记录。在前段时间 Deepseek Harness 的宣布,我很好奇Harness的意思,后面我大概知道了,我们的模型只能输出文本,但这些个的文本他需要得以转化和实现,通常需要比如Powershell一类的工具得以实现,Harness是一个调度框架,把所有的流程整合在了一起。
1. Prompt and Harness
作为一个Agent,其可以接入不同的智能体模型,但不同的智能体其所产出的文本内容或者格式可能都不一样,通过Prompt来完成这个显然是不足的,因此,我们只需要让Prompt充当一个语义规则的人就可以,用于Agent身份的定义,约束条件,决策逻辑等,而Harness的话则用于工程规则,其是更高一层的管理者,有强制的措施。
比如Prompt只可以做到:
你是 XXX 文件生成 Agent。 --身份设定
你的任务是根据用户需求创建 XXX 配置。
要求: --约束条件
1. 不得猜测不存在的字段。
2. xxx_type 只能是 A/B/C。
3. 每个 node 必须拥有唯一 ID。
4. 如果信息不足,在 warnings 中返回原因。
5. 不直接生成磁盘路径。
而对于Harness:
模型输出
↓
JSON Schema Validation
↓
semantic validation
↓
normalization
↓
deterministic serialization
↓
atomic file write
而且每次Prompt的生成都可能随着模型的改进其LLM的输出都不一样。
2. 输出
一个很直接的想法是直接使用Prompt然后传入LLM并且直接给予输出,这样的 Demo 非常快,但是生产环境很容易出问题。因此我们只要求模型只产生内部结构,比如一个 json 格式:
{
"model": {
"name": "abc",
"version": 3
},
"nodes": [
{
"id": "001",
"input": "...",
"output": "..."
}
]
}
然后通过脚本,比如python,实现生成一个标准化的格式文件
3. 对话ID
因为在建立Agent的时候,不同的大模型其每轮对话都会有特定的Conversation ID,但此时不能将此作为我们储存对话的Session ID,一方面是切换模型时会产生新的文件夹,后续不好管理,另外一方面也是为了对话安全。因此 Session ID 应该由我们自己独立生成,而将外部模型的 ID 只是作为一个元数据。
3.1 应用层状态管理
我们的对话交给Harness自动保存会更加统一和和谐: 在Harness里,我们:
[
{
"role": "user",
"content": "..."
},
{
"role": "assistant",
"content": "..."
}
]
在下一次的时候,我们就可以直接
provider.generate(messages)
来统一重做,然后就可以实现模型之前的任意切换,且仍然处于一个对话里。
3.2 上下文管理
现有大模型上下文最多可以支持1M,但仍然不能支持无限累积,可以将文本分为几个层级
┌─────────────────────┐
│ System instructions │ -- 系统说明
├─────────────────────┤
│ Persistent state │ -- 持续状态
├─────────────────────┤
│ Conversation summary│ -- 对话总结
├─────────────────────┤
│ Recent N turns │ -- 短记忆
├─────────────────────┤
│ Relevant artifacts │ -- 相关内容
├─────────────────────┤
│ Current request │ -- 当下请求
└─────────────────────┘
在对话维护上,有三个状态很重要:
- Conversation State:用户和 Agent 说了什么
- Agent State: Agent 决定了什么
{
"file_format_version": "2.1",
"selected_mode": "abc",
"parameter_x": 5
}
- Artifact State: 这轮工作创建了什么
{
"current_artifact": "artifact_0007",
"revision": 12,
"checksum": "...",
"path": "artifacts/main.xxx"
}
3.3 URI管理
我们不要让模型直接决定真实路径,其只应该操作相对路径,比如
{
"operation": "write_cache",
"path": "analysis/result.json",
"content": {}
}
Harness上的映射:
resolve_path(
session_id,
"analysis/result.json"
)
最后写入的时候为
/runtime/workspaces/
01K8.../
cache/
analysis/
result.json
且最好连我们写入文件 write_file 都抽象为 tool,这样子在不同的操作系统上都可以不需要修改 Prompt,因为在这种情况下,所有的文件都会在虚拟文件系统上被读取写入和操作。
3.4 缓存区分
缓存为 cache ,但在 Agent 的运作过程中会不断产生 cache, 其来源也都不一样, 有来自对话的,有来自文件生成的,有来自 Agent 运作的,因此至少要区分:
session/
conversation/ -- 整个Session
state/ -- 整个Session
cache/ -- 删除后可重新生成
artifacts/ -- 创建结果
temp/ -- 单次Agent运行结果
4. 版本
对于所有的Prompt也应该表明版本,其好处在于方便管理,且后续更新的时候更简单迭代,不会因为模型的变化而导致Prompt混乱导致的输出异常。
