摘要 :本文介绍如何在 LangGraph 中实现 Learning(学习)与 Adaptation(自适应)。案例介绍 :配套 demo 实现一个可反复调教的「自适应技术助手」------用户在命令行提问(如「什么是 LangGraph?」),用自然语言反馈(如「太啰嗦了,简单点」),助手逐轮调整风格。提供 main.py 交互式命令行与 main.ipynb 四轮非交互式演示;演示中同一问题 + 同一反馈多轮迭代后,回答字数从 3500+ 降至约 1200,风格从冗长专业变为简短口语。技术要点:用户反馈由 LLM 解析为各维度调整量(±1),偏好持久化到 JSON;四个维度(detail_level、tone、technical_depth、structure)各 0--10,动态构造 system prompt。不依赖 PPO / DPO,仅用「偏好 + 图结构 + 持久化 + LLM 反馈解析」即可跑通最小闭环,并与 SICA、AlphaEvolve 等高级系统在方法论上对齐。
关键词:Learning & Adaptation;LangGraph;自适应;UserPreference;自然语言反馈;feedback_parser;长期记忆;system prompt;自适应技术助手
源代码链接:Transformer 12. Learning and Adaptation 案例源代码
1. 为什么在 Agent 中要谈「学习与自适应」?
先用一个生活里的画面来感受这个概念。
想象一下,你请一个实习生帮忙写技术总结。第一天,他照本宣科地把 API 文档翻译成白话;你觉得太啰嗦,就提醒他「以后简洁一点」。几天之后,你会发现他真的开始控制篇幅、突出重点------这就是最朴素的 Learning & Adaptation:
- Learning(学习):从你的反馈中抽象出「什么是好答案」;
- Adaptation(自适应):下次写作时,主动调整风格和策略。
对智能体(Agent)来说,道理是一样的:
如果一个 Agent 每次回答都只依赖一段固定的 system prompt,而不会因为用户反馈、历史数据、环境变化去更新自己的「习惯」,那它本质上还是一个「高级脚本」,而不是一个真正会成长的智能体。
围绕 Learning & Adaptation 这一主题,我们可以从几个经典视角来理解「Agent 如何变聪明」:
-
强化学习(Reinforcement Learning) :
想象一个玩游戏的小孩,他一开始乱按按键,偶尔赢了几局,就会慢慢记住「哪些操作更容易赢」。
对 Agent 来说,就是在环境中尝试动作(Action),根据结果拿到奖励(Reward)或惩罚,逐步调整策略(Policy)。
这类方法适合:
- 机器人控制、游戏 AI 等 需要长期规划、连续决策 的场景;
- 用 PPO 此类算法稳定更新策略,避免「一步走错,经验全毁」。
-
在线学习(Online Learning) :
如果说「离线训练」像是每年一次体检,那么在线学习就更像是「每天戴着手环监控身体状态,随时调整作息」。
Agent 持续接收新的数据流(如日志、行情、传感器数据),边用边学,使得:
- 模型参数或内部策略随着时间不断更新;
- 能及时适应分布漂移(数据统计规律随时间变化)。
在推荐系统、风控、行情预测、运维监测等场景里,这是非常关键的能力。
-
记忆型学习(Memory-Based Learning) :
想象你和一个朋友聊天,如果他总是记得你之前提过的兴趣、忌讳、偏好,你会觉得「他很懂你」。
对 Agent 来说,「记忆」也是一种非常实用的 Learning 机制:
- 短期记忆:当前对话轮次及临时变量;
- 中期记忆:最近几天 / 几十次交互的摘要;
- 长期记忆:高度压缩的用户画像、重要事实、偏好。
这些信息不一定体现在模型参数里,但会 影响每次推理前的「上下文」和「工具选择」,从而展现出「根据经验做决策」的行为。
-
基于 LLM 的 Few-shot / Zero-shot 学习 :
这是近几年 Agent 最常用的一种「软学习」方式:
- 通过几条示例(Few-shot)或清晰的说明(Zero-shot),就让模型在新任务上表现得不错;
- 如果把「示例 + 指令」持续记录、整理,再喂回给后续调用,本质上也形成了一种「上下文级别」的学习和适应。
这些方法从不同角度说明了同一件事:
一个强大的 Agent,一定要能根据环境和反馈不断更新自己的内部状态,而不是永远停留在「一次性 prompt」的水平。
接下来,我们会用一个 可运行的 LangGraph 示例,把「学习与自适应」具体化成一个你可以反复调教的「自适应技术助手」,并在后文中再类比到更复杂的系统(比如 SICA、AlphaEvolve)。
2. 示例设定:一个会被你「调教」的技术助手
我们希望构建这样一个极简场景:
- 你在命令行里向助手提问(围绕 LLM / LangGraph / Agent 等技术话题);
- 助手给出回答后,你可以用自然语言 表达反馈,例如:
- 「太啰嗦了,简单点」;
- 「正式一些」;
- 「通俗一点,别用那么多术语」;
- 「分点说,列个清单」;
- 系统通过 LLM 解析 你的反馈,得到各维度的调整量(±1),并更新长期偏好;
- 多轮下来,助手会逐渐学会在四个维度上微调:
- detail_level:更简洁还是更详细;
- tone:更口语还是更正式;
- technical_depth:更通俗还是更专业;
- structure:更自由段落还是更分点编号。
也就是说,我们不在这里实现完整的 PPO / DPO,而是用一个 「偏好 + 图结构 + 持久化 + LLM 反馈解析」 的小例子,把 Learning & Adaptation 的「骨架」跑通:
- 接收反馈(自然语言)------更贴近真实场景,用户无需打分;
- 用 LLM 将反馈解析为各维度的调整量(-1 / 0 / +1);
- 把偏好写入 JSON 文件,每个维度 0--10 连续可调;
- 下次调用前,从长期记忆中读取最新偏好,动态调整 system prompt;
- 形成一个「不断更新内部状态 → 改变后续行为」的闭环。
3. 状态与图结构:用 LangGraph 表达「学习过程」
在代码中,我们用一个 AgentState 来描述图中流转的状态(见 adaptive_agent_graph.py):
python
class AgentState(TypedDict):
user_id: str # 用户标识
question: str # 当前问题
messages: List[str] # 简单对话历史
preference: UserPreference # 用户长期偏好(4 维度,各 0-10)
answer: str # 本轮回答
feedback_text: str | None # 用户自然语言反馈(可选)
对应的 LangGraph 结构非常简单:
text
START
↓
load_preference # 载入 user_id 对应的长期偏好
↓
answer_question # 按偏好构造 system prompt,调用 LLM 生成回答
↓
update_preference # 根据用户自然语言反馈,用 LLM 解析后更新偏好
↓
END
在 build_adaptive_agent() 中,我们用 LangGraph 把这条链路串起来:
python
def build_adaptive_agent() -> StateGraph:
graph = StateGraph(AgentState)
graph.add_node("load_preference", load_preference_node)
graph.add_node("answer_question", answer_question_node)
graph.add_node("update_preference", update_preference_node)
graph.add_edge(START, "load_preference")
graph.add_edge("load_preference", "answer_question")
graph.add_edge("answer_question", "update_preference")
graph.add_edge("update_preference", END)
return graph
这里有三个关键信息:
- 状态是显式建模的:你能清楚看到有哪些字段会被更新;
- 节点是纯函数 :输入
AgentState,输出新的AgentState,便于测试与复用; - 学习过程是图结构的一部分 :
update_preference节点就是「学习」的载体。
4. 长期记忆:把偏好写进一个简单的 JSON
Learning & Adaptation 的一个核心思想是:不要把所有「内部状态」都塞在 LLM 的上下文里,更适合用专门的数据结构持久化。
在 adaptive_memory.py 中,我们用一个极简的 JSON 文件来模拟「长期偏好」:
- 文件路径:
memory_files/adaptive_preferences.json; - 每个
user_id对应一个UserPreference,四个维度均为 0--10 的整数 :detail_level:0=极简,5=平衡,10=极详;tone:0=极口语,5=平衡,10=极正式;technical_depth:0=大白话,5=平衡,10=专业术语;structure:0=自由段落,5=平衡,10=分点编号。
adaptive_preferences.json 里的内容初始化状态下可以是:
json
{
"notebook_demo_user": {
"user_id": "notebook_demo_user",
"detail_level": 6,
"tone": 6,
"technical_depth": 6,
"structure": 6
}
}
加载偏好的逻辑大致如下:
python
def load_user_preference(user_id: str) -> UserPreference:
prefs = _load_all_preferences()
if user_id in prefs:
return prefs[user_id]
pref = UserPreference(user_id=user_id)
prefs[user_id] = pref
_save_all_preferences(prefs)
return pref
而根据自然语言反馈 更新偏好的逻辑,则借助 feedback_parser.py 中的 LLM 解析:
python
def update_preference_from_feedback(user_id: str, feedback_text: str) -> UserPreference:
deltas = parse_feedback_to_deltas(feedback_text) # LLM 解析为 {"detail_level": -1, ...}
pref = load_user_preference(user_id)
pref.detail_level += deltas.get("detail_level", 0)
pref.tone += deltas.get("tone", 0)
pref.technical_depth += deltas.get("technical_depth", 0)
pref.structure += deltas.get("structure", 0)
pref.clamp() # 限制在 0-10
_save_all_preferences(prefs)
return pref
你可以把这段逻辑理解成:
- 用户说「太啰嗦了,简单点」→ LLM 解析为
detail_level: -1; - 每次反馈各维度最多 ±1,多次迭代后自然收敛到你满意的组合;
- 真实场景下用户更习惯用语言表达,而非打 1--5 分。
5. 自适应的 system prompt:让风格真正「用起来」
有了长期偏好,还需要在实际调用 LLM 时用上它。在 adaptive_agent_graph.py 里,我们通过 _build_system_prompt() 把四个维度(0--10)映射成具体的提示词:
python
def _value_to_detail_instruction(v: int) -> str:
if v <= 2: return "尽量用 1-2 句话回答,只保留关键结论。"
if v <= 4: return "回答简短,适度展开。"
if v <= 6: return "在保证结论清晰的前提下,适度展开原理与示例。"
if v <= 8: return "给出分步骤、较详细的推理过程与示例。"
return "给出分步骤、非常详细的推理过程与多个示例。"
def _value_to_tone_instruction(v: int) -> str:
if v <= 2: return "语气非常口语、轻松。"
if v <= 4: return "语气可以更口语、轻松一些。"
if v <= 6: return "语气适中。"
if v <= 8: return "整体语气保持专业、正式。"
return "整体语气非常专业、正式,适合技术文档。"
# technical_depth、structure 同理,最终拼成完整 system prompt
answer_question_node 节点则会在调用 LLM 时,使用这个 system prompt:
python
def answer_question_node(state: AgentState) -> AgentState:
"""使用当前偏好,调用 LLM 回答问题。"""
pref = state["preference"]
system_prompt = _build_system_prompt(pref)
messages = [
{"role": "system", "content": system_prompt},
{
"role": "user",
"content": (
"请用 LangGraph 的视角回答问题,并尽量结合图结构、节点、状态的概念。\n"
f"问题:{state['question']}"
),
},
]
completion = _client.chat.completions.create(
model=_llm_config.model,
messages=messages,
)
answer = completion.choices[0].message.content or ""
history = list(state.get("messages", []))
history.append(f"Q: {state['question']}")
history.append(f"A: {answer}")
state["answer"] = answer
state["messages"] = history
return state
python
def _build_system_prompt(pref: UserPreference) -> str:
"""
根据长期偏好(4 个维度 0-10)动态构造 system prompt。
"""
parts = [
"你是一名资深 AI 技术顾问,擅长用中文解释大模型与 LangGraph。",
_value_to_detail_instruction(pref.detail_level),
_value_to_tone_instruction(pref.tone),
_value_to_technical_instruction(pref.technical_depth),
_value_to_structure_instruction(pref.structure),
]
return " ".join(parts)
这样,每次图执行时,都会「读取最新偏好 → 构造对应风格的 system prompt → 生成回答」。
学习发生在偏好上,自适应体现在 prompt 上。
6. 交互入口:用命令行感受「越聊越懂你」
在 main.py 中,我们用一个简单的命令行循环包装了整个图:
python
def run_cli_session(user_id: str = "demo_user") -> None:
graph = build_adaptive_agent().compile()
history: list[str] = []
while True:
question = input("\n你的问题:").strip()
if question.lower() in {"exit", "quit"}:
break
state: AgentState = {
"user_id": user_id,
"question": question,
"messages": history,
"preference": None,
"answer": "",
"feedback_text": None,
} # type: ignore[assignment]
result = graph.invoke(state)
answer = result["answer"]
history = result["messages"]
print("\n助手回答:")
print(answer)
feedback_text = input("\n对这次回答有什么反馈?(直接回车跳过):").strip()
if feedback_text:
feedback_state: AgentState = {
"user_id": user_id,
"question": question,
"messages": history,
"preference": result["preference"],
"answer": answer,
"feedback_text": feedback_text,
}
_ = graph.invoke(feedback_state)
print("已根据反馈更新长期偏好。")
运行体验建议如下:
- 第一次运行时,提一个相同类型的问题,例如「什么是 LangGraph,它和普通 LangChain 有什么区别?」;
- 如果你觉得回答太啰嗦,可以说「太啰嗦了,简单点」;
- 再问一次类似的问题,你会看到回答逐渐变短;
- 你也可以试试「正式一些」「通俗一点」「分点说」等不同维度的反馈。
Notebook 演示 :main.ipynb 提供了非交互式演示,模拟「同一问题 + 同一反馈」执行两轮:第一轮回答 → 反馈「太啰嗦了,简单点」→ 第二轮回答 → 再次反馈 → 第四轮回答。可直观看到 detail_level 连续下调、回答逐渐变短。
这就是一个最小可感知的 Learning & Adaptation 闭环。
我们在 main.ipynb 中先问了一个问题:什么是 LangGraph,它和普通 LangChain 有什么区别?。
初始化情况下,UserPreference 的四个维度均为 6 (detail=6, tone=6, technical=6, structure=6)。第一轮回答如下:"从LangGraph 的视角来看,它本质上是一个基于有向图(Directed Graph)建模的、状态驱动的 LLM 应用编排框架"... 一共3525个字符;
我们回复:"太啰嗦了,简单点。而且你说的太专业了,我听不懂。"
UserPreference 的四个维度更新为:detail=5, tone=5, technical=5, structure=6。大模型回答:"从 LangGraph 的视角来看,它本质上是一个基于有向图(Directed Graph)建模的、状态驱动的 LLM 应用编排框架"... 一共3109个字符;看起来好像和第一轮回复差别不大。
我们再一次回复:"太啰嗦了,简单点。而且你说的太专业了,我听不懂。"
UserPreference 的四个维度更新为:detail=4, tone=4, technical=4, structure=6。大模型回答:
好嘞~咱们用「图」的视角来聊聊 LangGraph 👇
🔹 **LangGraph 是什么?**
它本质上是一个**基于有向图(Directed Graph)的编排框架**,专为构建**状态驱动、可循环、带条件分支的 LLM 应用**而生。
核心三要素(对应图论结构):
...
一共1187个字符。现在我们可以看到,这次的回复和上两次明显字数缩短了,文字变得更加口语化了。
7. 与 SICA / AlphaEvolve 等高级系统的类比
像 SICA、AlphaEvolve、OpenEvolve 这样的系统,做的事情要复杂得多,但可以用同一套思维去理解:
- 状态更复杂:不只是「detail_level / tone」,而是包含完整的代码版本、性能指标、评测日志;
- 反馈更丰富:不只是 1--5 分,而是自动评测脚本、基准测试、人工偏好数据等;
- 搜索策略更高级:不只是「切换几种风格」,而是使用进化算法、自我改写、甚至调用多个子 agent 协同探索。
但从 LangGraph 的视角看,它们仍然可以抽象成:
- 有一个不断被更新的「世界状态」(代码、参数、工具集合、偏好等);
- 有一条或多条图,负责根据当前状态执行动作、收集反馈;
- 有专门的节点 / 子图,负责根据反馈更新世界状态;
- 整个系统通过「图 + 状态」的组合,不断在状态空间中进行搜索和优化。
你可以把本节的示例视为这些复杂系统的「玩具版本」:
我们只在 very small 的状态空间(回答风格)里做「搜索 / 适应」,但架构思路是一致的。
8. 小结:如何在自己的 Agent 项目中落地 Learning & Adaptation
如果你想在自己的 Agent 项目中引入 Learning & Adaptation,可以参考本节示例,从最小可行单元做起:
-
第一步:显式建模状态
把「会被更新」的东西(偏好、配置、重要的中间结果)放进 LangGraph 的状态,而不是全部交给 prompt。
-
第二步:专门的「学习节点」
为「根据反馈更新状态」写一个单独的节点 / 子图,哪怕逻辑一开始非常简单。
-
第三步:持久化长期记忆
先用 JSON / SQLite / 向量库等简单方式把「关键状态」写到磁盘上,让系统真正拥有跨会话的记忆。
-
第四步:从「调整风格」开始
不一定一上来就做 PPO / DPO,可以像本示例一样,先从「根据偏好调整回答风格 / 展示方式」开始,逐步演进到更复杂的策略和算法。
当你把这四步跑通后,再回头看 SICA、AlphaEvolve,就会发现:
它们做的是更大规模、更高维度的 Learning & Adaptation,但方法论高度相似。