简要

在这一系列文章中,我们将循序渐进地阐述如何从零开始,构建一个融合对话助手、代码代理和任务代理的统一大模型应用平台。阅读完毕后,您将对“大模型是什么、如何预训练、如何对齐成为ChatGPT、如何产品化部署、如何做检索增强生成(RAG)与智能Agent、如何实现代码助手,以及如何搭建整个平台并做好上线治理”形成深刻且可实践的整体理解。

全景路线图:

之前的章节参考我之前写的文章;

J. 检索增强生成(RAG)篇:让大模型连接知识库 – 全流程讲解如何将向量检索融入大模型应用:文档切分、向量化、检索、混合检索与重排、答案生成与引用,确保答案与证据一致,并讨论RAG系统的评测方法。
K. LangChain篇:大模型应用的工程化抽象 – 学习使用LangChain等框架,将提示(Prompt)、链(Chain)、工具(Tool)、检索器(Retriever)等进行模块化封装,实现复杂任务的链式调用、结构化输出、可观测性、回归测试和灰度发布。
L. 智能Agent篇:类似Manus的任务代理 – 深入解析智能体的工作模式:如何让模型进行任务规划-执行-反馈-反思循环;如何设计安全可靠的工具使用机制(权限控制、幂等性、沙箱、安全审计回放);探索多智能体协作以及评测体系。

👉了解更多

J. 检索增强生成 (RAG) 篇:让大模型学会引用知识

1. 切分文档:处理知识库长文的第一步

RAG系统通常面对大量资料(维基百科、公司文档等)。直接把所有知识塞进LLM上下文不现实,因此第一步是将知识库预处理:

文档切分:把长文档拆成小段片段(chunk)。每个chunk可能是几百字的一段,或一页文本,具体长度可根据模型上下文容量设定(常见200-500字)。切分要尽量语义完整:避免硬切断句子,使每片段包含一个相对完整的语义单元。可以按段落、标题划分,或者固定滑动窗口(比如每256 tokens一片段,重叠50 tokens以防割裂)。

为什么切分:因为检索算法(特别向量检索)对较短文本效果更好,更精准。长文如果不切,向量表示会模糊掉局部信息。而且LLM上下文有限,一次放太长文本可能稀释注意力。

切分后,知识库就变成很多自包含的小知识单元。比如一篇产品手册10页,切成50段,每段讲一个小主题。这样检索时能找具体段落内容,不会把无关部分混在一起。

标识来源:在切分时,记录每片段来源文档及位置(如文档ID+页码)。之后如生成回答引用,可以用这个信息构造引用标签。

2. 向量化表示:将文本转为“机器可比”的向量

切分后每个知识片段是一段文本字符串。为了让计算机能够根据语义相似性快速匹配用户问题与相关片段,我们需要把文本转换为向量表示。

Embedding模型:通常我们用一个预训练语言模型(或embedding模型,例如Sentence-BERT、Instructor XLNet等)来将每个文本片段编码成一个固定维度向量(比如768维浮点向量)。同样用户的问题也可以编码成向量。

这样文本语义上相近的,就会在向量空间距离较近。

例如句向量技术:一个描述”猫在沙发上睡觉”的句子embedding会与”沙发上有一只睡着的猫”距离很近。

embedding库:Hugging Face Transformers提供许多embedding模型。OpenAI也有embedding API。关键在于embedding模型应与任务匹配:如果知识库是百科内容,用一个通用语义embedding模型;如果带代码,可考虑CodeBERTembedding等。新的embedding如text-embedding-ada-002据称有很强通用性。

向量存储:将所有知识片段embedding计算并保存到一个向量索引中。通常用Faiss(Facebook AI Similarity Search)这种高效库,可以存下十万级向量并快速k近邻查询 。向量库也可支持磁盘存储超大量并保持查询速度如milvus、qdrant等服务。

维度和度量:embedding维度≥300通常效果不错。度量可用余弦相似度或内积。Faiss可以统一用内积(embedding先L2归一化则内积=余弦)。构建索引时可选择精确(Flat索引,扫描全部向量)或近似(IVF,PQ等) 。数据量大时用IVF可以加速但要调平衡准确率 。

构建过程:

  • 遍历每片段文本,调用embedding模型获得向量,存入索引(带上ID指向原文)。
  • 建立索引时可能需要训练(如K-means聚类建簇中心IVF)。

完成后就得到一个知识库向量索引:给它一句话embedding,能找出“最近”的片段embedding对应的片段文本。

3. 检索阶段:找到最相关的知识

当用户提问到来时,如”What are the health benefits of green tea?”,RAG系统执行检索:

  • 先将用户问题embedding化成向量q。
  • 在向量索引中最近邻搜索,找出与q距离最近的k个文档片段向量 。这些就是候选相关片段。
  • 取出对应的文本内容,作为知识材料。

例如,embedding算出问题”绿茶的健康益处”与知识库中embedding最近的3段,分别来自Wiki“绿茶”页的一段、某研究摘要段、还有“茶多酚”段。系统就拿到这3段文本。

有时也会混合检索:同时用关键词搜索和向量搜索结合。因为有些具体信息(如人名、数字)embedding模型可能不敏感,关键词匹配能补充。这可以通过对搜索结果取并集或交叉验证实现。微软Azure search提供一种Hybrid search pipeline。

阈值:向量距离可转为相似度分数。可以设置一个阈值,如果最高分太低,说明知识库可能没相关内容,则返回“未找到”。这样避免不相关内容乱加入导致答案瞎扯。但阈值调不好也会漏掉稍相关的内容,所以常取k个无视分数让生成模型自行判断相关性。

负载:向量检索一般ms级,faiss search 1M向量内可以在几十毫秒返回top10。可以cache热门问Queryembedding或者embedding向量->结果的Map减少重复计算(embedding模型和检索都可以cache)。

4. 混合检索与重排:确保找对资料

混合检索:

  • 向量检索擅长语义近似,但可能引入语义对但不精确的内容;
  • 关键字检索(传统信息检索TF-IDF)擅长精确匹配关键词。

二者结合常提升召回率。例如问“Who proposed the theory of relativity?” 关键词“theory of relativity”精准找爱因斯坦资料,而embedding可能给出广义相对论解释,也相关但不直接。将embedding结果和BM25结果合并,再让生成模型参考两边。

有工具如Lucene、Elasticsearch可做关键词检索,embedding model + milvus做向量;再综合结果deduplicate。现代HyDE(Hypothetical Document Embeddings)方法甚至让模型先拟想答案然后embedding再检索,提升效果 。

重排(Re-ranking):

初步检索k片段后,可用一个更精细的模型对候选进行重排排序。比如用一个跨编码器(BERT)拼接question+passage算相关性分数,再排序 。因为向量检索embedding一般是浅层模型(单向embedding),可以错把主题相似但非答案的放前面。重排用强模型(代价高,但k小可接受)更准确识别哪个片段真回答了问题。

举例:向量搜索对问题“最早的编程语言是哪一个?”返回 (1)关于Fortran的段落 (2)关于Ada Lovelace历史描述。重排BERT可能发现Ada段其实提到算法原型,Fortran段更直接回答“最早广泛使用的是Fortran”,所以重新排把Fortran段第一。

重排还能过滤:若embedding取片段内容有噪声,比如FAQ多个Q一起embedding导致乱匹配,重排模型或规则可以Drop无关片段,只留topN高分相关。

5. 生成与引用:让模型根据资料回答

取回相关知识片段集合后,把它们与原问题一道构造提示给LLM,让它参考资料生成答案。

提示设计通常:

问题: {question}
资料:
[1] {passage1}
[2] {passage2}
...
请根据以上资料回答问题。如引用资料,请在答案中标注出处。

这里[1]等标记对应每片知识的ID。这样模型清楚哪些内容可引用 。

大模型(如ChatGPT或fine-tuned BioGPT等)会综合多个片段生成回答,并在用到某片段信息时标上【1】引用标记 。训练时可以微调模型在输出中按格式插入引用。

模型若工作正常,应主要基于提供资料内容回答,不胡诌。幻觉减少,但要确保资料确实涵盖问题,否则模型可能猜或错引用,所以检索阶段力求找到包含答案的片段。

引用标注:

  • 可以让模型在句尾标上引用,如“…提高心血管健康【2】。”。如果一句使用多来源,也可标多标签。
  • 若模型没做到引用也没关系,后处理可以正则查模型生成内容里的部分短句去匹配来源文本。
  • 采用GPT-4等强模型时,可能它会根据上下文自发引用来源ID,但多半要explicit prompt。

格式:

可使用方括号+编号【编号】(如【2】)方便前端渲染成超链接到资料来源。

长度控制:

给模型的资料不能太多否则上下文爆炸,一般提供3-5段高相关足矣。模型长于综合概括多段。太多资料反而可能混淆或超token限制。

6. 答案与证据一致性:如何避免张冠李戴

一个风险是模型引用不准确:比如资料1提到”绿茶含茶多酚降低胆固醇”,模型回答“绿茶能降低胆固醇【1】和预防癌症【1】。”但资料1其实没说预防癌症,这就是错误引用。

解决方法:

  • 训练或规则:强调模型不要编造没在资料里的信息,若想说也要在资料中找到。Promote with “只根据资料回答,未提及则答不知道”。
  • 证据检查:在生成后,用算法检查引用内容和来源是否匹配。比如对每条引用句子检索看是否真的在相应资料出现。或用NLI模型判定句子与资料是否矛盾/无关。若发现问题,可打回模型再让它修正(多轮生成)。
  • 多段重叠:如果多个资料都提到同一点,更可信。可以鼓励模型优先引用那些重叠证据提高正确率。
  • 人审:对于非常关键的QA,最终答案可能还需人工验证引用正确性。无引用就宁可不答。

一致性:

  • 另一个方面是一致性:如果多资料信息冲突,模型要么择优要么说明有不同观点。不要混淆。例如一资料说X发生于1990,另一说1989,模型不该平均说“1989或1990【1】【2】”,而应观察来源可信度或指出存在争议。
  • 最好知识库内容先消歧或更新,否则模型也无法判断。RAG有瓶颈就是依赖知识库质量。

评测:

可以构造一些测试问答,看模型引用是否正确覆盖。如问“XX依据什么?”模型引用的内容段应该的确包含xx的依据点。评估metrics包括Precision(引用信息是否都在来源)和Recall(来源有没有没被引用的关键信息)。开源工具LlamaIndex等有些内置eval,或者LangChain的 QAG generation eval.

7. RAG系统评测:确保可靠实用

评估RAG通常从两个层面:

  • 回答质量:准确性、完整性、流畅度。如一般QA评测,人类或自动判是否正确回答了问题。
  • 引用正确性:每个事实是否有正确来源支持。

一些具体指标:

  • 准确率:对有标准答案的集计算正确率。
  • ROUGE/BLEU:对摘要类任务,可量化模型输出与参考摘要的接近。RAG often used for open QA, maybe check hit@k if answer present in retrieved or not.
  • Retrieval性能:检索 top5中是否涵盖了正确答案片段(Recall@5)。如果检索fail,后面生成再好也白搭。可以先算embedding检索正确召回率 。
  • 引用覆盖率:模型输出的关键事实是否都带引用。如果句句都有标注, recall高; 若部分句子没引用 (可能没来源或忘写)要关注,因为用户可能疑问此句来源。
  • 错误引用率:人工检查或者用前述NLI法,计算模型是否引用了文不对题的来源。如错误引用率高,须改进 prompt或模型训练。
  • 用户体验:最后可以AB测试带RAG与否用户是否更满意回答可信度。加引用通常增强信任,但不要过载引用让答案太难读。

一个RAG示例:

用户问:“Python如何读取CSV?”

检索代码文档段落,模型回答:

“可以使用Python的csv库读取CSV文件。如:

import csv
with open('data.csv', newline='') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

【1】上述代码每次循环返回一行。参考文档:Python官方教程【1】。”

评测:

  • 是否正确? 是的,代码可行。
  • 引用【1】是否涵盖代码?假设【1】是Python文档,包含类似示例,则引用正确。
  • 没提其他库如pandas,但问题没要求,算可接受。

RAG调优往往反复在检索和生成环节:调整embedding, 添加特殊处理(比如识别问题类型直接SQL搜索),调整prompt引导更简洁或更引用等。一旦指标达到要求,就可以集成应用了。

K. LangChain篇:把大模型能力进行工程化封装

1. Prompt模板:稳定高效地与模型交互

Prompt工程是核心,但不可能每次手工拼接字符串。LangChain等提供了PromptTemplate,允许你预定义带占位符的提示模板,再填充动态变量。

例:

from langchain import PromptTemplate
template = PromptTemplate(
    input_variables=["product"],
    template="写一封推荐信,推荐我们的{product}产品,突出其优点。"
)
prompt_text = template.format(product="智能扫地机器人")

这会生成:“写一封推荐信,推荐我们的智能扫地机器人产品,突出其优点。”

这样确保不同调用风格一致,不遗漏关键信息格式。而且如果发现Prompt需修改(比如改语气),只改模板即可,调用代码不变。

模板还支持few-shot示例插入、输出格式说明等。可通过PromptTemplate.from_examples(...)创建带示例的模板。

使用模板还有防止注入好处:LangChain会对输入变量做基本处理避免用户提供恶意片段破坏提示结构(虽然不是100%防,但降低风险)。

总之,PromptTemplate让提示设计模块化,易于维护。大型应用中可能有几十个Prompt模板对应不同任务,通过命名管理版本等,都比散落在代码里拼接好得多。

2. Chain:将多步骤串联起来

简单问答一轮LLM即可完成。但复杂任务需要多个步骤,Chain就是把这些步骤连接成流水线,自动把前一步输出作为后一步输入。

举例:链式推理。第一步模型从问题中提取关键实体,第二步用这些实体去查询数据库,第三步模型根据查询结果生成最终答案。

用LangChain可以:

from langchain.chains import LLMChain, SimpleSequentialChain

# 定义各子任务Chain
entity_extraction_chain = LLMChain(prompt=entity_prompt, llm=llm)
db_query_chain = ... # 这里可以是自定义函数Chain or LLMChain
answer_chain = LLMChain(prompt=answer_prompt, llm=llm)

# 串联
overall_chain = SimpleSequentialChain(chains=[entity_extraction_chain, db_query_chain, answer_chain])
result = overall_chain.run(user_input)

它会先调用entity_extraction_chain.run(user_input),取其输出fed入db_query_chain.run(…),再输出给answer_chain.run(…)。开发者不用处理中间数据传递逻辑,LangChain自动串接 。

Chain可以复杂如Branching(根据条件走不同子链)或循环(Plan-Execute loop)。LangChain提供RouterChain实现根据输入选择哪个LLMChain来处理(比如检测语种路由到对应语言模型)。

通过Chain,我们把LLM组合成可复用组件,就像搭乐高。调试时可单独测试子链。出现问题时,我们知道是哪一步出错,而不是面对一团糟的prompt无法分解。

3. 工具封装:赋予模型行动能力

LLM虽强,但关闭环境,不会主动查资料或算数。LangChain通过Tool机制,让模型调用外部工具 。工具可以是:

  • 网络搜索
  • 计算器
  • 数据库查询
  • Python REPL运行代码
  • 自定义任意函数

LangChain的Agent会将模型包装,使其能够接收用户输入并产生Action调用工具,再得到Observation,再决定下一步 。但在LangChain context里Tool often used with Agents.

Tool封装很简单:

from langchain.tools import Tool

def search_web(query: str) -> str:
    # call search API, get top result
    return result_text

search_tool = Tool(
    name="web_search",
    func=search_web,
    description="用网络搜索查询信息"
)

然后Agent可获知有工具”web_search”, 当对话需要就输出Action: web_search, Action Input: "...query...",LangChain捕获这pattern就调用search_web函数,把返回文本再注入模型上下文 。

Tool使用要注意权限:工具函数往往能访问系统资源,不可随便暴露。LangChain Tools ideally do controlled things (like no direct os.system). It’s developer’s job to ensure Tools safe, e.g., a Python tool should run in sandbox or restrict builtins.

幂等: 工具尽量重复调用结果一致。因为Agent可能因为LLM随机性尝试多次Action。如果Tool副作用比如“发送邮件”,重复就不安全。可以在Tool实现上加锁或check例如’if email sent: skip’。LangChain Tools best used for queries or pure functions.

沙箱: 若提供Python执行工具,可以使用 restricted environment (like Pyodide or a subprocess with limited libs)防止恶意代码危害。LangChain提供PythonAstREPLTool做些限制。开发者要预见坏case。

审计: Tools的调用参数和结果LangChain有日志可记录。上线要保留Audit log,如果出错可追溯:如某用户输入导致模型多次用calc工具异常,用日志可定位,进而改prompt或function。

4. Retriever:简化检索增强

上一章RAG中,检索知识需要embedding模型+向量库逻辑。LangChain提供Retriever接口,把这些封装起来。

使用FAISS向量库的Retriever例:

from langchain.vectorstores import FAISS
from langchain.embeddings.openai import OpenAIEmbeddings

# 先创建向量存储
embedding_model = OpenAIEmbeddings()
vector_db = FAISS.from_texts(texts, embedding_model)
retriever = vector_db.as_retriever(search_kwargs={"k": 3})

现在retriever就有一个.get_relevant_documents(query)方法,返回3个匹配文档 。

LangChain的RetrievalQAChain可以把Retriever和LLMChain合为一体:对于用户问题,先retriever取docs,填入模板让LLM生成答案 。

这大大简化RAG实现,不需要手写embedding计算、faiss search等,LangChain底层做了。还有ConversationalRetrievalChain可以附带记忆chat history, thus supporting follow-up questions with context .

Retriever还有UI: Many vector store classes (like Pinecone, Weaviate, etc) have .as_retriever() method, unify usage. Changing vector DB just one line change.

5. Memory:让Chain具备对话记忆

LangChain 的 memory(记忆)提供了在多次链式(chain)调用之间延续状态(state)的能力。一个典型例子是:

与 LLM 进行对话时,我们希望模型能够记住之前的对话轮次。

LangChain 的 ConversationBufferMemory 或 ConversationSummaryMemory 可以用来存储过去的消息。当调用 Chain.run 时,Memory 会把历史消息按指定格式整理并注入到提示词(prompt)中。

Memory 是作为 Chain 的一部分来实现的——LangChain 里的很多 Chain 都支持通过 memory= 参数来传入记忆对象,例如:

from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

conv_chain = ConversationChain(
    llm=llm, 
    memory=ConversationBufferMemory()
)
conv_chain.run("你好,能告诉我天气吗?")

Memory 会存储「用户 → LLM」的消息。下一次运行时,它会在新问题之前注入类似“用户:你好… 助理:…”的历史对话内容。

Memory 也可以更复杂一些,例如:

  • SummaryMemory(摘要记忆):当对话很长时,会把更早的部分做摘要,以保持上下文更短。
  • VectorStoreMemory(向量库记忆):可以把对话存到向量数据库里,这样模型就能在超过上下文长度后仍然召回更早的事实信息。
  • EntityMemory(实体记忆):跟踪对话中明确提到的实体,并记住它们的属性(比如用户的名字等)。

Memory 本质上是一种“链级别(chain-level)的状态”。因此在开启新会话时,要么清空它,要么创建新的 Memory。比如在多用户聊天场景中,每个用户都应该有自己独立的 Memory 实例。

Memory 的常见坑:

  • 如果 memory 增长太大,会消耗更多 token、增加成本。需要通过摘要或限制长度来控制。
  • memory 里包含敏感信息会有风险——模型可能会在后续重复这些内容。可能需要对记忆内容做过滤(比如用户说了秘密,可能不应保留原文)。
  • 在问答检索(QAG)类任务中通常不使用 memory ——memory 更适合用于对话场景。

6. 结构化输出:让模型说人话给机器看得懂

LLM 输出的人类自然语言可读性很好,但有时我们需要机器更容易解析的结果,比如 JSON 或特定格式(例如 DSL 代码)。LangChain 可以通过以下方式帮助强制输出格式:

  • Output Parsers(输出解析器):你可以用正则表达式(regex)或 Pydantic 的 schema 来定义解析器。例如,StructuredOutputParser.from_names_and_descriptions() 可以创建一个解析器,用来约束输出必须包含某些字段,并为这些字段提供描述。

例如:

from langchain.output_parsers import ResponseSchema, StructuredOutputParser

schemas = [
    ResponseSchema(name="summary", description="文章摘要"),
    ResponseSchema(name="sentiment", description="积极/消极态度")
]
parser = StructuredOutputParser.from_response_schemas(schemas)
prompt = PromptTemplate(template="... JSON ... {format_instructions}", ...)

parser.get_format_instructions() 会返回一段字符串,例如:

“输出一个 JSON,包含字段:summary(string)…… sentiment(string,取值为 ‘positive’ 或 ‘negative’)。”

你把这段格式说明放进 prompt 里,模型就更可能按要求输出 JSON。

然后你调用 parser.parse(model_output) 来把模型输出解析成一个 dict(或 Pydantic 对象)。如果解析失败,可以选择直接抛出异常,或者尝试修复。

这种方式能大幅简化把结果传递给下一个系统组件的流程。比如模型按 JSON 输出并带有固定 key,你的代码里就可以直接使用。

当然,模型有时还是会输出不合法的 JSON(比如缺少引号、多了尾随逗号等)。LangChain 的解析器通常会尝试修复一些小问题(例如补上缺失的括号)。如果还是失败,你可以再次把输出喂给模型并提示它:“你的输出不是合法的 JSON”。

那为什么不直接在 prompt 里要求模型输出 JSON 呢?我们确实会这样要求,但解析器能进一步“保证结构”。通常也建议在 prompt 里加上示例格式来降低出错概率。

这对构建比如“必须用 JSON 返回 action 的智能体”,或者“需要把数据返回给 UI 的链”来说非常关键。

L. Agent篇:Manus式任务代理的架构实践

1. 规划(Planning):将复杂任务拆解为可执行步骤

用户可能提出一个较模糊或宏大的目标,例如:“帮我写一个小型游戏的代码并确保它能运行”。Agent直接生成代码可能出错,最好先规划。

规划的实现:

  • Chain-of-Thought prompting:让LLM先输出思考,如:“我需要哪些步骤完成?1. 分析需求 2. 设计架构 3. 编写代码 4. 测试运行 5. 修复bug…” 。这可以引导模型列出子任务序列。
  • 或使用专门Prompt:如Manus场景有类似 “你现在是ProjectManager AI,目标… 请输出一个按顺序的任务清单”的定制角色,LLM会产生一个结构化列表。
  • 甚至LangChain提供PlanAndExecute chain或AutoGPT模板,会先调用一个规划Chain输出Task list,后面逐步执行。

关键是让模型按序列输出子任务,每个尽量清晰可检查。可以用某种格式(Markdown list, JSON tasks array)以便解析。

示例:

用户:“在这个仓库里实现一个排序算法的优化并提交PR”

Agent (Planning step) -> LLM:

“Plan:

  1. Clone repository .
  2. Identify sorting algorithm file.
  3. Analyze optimization opportunities.
  4. Implement improvements.
  5. Run tests.
  6. Commit changes and push branch.
  7. Open Pull Request.”

这个Plan拆解可执行动作。Agent后面会根据Plan逐步调用工具/LLM来实现。

验证Plan:

Agent 可以检查自己的计划(Plan)是否合理。如果(例如在 CLL 的 chain-of-thought 中)出现了像“Push branch(推送分支)”这样的步骤,但当前环境可能是只读的(read-only),那就应该修订计划。也可以通过一条系统消息来提醒 agent 相关约束条件。

动态计划(Dynamic plan)

计划不是固定不变的,agent 可以在执行过程中随时调整:

比如进行到第 5 步(运行测试)时发现测试失败,agent 可能会插入新的步骤,例如:“6. 调试测试失败原因,7. 重新运行测试”等。这就是“反馈与反思(Feedback & reflection)”的体现。

2. 执行(Execution):逐步完成子任务

有了计划之后,Agent 会进入一个循环:

对计划中的每一步:

  • 理解步骤(比如“Clone repo / 克隆仓库”):决定用哪个工具或方法来完成。如果是“克隆仓库”,agent 很可能会使用“Git clone 工具”。
  • 执行:用合适的输入调用选定工具(比如仓库 URL)。
  • 观察结果:是否成功?如果工具返回输出或报错,agent 会读取并理解这些信息。

如果成功,agent 就继续下一步。

如果失败,agent 可能会:

  • 重试(如果可行,比如搜索没找到答案,可以换个说法再搜)。
  • 调整计划(可能这个步骤不需要,或者缺少前置条件)。
  • 不可恢复时(没有工具能完成该步骤),agent 可以选择优雅失败,或向用户寻求帮助。

下面是一个执行日志示例:

Plan[1] Clone repository(克隆仓库):

Agent:Action(动作):GitClone,Input(输入):“https://github.com/user/repo.git

Observation(观察): “Repository cloned to /workspace/repo”(仓库已克隆到 /workspace/repo)

Plan[2] Identify sorting file(定位排序相关文件):

Agent:可能用内部 grep 工具,或者用 Python 工具读取代码:

Action:ListFiles,Input:”/workspace/repo”

Observation: “Found files: sort.py, util.py, test_sort.py”(找到文件:sort.py、util.py、test_sort.py)

Agent 判断 sort.py 很可能就是算法相关文件。

Plan[3] Analyze optimization(分析优化点):

Agent 打开 sort.py:

Action:OpenFile,Input:“sort.py”

Observation:(文件内容)

然后让 LLM 分析:

Action:LLM,Input:“Analyze code inefficiencies in … content…”(分析这段代码的低效之处……)

Observation:(LLM 回答,例如:“这里用了冒泡排序,效率较低,可以改用 Python 内置排序或更好的算法。”)

Plan[4] Implement improvements(实现改进):

Agent 使用 “EditFile” 工具:

Action:EditFile,Input:“sort.py”,Instruction(指令):“Replace bubble sort with merge sort.”(把冒泡排序替换为归并排序)

Observation: “File updated.”(文件已更新)

Plan[5] Run tests(运行测试):

Action:RunTests,Input:“test_sort.py”

Observation: “Tests failed on case X”(用例 X 测试失败)

计划调整:Agent 看到失败,决定新增计划:先调试再修复

反思: “失败了,我得先排查。”

插入新步骤:“5a. Read error log(读错误日志) 5b. Fix bug(修复 bug)”

Plan[5a] Read error log(读错误日志):

Action:GetLastError(可能是读取测试结果的工具/记忆)

Observation: “Failure at line…, expected sorted list got …”(某行失败,期望结果与实际结果不一致)

Agent 找到错误原因。

Plan[5b] Fix bug(修复 bug):

Action:EditFile,修正代码。

然后再次运行测试。

如果测试通过:

  • Plan[6] Commit changes(提交代码):使用 GitCommit 工具。
  • Plan[7] Open PR(发起 PR):可能使用 GitHub API 工具来创建 PR。

在执行过程中,每一步可能需要再次调用 LLM,也可能只需要执行代码/工具:

  • 在需要强推理的环节(比如分析输出、决定下一步行动)使用 LLM。
  • 具体的“重活”(执行命令、读写文件、跑测试等)交给工具完成。

3. 反馈与反思(Feedback & Reflection):不断校正自己

一个健壮的 agent 不应该只是盲目照着计划执行:它还需要评估每一步的结果,并在必要时进行调整。

我们在上面的例子里看到:

  • 测试失败之后,agent 通过读取输出识别到实现里有 bug,于是插入了调试步骤。

这意味着 agent 使用的是一种反馈循环(feedback loop)

对每一步都检查是否满足成功标准(success criteria)。

如果某一步产出的结果不符合预期,agent 会进行反思(Reflection)

  • 可能调用 LLM 来分析“为什么会失败?现在该怎么办?”(有些框架把这种通过额外 LLM 步骤实现的能力称为“自我修复 / Self-Healing”)。
  • 或者用简单规则:比如测试输出不是 “OK” 就进入修复流程。

反思也可能发生在最后:如果 agent “认为”任务已完成,但最终输出仍然没有完全满足用户需求,它可以进行一次全局反思(global reflection)。

一些更高级的 agent 在给出答案后,还会生成一段自检推理:“这个答案好吗?”如果不好,就再修改一版。

在 Manus 的语境里,他们强调反思是关键能力:

例如执行了几步之后,暂停让模型反思:“我是不是卡住了?有没有跑偏?如果是,就修订计划。”

如何实现反思(Reflection):

  • 可以在某些节点对 LLM 发送一个专门的提示词:“回顾当前进展:……是否需要调整?”
  • 或者把中间结果与指标对比:

例如抓取网页返回 404,那么反思结论可能是:搜索关键词错了,需要改查询方式。

一种常见做法是 ReAct 框架

  • 模型输出 “Thought: …”(思考)、“Action: …”(行动),得到 Observation(观察结果)后,再输出下一轮 “Thought: …”。

其中 “Thought” 基本就是每一步内置的反思。LangChain 的 Agent 也有类似机制。

结果记忆(Memory of results)

让 agent 记录之前的尝试很重要,能避免陷入无限循环(比如同一个失败的搜索词重复试 5 次)。可以在 memory 里维护一个“失败尝试列表”,并在提示词里告诉模型:“你已经尝试过 X,但没有成功。”

4. 工具系统工程:安全、幂等、沙箱、审计

为 agent 赋予工具能力是一把“双刃剑”。

安全:

  • 有些工具(比如系统 shell 或外部 API)如果 agent 失控或遭遇 prompt 注入,可能执行有害操作。
  • 需要一层权限控制:例如对用户请求或 agent 计划中的动作进行分类与校验。

比如在非明确允许的场景下,禁止调用 “DeleteFile(删除文件)” 工具。

  • 对高风险操作要求用户确认(例如给全公司群发邮件)。
  • 维护允许/禁止清单(allowlist/denylist):例如 agent 不能访问任意网络,只能访问指定域名。

工具执行环境:

  • 如果使用 Python REPL,应在沙箱环境运行:
  • 限制内置能力(restricted builtins),禁用 os.system,限制时间与内存。
  • 也可以让每段代码在一次性的 Docker 容器里执行,且容器对宿主机只有必要的最小访问权限。
  • 如果工具需要 API Key(如搜索 API),要确保 agent 的代码或输出无法泄露密钥,必要时限制其可打印内容。

幂等性:

  • 工具最好能做到“多次调用也不会产生额外副作用”。如果做不到,agent 或系统要保证调用的唯一性。
  • 例如写数据库:为避免重复插入,工具可以先检查记录是否存在,存在则更新而非重复插入。
  • 对非幂等操作(比如发邮件,每调用一次就会再发一封)必须谨慎:
  • 例如 agent 先问用户:“现在确认发送邮件吗?”确保只执行一次。

审计日志(Audit logs):

  • 每次工具调用(动作、输入、输出、时间戳、用户上下文)都应持久化记录。
  • 一旦出现问题(如安全事故或误操作),日志能帮助追溯整个过程并防止复发。
  • 也可以把摘要日志展示给用户审阅,尤其是关键变更场景(类似“执行日志 / execution log”)。

回放(Replay):

  • 开发阶段可以把日志交给分析 agent,定位推理或系统哪里出错。
  • 也可以把日志发回给模型:“第 4 步你做了 X 导致 Y 失败,下次该怎么修?”用于改进。

限流与并发:

  • 调用外部 API 的工具要做限流与退避(backoff),防止 agent 在循环里把搜索调用 100 次导致轰炸式请求。
  • 例如限制每个查询最多调用搜索工具 5 次;超过则系统中断或报错。
  • 并发方面:如果多个 agent 进程可能同时做重操作(如写文件),工具要保证线程安全或加锁。

工具测试:

  • 每新增一个工具,都应先独立充分测试,而不是让 agent 去“帮你调工具”。工具要么成功,要么给出清晰错误,方便 agent 做下一步决策。
  • 用典型输入与边界输入模拟 agent 调用,检查工具行为与输出格式是否稳定。

工具更新:

  • 如果工具行为变化(比如搜索 API 返回格式变了),agent 可能会误解响应。应当:
  • 要么让 prompt 足够鲁棒能适配变化;
  • 要么加一层中间适配器,把工具输出转换为对 agent 友好的稳定格式。

示例:

工具:“DatabaseQuery” 用来在公司数据库上跑 SQL。

  • 安全:只允许 SELECT(禁止 DROP 等破坏性语句)。
  • 只开放特定 schema 的访问权限。
  • 如果 agent 访问未授权表,工具返回错误,agent 应优雅处理(比如提示“无法访问”,并请用户调整请求)。
  • 记录所有查询日志;对更新类查询甚至可以要求主管审批后才允许执行。

5. 多智能体协作:分而治之的高效体系

一个 agent 能做很多事,但有时把角色拆分开会更有效:

Manus 架构(片段):

  • 规划 Agent(Planning Agent):专注任务拆解与优先级/排程。
  • 执行 Agent(Execution Agent):调用工具/API,完成实际动作。
  • 验证 Agent(Verification Agent):检查结果,对输出进行交叉验证以发现错误。

它们协同工作:

例如规划 agent 产出计划与优先级;执行 agent 挑一个任务去做;验证 agent 监控结果,如果发现不对,就触发反馈回路,让规划 agent 修订计划或让执行 agent 重做。

也可以拆分“领域专家”:

  • 如果任务涉及代码、写作、分析等不同能力,可以分别使用更擅长代码生成的 agent、以及更擅长写作的 agent。
  • 再由一个**编排/调度 Agent(Orchestration agent)**决定把哪个子任务分配给哪个子 agent。

通信方式:

  • agents 可以通过共享记忆或消息通信,可能借助一个中介(如运行环境或黑板/公告板机制)。
  • 例如在仿真里:一个 Chat agent 负责决定公式,把公式发给 Calc agent;Calc agent 计算后返回结果。

协作与调度:

  • 可以串行也可以并行:
  • 对互不依赖的子任务,多个执行 agent 可以并发处理不同部分(比如同时爬取多个网页),然后由汇总 agent 收集整合。
  • 但并发会带来复杂度:要确保协同一致(避免两个 agent 做了重复或互相矛盾的动作)。

多智能体评估:

  • 难点在于“涌现行为”(emergent behaviors):
  • 例如 agent 之间互相协商可能陷入僵局,或出现无限礼貌来回。
  • 可能的解决方案:
  • 设定对话规则(比如来回 3 轮仍无共识就升级给用户决策)。
  • 用结果指标评估:协作 agent 是否在限定步骤内完成任务?

模拟用户:

  • 多智能体也常用于模拟“用户—助手”对话做测试:一个 agent 扮演用户,一个扮演助手,用于自动生成训练数据或测试一致性。

案例:

想象一个“DevOps agent 团队”:

  • InfraAgent:专门搭建服务器/基础设施。
  • CodeAgent:专门写代码。

用户说:“把一个 Web 应用部署到云上。”编排 agent 就拆分任务:

InfraAgent 负责“搭建云 VM”,CodeAgent 负责“把应用代码整理成可部署形态”。

它们并行工作,交换应用需求或环境信息。最后编排 agent 验证两边产物能集成:代码交付、基础设施部署并配置好环境,然后触发最终应用测试。

这种协作能加速执行并引入领域专长(甚至为不同角色使用不同底层模型,比如代码模型 vs DevOps 知识模型)。

但复杂度也更高:多 agent 的调试更难,且每个 agent 的调用都会让成本叠加。

性能:

  • 如果来回沟通太多,多智能体可能反而拖慢性能。要权衡:是否用一个更强、上下文更大的单 agent 反而更省事,避免拆分带来的开销。
  • 不过,专门化也可能带来更高质量(例如专门的代码模型在写代码上通常优于通用模型)。

衡量方式:

  • 跟踪消息交换次数、总耗时、纠错次数,并与单 agent 场景对比,确保额外开销能换来成功率或质量提升。

总结:

合理使用多智能体能带来模块化(某个 agent 出问题只调整它)和并行能力,但需要谨慎的架构设计(类似操作系统里的“一个管理者 + 多个工作者”的并发模式)。

更多推荐