AI系统开发思维

General#AI原生开发#ai自进化#ai-agent#Prompt#hermes#ai-memory#ai-评估体系
自然风景配图:AI系统开发思维
User
提问
U

大家在谈论anthropic的时候,提到:对很多应用来说,优化单次LLM调用,在配合检索和上下文示例,通常就够了。 这个观点在实际落地的时候都要做哪些工作,怎么做

C
claude
模型回复

Claude responded: 这个观点的完整表述是:在你跳到multi-agent、复杂pipeline之前,先把单次调用做到极致。很多团队在单次调用还很粗糙的时候就去搭复杂系统,结果复杂度上去了,效果没好多少。

这个观点的完整表述是:在你跳到multi-agent、复杂pipeline之前,先把单次调用做到极致。很多团队在单次调用还很粗糙的时候就去搭复杂系统,结果复杂度上去了,效果没好多少。

让我把"优化单次LLM调用 + 检索 + 上下文示例"这三件事拆开说清楚。


一、优化单次LLM调用

这是最被低估的部分。大多数人的prompt停留在"描述一下任务"的水平,但一个认真优化过的prompt和随手写的prompt,效果差距可以非常大。

1.1 把任务分解写进prompt,而不是让模型自己分解

差的写法:

分析这份合同,告诉我有什么风险。

好的写法:

分析这份合同,按以下步骤:
1. 先识别合同类型和主要当事方
2. 列出所有涉及金额、期限、违约责任的条款
3. 对每个条款评估:对我方有利/不利/中性
4. 最后给出前三个最需要关注的风险点

每个风险点写明:条款位置、风险描述、建议处理方式。

区别在于:你把思考步骤显式化了,模型按步骤走,不容易跳步、漏项。

1.2 用输出格式约束来提高一致性

如果你的输出要被程序处理,格式约束是必须的:

输出格式严格按照以下JSON,不要有任何其他文字:
{
  "risk_level": "high|medium|low",
  "clauses": [
    {
      "clause_id": "string",
      "content": "string", 
      "risk": "string",
      "recommendation": "string"
    }
  ],
  "top_risks": ["string", "string", "string"]
}

1.3 给模型一个角色和判断框架

不是为了"让模型扮演角色"这种玩法,而是给模型一个明确的判断视角:

你是一个代表乙方的合同律师,你的目标是保护乙方利益。
在评估每个条款时,从乙方角度判断风险。

没有这个框架,模型会给出"中立"的分析,对你的具体场景用处有限。

1.4 系统提示和用户提示分开管理

系统提示放稳定不变的内容:角色定义、输出格式、判断框架、约束条件。 用户提示放每次变化的内容:具体输入、具体问题。

这样做的好处是:系统提示可以精心打磨一次,反复复用;用户提示保持简洁。很多人把所有东西都堆在用户提示里,每次都在重复解释背景,效率很低。

1.5 温度和采样参数

这个经常被忽视。对于需要准确、一致输出的任务(分类、提取、分析),temperature设低(0到0.3)。对于需要创意和多样性的任务(写作、头脑风暴),temperature设高(0.7到1.0)。用默认值处理所有任务是一种懒惰。


二、检索(RAG)

检索解决的问题是:模型不知道你的私有数据、最新信息、或者超出上下文窗口的长文档。

2.1 检索的完整流程

用户问题
    ↓
查询改写(Query Rewriting)
    ↓
向量检索 / 关键词检索 / 混合检索
    ↓
重排序(Reranking)
    ↓
上下文压缩(把检索到的内容精简)
    ↓
组装进prompt → LLM调用
    ↓
输出

每一步都有优化空间,但很多人只做了中间的"向量检索"这一步,前后都跳过了。

2.2 查询改写——被低估最多的步骤

用户的原始问题往往不适合直接用来检索。

原始问题:"上次说的那个方案怎么样了" 改写后:"项目方案进度更新 状态报告"

原始问题:"这个功能为什么这么慢" 改写后:"功能性能问题 响应时间慢 原因分析"

查询改写可以用一个小的LLM调用来做:

将以下用户问题改写为适合文档检索的搜索查询。
去掉指代词,补充可能的关键词,输出2-3个检索变体。

用户问题:{question}

这一步能显著提升检索召回率。

2.3 分块策略直接决定检索质量

文档怎么切块,对检索效果的影响比向量模型的选择更大。

按固定字数切(最常见但效果最差):切块不考虑语义边界,一句话可能被切成两半。

按语义边界切:按段落、按标题层级、按句子组切。同一个主题的内容在同一块里,检索时更容易命中。

父子切块:小块用来检索(粒度细,命中准),命中后返回它的父块(内容完整,上下文足)。这是目前实践中效果最好的策略之一。

文档结构:
├── 第3章:渠道管理(父块,2000字)
│   ├── 3.1 添加渠道(子块,300字)← 检索命中这里
│   ├── 3.2 渠道测试(子块,400字)
│   └── 3.3 禁用渠道(子块,250字)

返回给LLM的是:父块(第3章全文)

2.4 混合检索比纯向量检索更鲁棒

向量检索擅长语义相似("怎么发短信" 能找到"短信发送方法")。 关键词检索擅长精确匹配(产品型号、人名、代码片段)。

两者结合,用RRF(Reciprocal Rank Fusion)合并结果,覆盖两类查询。纯向量检索在精确名词查询上经常失败,这是实际落地中很常见的坑。

2.5 重排序(Reranking)

向量检索召回20条,不代表top 5就是最相关的。Reranker做的事是:用一个专门训练的模型,对查询和每个候选文档做精细的相关性打分,重新排序。

常用方案:

  • Cohere Rerank API(直接可用)
  • BGE-Reranker(开源,可本地部署)
  • 用LLM做rerank(效果好但慢,适合低延迟要求不高的场景)

这一步通常能让最终结果的相关性提升20-30%,但很多系统跳过了。

2.6 检索结果的质量评估

很多人建完RAG系统,只用肉眼感受好不好。实际上应该建一个评估集:

评估集格式:
问题 → 正确答案应该来自哪个文档片段

评估指标:
- 召回率:正确文档是否在检索结果里(不管排第几)
- 精确率:top-k里有多少是真正相关的
- MRR:正确文档的排名

没有评估集,你不知道优化是否真的有效,改了半天可能在某些查询变好、另一些查询变差,整体没有进步。


三、上下文示例(Few-shot)

这是prompt engineering里最稳定有效的技术,但需要认真设计,不是随手放几个例子。

3.1 示例的作用

示例告诉模型:不只是任务是什么,而是我期望的输出风格、详细程度、判断标准是什么样的

纯指令有时候描述不清楚的东西,一个好例子能立刻说清楚。

3.2 示例怎么选

不要用随手想出来的例子。好的示例来自真实数据:

  1. 先用zero-shot(不给示例)跑100个真实输入
  2. 人工标注哪些输出是好的、哪些是差的
  3. 从好的输出里选3-5个作为示例
  4. 从差的输出里分析错误模式,针对性地写示例来纠正

这样选出来的示例覆盖了模型真实容易犯错的地方,而不是你主观认为重要的地方。

3.3 动态示例比固定示例更好

固定示例:每次调用都用同样的3个例子。

动态示例:根据当前输入,从示例库里检索最相似的3个例子放进prompt。

python

# 示例库:500个已标注的输入输出对
# 用户来了一个新输入
# 从示例库里检索最相似的3个 → 放进prompt

示例库
    ↓ 向量检索(用输入做查询)
最相似的3个示例
    ↓
动态组装进prompt

动态示例的效果通常比固定示例好,因为示例和当前输入越相似,模型越能迁移。

3.4 示例的数量和顺序

数量:3-5个通常足够,不是越多越好。太多示例会压缩真实输入的上下文空间,而且模型会过拟合示例的风格。

顺序:最相关的示例放最后(离真实输入最近),因为模型对靠近输入的内容注意力更高。


三件事组合起来

单独做好每一件事是基础,但真正的效果来自把三者有机结合:

用户输入
    ↓
查询改写(小LLM调用)
    ↓
并行:
  ├── 检索相关文档(RAG)
  └── 检索相似示例(Few-shot库)
    ↓
组装prompt:
  系统提示(角色 + 框架 + 输出格式)
  + 动态示例(3个最相似的)
  + 检索到的相关文档
  + 用户输入
    ↓
单次LLM调用
    ↓
输出

这个架构没有agent、没有多步推理、没有工具调用,但如果每个环节都做扎实,能解决80%的实际业务问题,而且延迟低、成本可控、行为可预测。

什么时候这个架构不够用?

当任务需要真正的多步决策(下一步取决于上一步的结果)、需要调用外部工具、或者需要并行处理多个子任务时,才真正需要往agent方向走。但很多团队在还没把这一层做扎实之前就跳过去了,结果复杂度高、调试难、效果也没好多少。

User
提问
U

你这里没有提到如何评估单次LLM调用是否最优,或者如何设计这个机制,类似agent一样,把自我优化当作他的目标之一,他能自己进步,这个需要怎么做

C
Claude
模型回复

这个问题触到了一个真正有深度的工程问题。你说的本质是:让系统能观测自己的输出质量,并根据观测结果改进自己的prompt。这在工程上叫"prompt optimization loop"或者"self-improving pipeline"。

让我认真设计这个机制。


先想清楚"最优"是什么意思

在设计自优化之前,必须先回答一个问题:用什么信号判断一次调用是好还是坏?

这个信号来源有三类,决定了整个优化机制的设计:

信号来源
├── 显式反馈:用户直接告诉你好不好(点赞/踩、改写、投诉)
├── 隐式反馈:用户行为推断(采纳了输出?还是忽略了?复制了吗?)
└── 自动评估:另一个LLM或规则判断输出质量

三类信号各有局限:显式反馈稀少,隐式反馈有噪声,自动评估有偏差。好的系统需要把三类信号结合起来。


完整的自优化架构

                    ┌─────────────────────────────┐
                    │         Prompt库              │
                    │  prompt_v1, v2, v3...         │
                    │  每个版本有性能记录            │
                    └──────────┬──────────────────┘
                               │ 当前最优版本
                               ▼
用户输入 ──────────────→  单次LLM调用  ──────────→  输出
                               │                      │
                               │                      ▼
                               │              评估层(Judge)
                               │                      │
                               │              ┌───────┴────────┐
                               │              │  评估结果+原因  │
                               │              └───────┬────────┘
                               │                      │
                               ▼                      ▼
                         日志存储层  ←────────  观测层
                    (输入/输出/评分)         (聚合分析)
                               │
                               ▼
                         优化触发器
                    (定期 or 质量下降时)
                               │
                               ▼
                         Optimizer LLM
                    (分析失败案例 → 生成新prompt)
                               │
                               ▼
                         候选prompt测试
                    (在历史数据上回测)
                               │
                               ▼
                         A/B测试 or 直接替换

第一层:评估层(Judge)

这是整个系统的基础,没有评估就没有优化。

评估层的设计

用一个独立的LLM调用来评估主调用的输出质量,这叫"LLM-as-Judge"。

JUDGE_PROMPT = """
你是一个严格的输出质量评估者。

任务类型:{task_type}
用户输入:{user_input}
系统输出:{system_output}
参考标准:{criteria}

请从以下维度评估输出质量,每项1-5分:
1. 准确性:输出内容是否正确、无事实错误
2. 完整性:是否覆盖了输入中的所有要求
3. 格式合规:是否符合要求的输出格式
4. 简洁性:是否有冗余内容

对于每个低于4分的维度,必须写出:
- 具体问题是什么
- 一个改进的例子

输出严格按JSON:
{
  "scores": {
    "accuracy": int,
    "completeness": int, 
    "format": int,
    "conciseness": int
  },
  "overall": float,
  "failures": [
    {
      "dimension": str,
      "problem": str,
      "example_fix": str
    }
  ],
  "pass": bool  // overall >= 3.5 则为true
}
"""

关键设计原则:Judge必须给出具体的失败原因和改进例子,不只是打分。这些原因后面会喂给Optimizer。

评估标准怎么来

不同任务类型的评估标准不一样,需要分开定义:

CRITERIA = {
    "extraction": """
        - 所有要求提取的字段都存在
        - 字段值与原文一致,无捏造
        - 格式符合schema定义
    """,
    "classification": """
        - 分类结果在允许的类别范围内
        - 置信度与输出一致
        - 边界情况有说明
    """,
    "generation": """
        - 内容与输入主题相关
        - 没有重复已知的错误模式
        - 长度在要求范围内
    """
}

防止Judge偏差

Judge LLM本身也有偏差,主要问题:

  • 位置偏差:倾向于给第一个或最后一个选项打高分
  • 长度偏差:倾向于给更长的输出打高分
  • 自我偏袒:同一个模型评估自己的输出

缓解方式:

# 对同一输出,用不同顺序评估两次,取平均
# 评估时明确提示"长度不是质量标准"
# 考虑用不同厂商的模型做Judge
# 对于关键任务,维护一个人工标注的golden set做校准

第二层:观测层

把所有调用的输入、输出、评估结果存下来,这是优化的原材料。

日志结构

{
  "call_id": "uuid",
  "timestamp": "ISO8601",
  "prompt_version": "v3.2",
  "input": {
    "user_query": str,
    "retrieved_docs": [str],  # 如果有RAG
    "examples_used": [str]    # 如果有few-shot
  },
  "output": str,
  "judge_result": {
    "scores": {...},
    "overall": float,
    "failures": [...]
  },
  "user_signal": {
    "adopted": bool,     # 用户是否采纳了输出
    "edited": bool,      # 用户是否修改了输出
    "explicit": int      # 用户显式评分,-1/0/1
  },
  "latency_ms": int,
  "token_count": int
}

聚合分析:发现系统性问题

单条日志看不出规律,需要聚合:

# 按输入类型分组,看哪类输入失败率最高
failure_by_input_type = logs.groupby('input_type')['pass'].mean()

# 看失败原因的分布
failure_reasons = logs[~logs['pass']].explode('failures')
top_failures = failure_reasons['dimension'].value_counts()

# 看prompt版本的性能曲线
version_performance = logs.groupby('prompt_version')['overall'].agg(['mean', 'std', 'count'])

# 找到具体的失败模式
# 把所有failures的problem字段聚类,找出反复出现的问题

这个分析的输出是给Optimizer的上下文,告诉它"系统在哪里系统性地失败"。


第三层:Optimizer

这是真正"自我改进"的部分。Optimizer是一个LLM调用,输入是失败案例分析,输出是改进后的prompt。

Optimizer的prompt设计

OPTIMIZER_PROMPT = """
你是一个prompt工程专家。你的任务是改进一个LLM系统的prompt,
使其在失败案例上表现更好,同时不破坏已经表现良好的场景。

## 当前prompt(版本{version})
{current_prompt}

## 性能统计
- 总调用次数:{total_calls}
- 通过率:{pass_rate}%
- 平均分:{avg_score}

## 系统性失败模式(出现超过5次的问题)
{failure_patterns}

## 典型失败案例(每种失败模式各3个)
{failure_examples}

## 已知的成功案例(不要破坏这些)
{success_examples}

## 任务
1. 分析失败的根本原因(不是表面现象)
2. 提出针对性的修改方案,每个修改说明:
   - 修改了什么
   - 为什么这个修改能解决对应的失败模式
   - 可能带来的风险
3. 输出改进后的完整prompt

输出格式:
{
  "root_cause_analysis": str,
  "changes": [
    {
      "what": str,
      "why": str,
      "risk": str
    }
  ],
  "new_prompt": str
}
"""

关键约束:不要让Optimizer乱改

Optimizer如果没有约束,会倾向于生成越来越长、越来越复杂的prompt,堆砌大量示例,结果是过拟合失败案例、破坏原本工作良好的场景。

需要给Optimizer加约束:

OPTIMIZER_CONSTRAINTS = """
约束条件(必须遵守):
1. 新prompt的token数不得超过当前prompt的150%
2. 不得删除当前prompt中已有的格式约束
3. 修改必须针对失败模式,不得做无关的"优化"
4. 每次只解决最严重的1-2个失败模式,不要试图一次解决所有问题
"""

第四层:候选prompt验证

Optimizer生成的新prompt不能直接上线,必须先验证。

在历史数据上回测

def backtest_prompt(new_prompt, historical_logs, n=100):
    # 从历史日志里抽样
    # 包含:之前失败的案例(验证改进)+ 之前成功的案例(验证不退步)
    
    test_cases = sample_balanced(
        failures=historical_logs[~historical_logs['pass']],
        successes=historical_logs[historical_logs['pass']],
        n=n
    )
    
    results = []
    for case in test_cases:
        output = call_llm(new_prompt, case['input'])
        score = judge(output, case['input'])
        results.append({
            'case_id': case['id'],
            'was_failing': not case['pass'],
            'new_score': score['overall'],
            'old_score': case['overall']
        })
    
    # 关键指标
    fixed_rate = mean(r['new_score'] > r['old_score'] 
                      for r in results if r['was_failing'])
    regression_rate = mean(r['new_score'] < r['old_score'] - 0.5
                           for r in results if not r['was_failing'])
    
    return {
        'fixed_rate': fixed_rate,        # 修复了多少失败案例
        'regression_rate': regression_rate,  # 破坏了多少成功案例
        'deploy': fixed_rate > 0.3 and regression_rate < 0.1
    }

上线标准:修复率 > 30%,且回归率 < 10%。这两个阈值需要根据你的业务容忍度调整。

小流量A/B测试

回测通过后,不是直接全量替换,而是先给10%的流量用新prompt:

def route_request(user_input, experiment_config):
    if random() < experiment_config['new_prompt_ratio']:  # 0.1
        prompt = load_prompt('candidate')
        version = 'candidate'
    else:
        prompt = load_prompt('current')
        version = 'current'
    
    output = call_llm(prompt, user_input)
    log(version=version, input=user_input, output=output)
    return output

# 跑够1000次后对比两个版本的pass_rate和user_signal
# 统计显著性检验确认差异真实存在

触发优化的时机

不是每天都跑优化,而是在以下条件触发:

def should_optimize(recent_logs, window=1000):
    recent_pass_rate = recent_logs.tail(window)['pass'].mean()
    baseline_pass_rate = load_baseline_pass_rate()
    
    triggers = {
        # 质量下降:近期通过率比基线低5个百分点
        'quality_degradation': recent_pass_rate < baseline_pass_rate - 0.05,
        
        # 定期优化:每积累500个新的失败案例
        'accumulation': count_new_failures() > 500,
        
        # 新的失败模式出现:某类错误突然增多
        'new_failure_pattern': detect_new_patterns(recent_logs),
    }
    
    return any(triggers.values()), triggers

整个循环的实际样子

第1周:
  - 部署系统,开始收集日志
  - 人工标注200个golden cases,建立Judge校准基准
  - 通过率:假设65%

第2周:
  - 积累了800个失败案例
  - 聚合分析发现:40%的失败是"格式不合规",30%是"遗漏字段"
  - Optimizer针对这两个问题生成v2 prompt
  - 回测:修复率42%,回归率6% → 通过
  - A/B测试10%流量,1000次后通过率提升到71%
  - 全量替换为v2

第4周:
  - 新的失败模式出现:边界输入处理差
  - 触发新一轮优化
  - v3 prompt上线,通过率76%

第8周:
  - 通过率稳定在78-80%
  - 优化触发频率降低,系统趋于稳定
  - 剩余的20%失败开始需要人工介入判断
    (这部分可能是任务本身的模糊性,不是prompt问题)

一个重要的认知边界

这套机制能做到的是:在你定义的评估标准范围内,系统性地减少失败

它做不到的是:

  • 自动发现你的评估标准本身是错的
  • 处理评估标准覆盖不到的新型失败
  • 替代你对"什么是好输出"的根本判断

所以这套系统不是真正的"自主进化",而是"在人定义的目标函数下自动优化"。目标函数的定义和校准,始终需要人来做。这也是为什么Judge的golden set需要人工标注,而不能完全自动生成。

user
提问
U

你设计的这套机制,和hermes claw的自进化机制相比,是同一个技术实现原理吗,都说hermes也能实现自进化和学习,它的设计和代码是如何实现它的自进化的。你的设计是否有可以借鉴它的地方。

C
Claude
模型回复

我需要先搜索确认这个项目的真实情况,不想凭印象回答。现在我读到了足够的真实信息。可以做一个准确的对比分析了。


先说结论:两套机制的本质不同

表面上都叫"自进化",但Hermes和我设计的机制在进化的对象、进化的触发方式、信号来源上完全不同。不是同一个技术原理。


Hermes的自进化:三层机制叠加

Hermes的核心自进化体现在:持久记忆(agent自己策划事实写入MEMORY.md)、自主技能创建(复杂任务后自动创建可复用技能文档)、技能自我改进(使用中发现技能过时或不完整时自动修补)。

第一层:运行时技能学习(内置核心功能)

它的学习循环:从经验创建技能,在使用中改进技能,跨会话持久记忆,跨会话搜索历史对话,建立用户的深度模型。

机制是这样的:

用户执行一个复杂任务(5+个工具调用)
            ↓
任务完成后,agent自动反思:
"这个任务有没有可以复用的工作流程?"
            ↓
如果有 → 自动生成 SKILL.md 文件
写入 ~/.hermes/skills/ 目录
            ↓
下次遇到类似任务,agent先检索技能库
找到匹配的技能 → 直接复用
            ↓
使用过程中发现技能描述不准确/不完整
→ agent在当次会话中自动patch这个skill文件

关键点:这是经验→程序化知识的转化,技能文件是结构化的Markdown(SKILL.md格式),agent既是技能的消费者也是技能的生产者和维护者。整个循环发生在运行时,不需要离线训练步骤。

第二层:记忆层(跨会话持久化)

Hermes有插件式内存提供者——内置内存层可以通过已发布的memory-provider插件扩展,比如Honcho和其他后端。on_session_start钩子支持自动上下文加载。

记忆分三层:

  • 事实记忆(MEMORY.md):agent自己判断什么值得长期记住,主动写入
  • 会话记忆(SQLite FTS5):所有历史会话可被语义检索,存储可搜索的会话历史,并用LLM摘要处理
  • 用户模型(Honcho dialectic):跨会话构建用户的深度画像,理解偏好、工作方式、判断标准

第三层:hermes-agent-self-evolution(研究级,独立repo)

使用DSPy + GEPA(Genetic-Pareto Prompt Evolution)自动进化Hermes Agent的技能文件、工具描述、系统提示和代码——通过反思性进化搜索产生可量化更好的版本。不需要GPU训练,完全通过API调用完成——变异文本、评估结果、选择最优变体。每次优化运行约2-10美元。

GEPA的工作流程:

读取当前skill/prompt/tool
        ↓
生成评估数据集(合成 or 真实session历史)
        ↓
GEPA优化器读取执行轨迹
理解"为什么失败"而不只是"失败了"
        ↓
提出针对性变异(不是随机变异)
        ↓
生成候选变体
        ↓
约束门控:
  - pytest必须100%通过
  - 大小限制(skill≤15KB)
  - 语义保持(不能偏离原始目的)
        ↓
最优变体 → 提PR,人工review,不直接commit

关于防止自我欺骗的建议:将hermes-agent-self-evolution与定期回归检查配合使用,并用第二次评估来阻止糟糕的prompt变异。诀窍不是"进化更快",而是"进化时不悄悄变奇怪"。


两套机制的本质对比

维度我设计的机制Hermes内置机制Hermes GEPA机制
进化对象Prompt文本技能文件(SKILL.md)+ 记忆Prompt + 工具描述 + 代码
进化触发质量下降/失败积累任务完成后自动触发人工/定期调度
信号来源LLM-as-Judge评分任务复杂度(工具调用数)执行轨迹(execution traces)
优化算法LLM直接生成改进建议无算法,纯LLM判断GEPA遗传算法+Pareto选择
验证方式历史数据回测+A/B使用中自然验证pytest+约束门控
人工参与A/B测试决策节点基本无人工PR review必须通过
学习粒度单个task的prompt可复用工作流程(跨任务)具体的skill/tool文档
持久化方式版本化prompt库文件系统(~/.hermes/)Git PR

Hermes有哪些地方值得借鉴到我设计的机制里

借鉴点一:执行轨迹比评分更有信息量

我的设计用Judge LLM打分,然后让Optimizer分析失败案例。Hermes GEPA的做法是直接读执行轨迹——agent做了哪些工具调用、中间推理步骤是什么、在哪一步出了问题。

GEPA读取执行轨迹来理解为什么失败(而不只是失败了),然后提出针对性改进。

这个信息比"Judge说输出质量3.2分"要丰富得多。改进方向:

# 现有:只记录输入输出和评分
log = {
    "input": ...,
    "output": ...,
    "judge_score": 3.2,
    "failures": [...]
}

# 借鉴Hermes:同时记录推理轨迹
log = {
    "input": ...,
    "reasoning_trace": [
        {"step": 1, "thought": "...", "action": "...", "result": "..."},
        {"step": 2, "thought": "...", "action": "...", "result": "..."},
    ],
    "output": ...,
    "judge_score": 3.2,
    "failure_step": 2,  # 在第几步出了问题
    "failure_reason": "在步骤2错误地跳过了格式校验"
}

Optimizer拿到的输入从"输出结果不好"变成了"在步骤2的判断逻辑有问题",生成的改进建议会更精准。

借鉴点二:技能库概念——把优化结果结构化存储

我的设计把优化结果存为"prompt_v1, v2, v3",是线性版本。Hermes的技能库是按任务类型索引的知识库,不同任务类型有不同的技能,可以被检索和组合。

借鉴到我的设计里:

prompt_library/
├── extraction/
│   ├── contract-risk-extraction_v3.md    ← 合同风险提取的最优prompt
│   ├── invoice-parsing_v2.md
│   └── performance_log.json              ← 每个版本的历史性能
├── classification/
│   ├── intent-detection_v4.md
│   └── sentiment_v2.md
└── generation/
    └── report-writing_v5.md

这样当一个新任务来了,先检索最相似的已有prompt作为起点,而不是每次从零开始优化,也不是用同一个通用prompt处理所有任务。

借鉴点三:GEPA的遗传算法思路——多样性+Pareto选择

我的Optimizer每次只生成一个候选prompt。GEPA的做法是同时生成多个变体(种群),然后用Pareto前沿选择:在多个评估维度上都不被其他变体支配的那些变体才进入下一轮

这避免了一个常见问题:单一Optimizer生成的改进方案,容易在提升某个维度的同时降低另一个维度。

简化版实现:

def gepa_style_optimization(current_prompt, failure_cases, n_variants=5):
    # 生成多个不同方向的改进变体
    variants = []
    mutation_directions = [
        "重点修复格式不合规问题",
        "重点修复遗漏字段问题", 
        "提高简洁性,减少冗余",
        "增加边界情况的处理说明",
        "重写整体结构但保持约束"
    ]
    
    for direction in mutation_directions:
        variant = optimizer_llm(current_prompt, failure_cases, direction)
        variants.append(variant)
    
    # 多维度评估每个变体
    scores = []
    for v in variants:
        scores.append({
            "variant": v,
            "fix_rate": eval_on_failures(v, failure_cases),
            "regression_rate": eval_on_successes(v, success_cases),
            "token_efficiency": len(v) / len(current_prompt)
        })
    
    # Pareto筛选:找出没有被任何其他变体在所有维度上支配的变体
    pareto_front = get_pareto_front(scores)
    return pareto_front  # 返回多个候选,人工或A/B决定最终选择

借鉴点四:约束门控在变体评估前就过滤

我的设计是先生成候选prompt,再回测,再A/B。Hermes在评估之前就有一道硬性约束过滤:

每个进化变体必须通过:完整测试套件(pytest 100%通过)、大小限制(技能≤15KB)、缓存兼容性(不能在对话中途改变)、语义保持(不能偏离原始目的)。

在我的设计里对应:

def validate_candidate_prompt(new_prompt, current_prompt):
    gates = {
        # 大小约束:不能超过当前的150%(防止无限膨胀)
        "size": len(new_prompt) <= len(current_prompt) * 1.5,
        
        # 格式约束:必须包含输出格式定义
        "has_output_format": "输出格式" in new_prompt or "JSON" in new_prompt,
        
        # 语义保持:核心任务描述不能消失
        "semantic_preservation": check_core_intent_preserved(
            current_prompt, new_prompt
        ),
        
        # 最低质量门槛:在golden set上不能低于基线的80%
        "baseline_floor": eval_on_golden_set(new_prompt) >= baseline * 0.8
    }
    
    # 任何一个门控失败,直接拒绝,不进入回测
    if not all(gates.values()):
        failed = [k for k, v in gates.items() if not v]
        return False, f"约束门控失败:{failed}"
    
    return True, "通过"

这样能在进入昂贵的回测之前过滤掉明显有问题的候选。


一个重要的认知校准

Hermes代表了从静态系统到进化实体的转变,优先适应性而非配置,优先学习而非脚本编写,优先简单性而非基础设施。

但有一点需要清醒认识:Hermes的"自进化"和我设计的"prompt优化循环",在进化对象上有根本区别。

Hermes进化的是技能文件和工作流程——它在学习"如何做事",这是程序化知识的积累。我的设计进化的是prompt文本——它在优化"如何表达任务",这是提示质量的改善。

两者都是有价值的,但解决的不是同一个问题。如果你的系统需要处理越来越多类型的任务,Hermes的技能库机制更合适。如果你的系统任务类型固定但需要持续提升单类任务的输出质量,我设计的优化循环更直接。

真正完整的自优化系统应该把两层都做:技能库解决"知道怎么做",prompt优化循环解决"每次都做好"。

User
提问
U

所以,两者如果结合才是最佳的选择吗。你之前的设计解决单次和LLM沟通的问题,hermes优化解决问题过程中多次和人类以及LLM沟通的效率和准确性。如果是结合一起更好,怎么做到?

C
Claude
模型回复

你的理解方向对,但我想先把这个区分说得更精确,再谈怎么结合。

你说的"单次LLM调用"和"多次沟通",本质上是两个不同层次的问题:

我的设计解决的:
  同一个任务类型,每次调用怎么做得更准确
  → 优化的是"表达"——如何把任务说清楚

Hermes解决的:
  跨任务、跨会话,积累可复用的工作方式
  → 优化的是"知道"——学会如何处理一类问题

两者结合不是简单叠加,而是需要设计清楚哪一层的信息往哪里流


结合后的完整架构

用户输入
   ↓
┌─────────────────────────────────────────┐
│           技能路由层(Hermes机制)          │
│  检索技能库 → 找到匹配的技能               │
│  技能里包含:最优prompt版本 + 上下文示例    │
└──────────────┬──────────────────────────┘
               ↓ 技能存在               ↓ 技能不存在
    ┌──────────┴──────────┐    ┌────────┴────────────┐
    │   加载技能的prompt   │    │  使用通用prompt      │
    │   + 动态示例        │    │  标记为"新任务类型"   │
    └──────────┬──────────┘    └────────┬────────────┘
               └──────────┬────────────┘
                           ↓
┌─────────────────────────────────────────┐
│         单次LLM调用优化层(我的设计)       │
│  RAG检索 + 动态few-shot + 格式约束        │
└──────────────┬──────────────────────────┘
               ↓
            LLM调用
               ↓
┌─────────────────────────────────────────┐
│              执行轨迹记录                 │
│  input / reasoning steps / output       │
│  judge评分 / 用户信号                    │
└──────┬────────────────────┬─────────────┘
       ↓                    ↓
┌──────┴──────┐    ┌────────┴────────────┐
│  prompt优化  │    │   技能知识更新       │
│  循环        │    │   循环              │
│ (我的设计) │    │  (Hermes机制)      │
└──────┬──────┘    └────────┬────────────┘
       ↓                    ↓
  更新prompt版本        更新/创建技能文件
       └──────────┬─────────┘
                  ↓
            下次调用更好

关键点是中间那层:执行轨迹是两个优化循环共享的原材料,不是分开收集两份数据。


两个循环的触发条件和职责边界

这是结合时最容易搞混的地方,必须设计清楚。

prompt优化循环触发条件:同一任务类型,最近N次调用的Judge评分出现系统性问题,或者失败模式在积累。它只改prompt文本,不改技能文件的结构。

技能更新循环触发条件:任务完成后(尤其是复杂任务),发现执行路径里有可以封装的工作流程。或者技能在使用中被发现有错误/过时的内容。它改的是技能文件本身——包括但不限于其中存储的prompt。

一个具体例子:

技能文件(skill: contract-review)
├── 工作流程描述:先看甲乙方 → 再看金额条款 → 再看违约责任
├── 当前最优prompt:v4(由prompt优化循环维护)
├── 动态示例库:18个已标注案例
└── 已知边界情况:跨境合同需要特别关注管辖权条款

prompt优化循环负责:把v4升级到v5,改的是prompt文本
技能更新循环负责:发现跨境合同是个边界情况,
                  把这条知识写进技能文件的"已知边界情况"

信息流的具体设计

执行轨迹的结构

两个循环都需要的共享数据格式:

{
  "call_id": "uuid",
  "skill_id": "contract-review",   # 用了哪个技能
  "prompt_version": "v4",
  
  # prompt优化循环需要的
  "input": "...",
  "output": "...",
  "judge": {
    "score": 3.8,
    "failures": [{"dimension": "completeness", "problem": "..."}]
  },
  "user_signal": {"adopted": true, "edited": false},
  
  # 技能更新循环需要的
  "reasoning_trace": [
    {"step": 1, "thought": "识别合同类型", "result": "NDA"},
    {"step": 2, "thought": "扫描金额条款", "result": "未找到"},
    {"step": 3, "thought": "这是NDA不涉及金额", "result": "跳过"}
  ],
  "task_complexity": 7,           # 工具调用次数
  "novel_pattern_detected": true, # 这次任务走了不寻常的路径
  "session_id": "...",
  "timestamp": "..."
}

技能文件的完整结构

技能文件要同时服务于两个循环,所以结构要包含两层信息:

---
skill_id: contract-review
version: 1.3
created: 2026-01-10
last_updated: 2026-04-28
prompt_version: v4         ← 指向prompt优化循环维护的版本
total_uses: 847
avg_judge_score: 4.1
---

# 技能:合同风险审查

## 工作流程
(由技能更新循环维护——什么情况下走什么路径)
1. 识别合同类型(NDA / 采购合同 / 服务合同 / 劳动合同)
2. 按类型加载对应的审查重点
3. 扫描金额、期限、违约、管辖权四个核心维度
4. 生成分级风险报告

## 已知边界情况
(技能在使用中积累的特殊情况处理方式)
- 跨境合同:特别关注管辖权条款和适用法律
- 框架协议:不单独看金额,看累计额度上限
- 补充协议:必须关联主合同,单独看可能误判

## 当前prompt(由prompt优化循环维护)
→ 见 prompts/contract-review/v4.md

## 动态示例库
→ 见 examples/contract-review/(18个标注案例)

## 优化历史
| 版本 | 日期 | 平均分 | 主要改进 |
|------|------|--------|----------|
| v1   | 01-10 | 3.2  | 初始版本 |
| v2   | 02-15 | 3.6  | 修复格式不合规 |
| v3   | 03-20 | 3.9  | 补充边界情况描述 |
| v4   | 04-28 | 4.1  | 针对跨境合同优化 |

新任务类型的冷启动

一个全新任务类型出现时,技能库里没有对应技能,两个循环都还没有可用数据。这是结合架构里最容易有空档的地方。

处理方式:

def handle_new_task_type(user_input, task_type):
    # 第一步:检索最相似的已有技能作为起点
    similar_skills = skill_library.search(task_type, top_k=2)
    
    if similar_skills:
        # 基于相似技能的prompt做适配,不是从零开始
        bootstrap_prompt = adapt_prompt(
            base=similar_skills[0].prompt,
            new_task_description=task_type
        )
    else:
        bootstrap_prompt = DEFAULT_PROMPT
    
    # 第二步:用通用prompt执行,但标记为"新任务类型"
    result = call_llm(bootstrap_prompt, user_input)
    log(task_type=task_type, is_new=True, ...)
    
    # 第三步:降低技能创建门槛
    # 新任务类型:3次成功调用后就可以创建技能
    # (正常门槛是5次复杂任务)
    if new_task_call_count(task_type) >= 3:
        create_skill_from_traces(task_type)

一个反直觉的设计决策

两个循环的更新频率应该不同,而且技能更新循环应该比prompt优化循环慢。

prompt优化循环可以相对频繁——积累了200-300个失败案例就可以触发一次,因为它改的只是文本表达,影响范围有限,可以用A/B测试安全验证。

技能更新循环应该更保守——技能文件是系统的"长期记忆",改错了影响所有后续使用这个技能的调用。每次更新都要有强信号支持(多次会话证实,而不是一次偶发情况),而且更新完需要在历史数据上做全量回测,不是只看新数据。

OPTIMIZATION_THRESHOLDS = {
    "prompt_update": {
        "min_failures": 200,        # 失败案例积累
        "check_frequency": "daily",
        "validation": "ab_test_100_calls"
    },
    "skill_update": {
        "min_confirmations": 5,     # 同一个新发现在多次会话中确认
        "check_frequency": "weekly",
        "validation": "full_backtest_500_calls",
        "requires_human_review": True  # 借鉴Hermes GEPA的PR机制
    }
}

最后一条借鉴了Hermes GEPA的核心原则:所有技能变更通过PR,人工review,不直接commit。这个约束不是繁文缛节,而是防止系统悄悄进化出奇怪行为的关键安全阀——awesome-hermes的社区总结是"诀窍不是进化更快,而是进化时不悄悄变奇怪"。