元哥开讲:AI Agent(四)实战!从零打造一个“深度研究型Agent”(代码篇)

44 阅读6分钟

嘿,各位未来的“AI架构师”,我是元哥。

上回书咱们聊明白了,“上下文工程”是AI Agent的灵魂。理论讲了一大堆,大家肯定手痒了:“元哥,别光说不练啊,到底咋搞?”

好!今天,咱们就来一场真刀真枪的“深度潜航”!不扯理论,只上实战。元哥将用一个完整的Python代码示例,手把手带你,从零开始,打造一个能帮你读万卷书、做深度研究的“研究型AI Agent”。

注意: 下面的代码为了便于理解,省略了真实的LLM调用,用“模拟函数”代替。但它完整地展示了Agent的思考框架和运行逻辑,这才是精髓!

第一步:定义目标和武器库

我们的目标: 打造一个Agent,它能根据我们指定的几个本地文档,回答一个综合性的问题。

场景: 假设我们有两个本地文件,doc1.txtdoc2.txt,里面分别记录了关于RAG技术的优点和缺点。我们要让Agent回答:“请基于'doc1.txt'和'doc2.txt',总结RAG技术的优缺点。”

Agent的武器库(工具箱): 我们先用Python定义好Agent能使用的工具。

# --- 模拟工具箱 ---

def read_file_content(path: str) -> str:
    """读取指定文件的内容。"""
    print(f"【工具调用:读取文件 {path}】")
    # 在真实世界里,这里会真的去读文件
    # 我们用假数据代替
    if path == 'doc1.txt':
        return "RAG的优点:1. 知识库可以随时更新,解决了LLM知识截止的问题。 2. 因为答案有据可循,大大减少了模型一本正经胡说八道的‘幻觉’现象。"
    elif path == 'doc2.txt':
        return "RAG的缺点:1. 检索的质量很大程度上决定了最终生成答案的质量,所谓‘垃圾进,垃圾出’。 2. 检索回来的文档片段和原始问题如何优雅地整合,是一个技术挑战。"
    else:
        return "错误:文件未找到。"

def synthesize_answer(information: list, question: str) -> str:
    """根据一系列信息,综合成最终答案。"""
    print(f"【工具调用:综合信息回答 ‘{question}’】")
    # 这里的核心是调用LLM来做最终的“总结陈词”
    info_str = "\n".join(information)
    # final_prompt = f"请基于以下信息:\n{info_str}\n\n来回答问题:{question}"
    # return llm(final_prompt)
    # 我们用模拟输出来代替
    return f"综合分析,RAG技术的优点是知识可更新、减少幻觉;缺点是依赖检索质量、整合有挑战。"

# 我们还需要一个特殊的“结束”工具
def finish(final_answer: str) -> str:
    """任务完成,输出最终答案。"""
    print("【任务完成】")
    return final_answer

第二步:设计Agent的“大脑”和“工作手册”

接下来,我们用“分层上下文”的思想,来设计Agent的核心Prompt。这就像给它一本清晰的《工作手册》。

# --- Agent的核心Prompt设计 ---

# 1. 系统上下文 (System Layer) - Agent的灵魂和世界观
SYSTEM_PROMPT = """你是一个顶级的AI研究员。你的任务是根据用户的提问和指定的文档,给出一个全面、客观的回答。
你必须通过“思考”来规划步骤,然后选择合适的“行动”(工具调用)来执行。
你的行动必须是以下格式之一:
- `read_file_content(path: '文件名')`
- `synthesize_answer(information: [信息列表], question: '问题')`
- `finish(final_answer: '你的最终答案')`
"""

# 2. 任务上下文 (Task Layer) - 当前的具体任务
USER_QUESTION = "请基于 'doc1.txt' 和 'doc2.txt',总结RAG技术的优缺点。"

# 记忆与草稿纸 (Memory/Scratchpad) 将在运行中动态构建

这份“手册”告诉了Agent:我是谁?(研究员),我最终要干什么?(回答用户问题),我能用什么武器?(工具列表),我该如何行动?(思考-行动格式)

第三步:构建Agent的“主循环”

现在,万事俱备,我们来构建Agent的核心运行逻辑。它将遵循ReAct模式,在“思考→行动→观察”的循环中,一步步完成任务。

# --- Agent的主循环 ---

class ResearchAgent:
    def __init__(self, goal):
        self.goal = goal
        self.scratchpad = "" # 记忆与草稿纸,用于记录每一步的观察
        self.tools = {
            "read_file_content": read_file_content,
            "synthesize_answer": synthesize_answer,
            "finish": finish
        }

    def run(self):
        while True:
            # 1. 构建完整的Prompt (四层上下文的组合)
            prompt = self._build_prompt()
            print("--- 向LLM发送的完整Prompt ---" + prompt + "--------------------------")

            # 2. 调用LLM进行思考和行动决策 (这里我们手动模拟LLM的输出)
            llm_output = self._mock_llm_call(self.scratchpad)
            print(f"--- LLM的输出 ---
{llm_output}\n--------------------")

            # 3. 解析LLM的输出,分离出“思考”和“行动”
            thought, action_str = self._parse_output(llm_output)
            print(f"思考: {thought}")

            # 4. 执行行动
            if action_str.startswith("finish"):
                # 如果是结束指令,就输出结果并终止循环
                final_answer = eval(action_str.replace("finish", ""))[0]
                print(f"\n最终答案:{final_answer}")
                break
            
            observation = self._execute_action(action_str)
            print(f"观察: {observation}\n")

            # 5. 更新“草稿纸”,为下一步思考做准备
            self.scratchpad += f"\n思考: {thought}\n行动: {action_str}\n观察: {observation}"

    def _build_prompt(self):
        """构建发送给LLM的完整Prompt"""
        return f"{SYSTEM_PROMPT}\n\n# 记忆与草稿纸\n{self.scratchpad}\n\n# 当前任务\n用户问题: {self.goal}"

    def _execute_action(self, action_str):
        """执行工具调用"""
        try:
            # 解析`tool_name(args)`格式
            tool_name = action_str.split("(")[0]
            args_str = action_str[len(tool_name):]
            # 安全地执行函数字符串
            return self.tools[tool_name](**eval("dict" + args_str))
        except Exception as e:
            return f"行动执行错误: {e}"

    def _parse_output(self, llm_output):
        """解析LLM输出,分离思考和行动"""
        thought = llm_output.split("行动:")[0].strip()
        action_str = llm_output.split("行动:")[1].strip()
        return thought, action_str

    def _mock_llm_call(self, scratchpad):
        """模拟LLM的思考过程,这是Agent智能的核心"""
        if "doc1.txt" not in scratchpad:
            return "思考: 我需要先读取第一个文档'doc1.txt'来找RAG的优点。\n行动: read_file_content(path='doc1.txt')"
        elif "doc2.txt" not in scratchpad:
            return "思考: 我已经有了关于优点的信息。现在我需要读取第二个文档'doc2.txt'来找RAG的缺点。\n行动: read_file_content(path='doc2.txt')"
        else:
            return "思考: 我已经收集了所有文档中关于优缺点的信息。现在是时候把它们整合起来,形成最终答案了。\n行动: synthesize_answer(information=['RAG的优点...', 'RAG的缺点...'], question='总结RAG技术的优缺点')"

# --- 运行Agent ---
if __name__ == "__main__":
    agent = ResearchAgent(USER_QUESTION)
    agent.run()

当你运行这段代码,你会清晰地看到一个Agent是如何一步步思考、行动、观察,并最终完成你交给它的复杂研究任务的。

元哥小结

老铁们,感觉怎么样?

今天,我们没有讲太多花哨的理论,而是完整地走了一遍“构建-思考-执行”的全流程,并给出了一个可以跑起来(虽然是模拟的)的代码框架。我们通过“分层上下文”赋予了Agent清晰的“世界观”,通过定义工具给了它行动的“手脚”,最后通过ReAct的思考模式,让它“活”了起来。

这就是“上下文工程”的魅力。它不是一个单一的技巧,而是一套设计哲学,一套将我们的目标、规则和资源,有效地传递给AI,并引导它完成复杂任务的系统工程。

我们今天的Agent很强大,但它的“工作手册”(分层上下文)还是需要我们人工去精心设计的。有没有可能,让Agent自己去管理和维护这个“工作台”,甚至根据任务动态调整自己的“思考架构”?

当然有!

下一期,也是我们AI Agent系列的最后一期,元哥将带大家仰望星空,聊一聊更前沿的、拥有“元认知”能力的**“深度智能体”(Deep Agents)**!这可能是通向通用人工智能(AGI)的最后一块拼图。千万别错过!

【引用说明】 本文的创作灵感和核心知识点来源于《Prompt Engineering Guide》网站。

  1. Context Engineering Deep Dive: Building a Deep Research Agent: www.promptingguide.ai/agents/cont…
  2. Context Engineering Guide: www.promptingguide.ai/guides/cont…

大家也可微信搜索“极客纪元”并关注我,获得更多分享。

扫码_搜索联合传播样式-标准色版.png