单agent落幕,双agent才能解决复杂问题!附LangGraph+Milvus实操
单Agent落幕,双Agent才能解决复杂问题!附LangGraph+Milvus实操
做Agent简单,但做能落地的Agent难,而做能落地的长周期Agent,更是难上加难。这恐怕是不少搞Agent开发的朋友,在日常工作中最真实的感受了。
仔细复盘那些长周期Agent落地失败的情况,其实可以归纳为两种典型模式:
第一种,往往发生在任务刚开始的阶段。比如接到一个“搭建类Claude.ai的Web应用”这样的高阶指令,Agent采取贪心策略,想一次性把全量开发搞定。结果呢?执行到一半,上下文窗口被耗尽,上下文逐步丢失,整个功能开发半途而废。这时候再开一个新实例,面对的只有一个没有进度文档的半成品代码库,需要耗费巨大精力去梳理和复盘,才能勉强恢复应用的基础功能。
第二种,则出现在项目中后期。部分功能已经落地,但新启动的Agent因为缺乏全局目标的认知,只看到局部功能可用,便过早地判定任务完成,完全忽略了大量尚未实现的功能模块。
表面上看,这两种失效模式分别发生在项目的不同阶段,表现形式也截然不同。但深挖其本质,核心原因其实是一致的:
Agent无法准确确认任务切分的粒度,导致多个会话间的上下文传递出现断裂或偏差。
既然找到了病根,那该如何对症下药呢?接下来,我们将从架构设计、状态恢复、功能验证三个核心维度,详细拆解如何通过双Agent架构、基于文件与向量数据库的状态恢复机制,以及测试驱动的功能验证,系统性破解长周期Agent的落地难题。
01 为什么需要两个Agent?
要解决任务切分粒度不准和上下文传递失效的问题,首先得从架构层面搭建一套清晰的分工机制。
不久前,Anthropic发布了一套双Agent架构方案,它由两个Agent组成:
Initializer Agent + Coding Agent。
其中,
Initializer Agent
- 把用户的高级需求,精准地展开为一个个具体的、可验证的功能点。就拿Claude.ai克隆项目来说,Initializer Agent不会停留在“构建一个对话应用”这种模糊表述上,而是会将其细化为200多项具体功能。每一项都清晰描述了用户可执行的具体操作和对应的预期结果,让后续开发有明确的执行标准。
任务拆解:
- 通过创建进度追踪文件,实时记录每个功能的完成状态,让开发进度可视化,避免出现“局部完成,就判定整体结束”的误判。
进度跟踪:
- 编写初始化脚本(init.sh),提前配置好开发所需的基础环境,确保后续Coding Agent能快速启动开发,无需在环境配置上耗费额外精力。
环境搭建:
与Initializer Agent的统筹角色相对应,Coding Agent的职责是充当“专家”,在Initializer Agent的指挥下,聚焦具体功能的落地执行。
其核心工作逻辑是
增量迭代
需要特别注意的是,进度追踪列表的格式选择,对Agent的执行效率影响极大。这里更推荐采用
JSON格式
一个标准的功能项JSON结构如下:
{
"category": "functional",
"description": "New chat button creates a fresh conversation",
"steps": [
"Na vigate to main interface",
"Click the 'New Chat' button",
"Verify a new conversation is created",
"Check that chat area shows welcome state",
"Verify conversation appears in sidebar"
],
"passes": false
}这种结构化格式,能让Agent清晰地理解每个功能的范围和验证标准,同时通过passes字段追踪完成状态,为跨会话的进度衔接提供可靠依据。
02 从文本匹配到语义检索:Agent记忆的升级
在解决了任务切分和增量开发的问题后,另一个影响长周期Agent落地的关键痛点浮现出来:
跨会话的状态恢复与经验复用。
起初,基于进度文件与Git的状态恢复机制在项目初期还能正常运行。但随着项目规模扩大,三类问题会逐渐凸显:一是Git历史提交次数超过数百次后,线性扫描的效率大幅下降,无法快速定位关键信息;二是基于文本的查找只能匹配关键词,无法理解“JWT刷新令牌”与“用户认证”这类语义关联,导致历史经验无法有效复用;三是各项目的经验被孤立存储,无法跨项目迁移复用,增加了重复开发成本。
向量数据库通过语义检索解决了这些问题。其核心逻辑是将进度记录、Git提交信息等历史数据转换为向量嵌入。当Agent需要查询历史信息时,只需输入查询语句,向量数据库就能在毫秒级响应时间内找到语义相关的内容。
在这一场景中,**Milvus**数据库的优势尤为突出。一方面,Milvus Lite支持轻量化部署,可像SQLite一样直接嵌入应用,无需复杂的集群配置;另一方面,你也可以采用Milvus的Docker、K8s版本,它们都原生支持主流的嵌入模型API。通过TextEmbedding功能,Agent无需手动处理向量生成,直接用自然语言查询语句就能检索历史记录,大幅降低了使用门槛。
03 代码跑通≠功能完成:测试驱动的功能验证闭环
即便解决了任务切分和记忆复用问题,长周期Agent仍可能因功能验证不充分导致落地失效。
很多时候,Agent在实现功能后,仅通过简单的单元测试或curl命令测试就标记功能完成。但这类测试往往无法覆盖真实使用场景,很容易遗漏实际应用中的问题——这一痛点在Web应用开发中尤为明显。
要解决这一问题,可以让Agent使用浏览器自动化工具进行
端到端测试
这种测试方式让Agent能够发现仅从代码层面无法察觉的问题。比如一个对话功能,代码逻辑可能完全正确,API返回也符合预期,但在浏览器中可能因为CSS问题导致用户看不到回复内容。通过浏览器自动化测试,Agent可以截图验证页面显示是否符合预期,从而发现这类问题。
但这种方法也有局限性。某些浏览器原生功能,特别是系统级弹窗(如确认对话框、文件选择器),无法被Puppeteer这样的自动化工具访问。依赖这些原生弹窗的功能往往更容易出现测试盲区。在设计应用时需要考虑这一点,尽量使用可测试的自定义UI组件替代原生弹窗。
04 动手实操:LangGraph管状态,Milvus管记忆
为了让上述方案更容易理解,下面通过一个实操示例,展示如何通过LangGraph和Milvus的协同,实现长周期任务中双Agent的状态管理与记忆复用。
该示例的核心逻辑是“短期记忆+长期记忆”的协同工作:LangGraph的检查点机制负责维护单个会话内的状态一致性,确保会话内的开发流程不中断;当需要跨会话查询历史信息时,由Milvus提供语义检索支持,帮助Agent快速恢复上下文,彻底避免从零开始的冷启动问题。
from sentence_transformers import SentenceTransformer
from pymilvus import MilvusClient
from langgraph.checkpoint.memory import MemorySa ver
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator
import subprocess
import json
# ==================== 初始化 ====================
embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
milvus_client = MilvusClient("./milvus_agent_memory.db")
# 创建集合
if not milvus_client.has_collection("agent_history"):
milvus_client.create_collection(
collection_name="agent_history",
dimension=384,
auto_id=True
)
# ==================== Milvus操作函数 ====================
def retrieve_context(query: str, top_k: int = 3):
"""从Milvus检索相关历史(核心要素:语义检索)"""
query_vec = embedding_model.encode(query).tolist()
results = milvus_client.search(
collection_name="agent_history",
data=[query_vec],
limit=top_k,
output_fields=["content"]
)
if results and results[0]:
return [hit["entity"]["content"] for hit in results[0]]
return []
def sa ve_progress(content: str):
"""保存进度到Milvus(长期记忆)"""
embedding = embedding_model.encode(content).tolist()
milvus_client.insert(
collection_name="agent_history",
data=[{"vector": embedding, "content": content}]
)
# ==================== 核心要素1:Git提交 ====================
def git_commit(message: str):
"""Git提交(原文核心要素)"""
try:
# 实际项目中会执行真实的Git命令
# subprocess.run(["git", "add", "."], check=True)
# subprocess.run(["git", "commit", "-m", message], check=True)
print(f"[Git提交] {message}")
return True
except Exception as e:
print(f"[Git提交失败] {e}")
return False
# ==================== 核心要素2:测试验证 ====================
def run_tests(feature: str):
"""执行测试(原文强调的端到端测试)"""
try:
# 实际项目中会调用Puppeteer等测试工具
# 这里简化为模拟测试
print(f"[测试验证] 正在测试功能: {feature}")
# 模拟测试结果
test_passed = True # 实际会根据测试结果返回
if test_passed:
print(f"[测试通过] {feature}")
return test_passed
except Exception as e:
print(f"[测试失败] {e}")
return False
# ==================== 状态定义 ====================
class AgentState(TypedDict):
messages: Annotated[list, operator.add]
features: list # 所有功能列表
completed_features: list # 已完成功能
current_feature: str # 当前正在处理的功能
session_count: int # 会话计数器
# ==================== 双Agent节点 ====================
def initialize_node(state: AgentState):
"""初始化Agent:生成功能列表和工作环境"""
print("\n========== 初始化Agent启动 ==========")
# 生成功能列表(实际会根据需求生成详细的功能清单)
features = [
"实现用户注册功能",
"实现用户登录功能",
"实现密码重置功能",
"实现用户资料编辑",
"实现会话管理功能"
]
# 保存初始化信息到Milvus
init_summary = f"项目初始化完成,生成{len(features)}个功能点"
sa ve_progress(init_summary)
print(f"[初始化完成] 功能列表: {features}")
return {
**state,
"features": features,
"completed_features": [],
"current_feature": features[0] if features else "",
"session_count": 0,
"messages": [init_summary]
}
def code_node(state: AgentState):
"""编码Agent:实现、测试、提交(核心循环节点)"""
print(f"\n========== 编码Agent会话 #{state['session_count'] + 1} ==========")
current_feature = state["current_feature"]
print(f"[当前任务] {current_feature}")
# ===== 核心要素3:从Milvus检索历史经验(跨会话记忆) =====
print(f"[检索历史] 查询与'{current_feature}' 相关的历史经验...")
context = retrieve_context(current_feature)
if context:
print(f"[检索结果] 找到 {len(context)} 条相关记录:")
for i, ctx in enumerate(context, 1):
print(f" {i}. {ctx[:60]}...")
else:
print("[检索结果] 无相关历史记录(首次实现此类功能)")
# ===== 步骤1:实现功能 =====
print(f"[开始实现] {current_feature}")
# 实际会调用LLM生成代码
implementation_result = f"已实现功能: {current_feature}"
# ===== 步骤2:测试验证(核心要素) =====
test_passed = run_tests(current_feature)
if not test_passed:
print(f"[会话结束] 测试未通过,需要修复")
return state # 测试失败则不推进
# ===== 步骤3:Git提交(核心要素) =====
commit_message = f"feat: {current_feature}"
git_commit(commit_message)
# ===== 步骤4:更新进度文件 =====
print(f"[更新进度] 标记功能为已完成")
# ===== 步骤5:保存到Milvus长期记忆 =====
progress_record = f"完成功能: {current_feature} | 提交信息: {commit_message} | 测试状态: 通过"
sa ve_progress(progress_record)
# ===== 步骤6:更新状态并准备下一个功能 =====
new_completed = state["completed_features"] + [current_feature]
remaining_features = [f for f in state["features"] if f not in new_completed]
print(f"[进度统计] 已完成: {len(new_completed)}/{len(state['features'])}")
# ===== 核心要素4:会话结束(明确的会话边界) =====
print(f"[会话结束] 代码库处于干净状态,可安全中断\n")
return {
**state,
"completed_features": new_completed,
"current_feature": remaining_features[0] if remaining_features else "",
"session_count": state["session_count"] + 1,
"messages": [implementation_result]
}
# ==================== 核心要素3:循环控制 ====================
def should_continue(state: AgentState):
"""判断是否继续下一个功能(实现增量循环开发)"""
if state["current_feature"] and state["current_feature"] != "":
return "code" # 继续执行下一个功能
else:
print("\n========== 所有功能已完成 ==========")
return END
# ==================== 构建工作流 ====================
workflow = StateGraph(AgentState)
# 添加节点
workflow.add_node("initialize", initialize_node)
workflow.add_node("code", code_node)
# 添加边
workflow.add_edge(START, "initialize")
workflow.add_edge("initialize", "code")
# 添加条件边(实现循环)
workflow.add_conditional_edges(
"code",
should_continue,
{
"code": "code", # 继续循环
END: END # 结束
}
)
# 编译工作流(使用MemorySa ver作为检查点器)
app = workflow.compile(checkpointer=MemorySa ver())
# ==================== 使用示例:演示跨会话恢复 ====================
if __name__ == "__main__":
print("=" * 60)
print("演示场景:长周期Agent的多会话开发")
print("=" * 60)
# ===== 会话1:初始化 + 完成前2个功能 =====
print("\n【场景1】第一次启动:完成前2个功能")
config = {"configurable": {"thread_id": "project_001"}}
result = app.invoke({
"messages": [],
"features": [],
"completed_features": [],
"current_feature": "",
"session_count": 0
}, config)
print("\n" + "=" * 60)
print("【模拟场景】开发者手动中断(Ctrl+C)或上下文窗口耗尽")
print("=" * 60)
# ===== 会话2:从checkpoint恢复状态 =====
print("\n【场景2】新会话启动:从上次中断处继续")
print("通过相同的thread_id,LangGraph自动恢复checkpoint...")
# 使用相同的thread_id,LangGraph会自动从checkpoint恢复状态
result = app.invoke({
"messages": [],
"features": [],
"completed_features": [],
"current_feature": "",
"session_count": 0
}, config)
print("\n" + "=" * 60)
print("演示完成!")
print("=" * 60)
print("\n关键要点:")
print("1. ✅ 双Agent架构(initialize + code)")
print("2. ✅ 增量循环开发(条件边控制循环)")
print("3. ✅ Git提交(每个功能完成后提交)")
print("4. ✅ 测试验证(端到端测试)")
print("5. ✅ 会话管理(明确的会话边界)")
print("6. ✅ 跨会话恢复(thread_id + checkpoint)")
print("7. ✅ 语义检索(Milvus长期记忆)")尾声
此前,市面上的Agent方案多采用单一Coding Agent处理所有编码相关任务。但在真实的软件开发流程中,不同阶段的需求差异极大。
因此,未来的趋势是
多Agent专业分工
值得注意的是,这套方案的核心原则——结构化初始环境、增量式进展、状态追踪、验证测试——并非仅适用于软件开发场景,完全可以迁移到其他长周期任务中:例如科学研究中的数周连续实验、金融建模中的多步骤复杂计算、法律文档的多轮迭代审查等。
随着向量数据库技术的成熟和多Agent框架的完善,让AI可靠地完成复杂长周期任务,已经从理论探索逐步进入工程实践阶段,未来将释放出更多的生产力价值。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名