徒手打造个人AI Agent:基于DeepSeek-R1+websearch从零构建类Manus深度探索 ...

打印 上一主题 下一主题

主题 1339|帖子 1339|积分 4017

从零构建深度探索Agent

   原创不易,转发请备注【评论S源码】
  我们将从零开始构建这样一个基于DeepSeek R1的深度研究Agent。
什么是深度研究代理(深度探索Agent)?

该系统可以或许在预定义的主题上进行深入研究。通常,这包括以下步调:


  • 研究筹划:这意味着创建一个研究陈诉大纲,这将成为系统的最终输出。
  • 将上述内容拆分为可管理的步调
  • 对陈诉的各个部分进行深入研究。针对推理所需的数据,进行全面的分析,并使用网络搜索工具支持分析。
  • 反思研究过程中不同步调生成的数据,并改进结果
  • 总结检索到的数据,并生成最终的研究陈诉
今天,我们将实现上述所有步调,而不使用任何LLM编排框架。
系统拓扑布局

以下是我们将要构建的系统布局图,系统将执行以下步调:

  • 用户提供一个查询或研究主题。
  • LLM将创建最终陈诉的大纲,目标是不凌驾肯定数量标段落。
  • 每个段落描述将分别输入到研究过程中,以生成用于陈诉构建的全面信息。研究过程的详细描述将在下一节中介绍。
  • 所有信息将输入到总结步调中,构建最终陈诉,包括结论。
  • 陈诉将以Markdown形式提供给用户。

深度研究代理拓扑布局
研究步调

让我们深入研究上一段中定义的研究步调:

  • 一旦我们有了每个段落的大纲,它将被传递给LLM,以构建网络搜索查询,以尽可能丰富所需的信息。
  • LLM将输出搜索查询及其推理过程。
  • 我们将执行网络搜索,并检索最相干的顶部结果。
  • 结果将传递到反思步调,LLM将推理任何遗漏的细节,并实验提出一个可以或许丰富初始结果的搜索查询。
  • 这个过程将重复n次,以实验获得最佳的信息集。

实现代理

到deepseek官方、阿里、腾讯登平台注册并获取你DeepSeek R1模子家族的API密钥。
安装工具包:
  1. pip install openai
复制代码
对于该项目,我们将使用6710亿参数的非精简版DeepSeek-R1版本。
确保你的DS_API_KEY已作为环境变量导出,并在控制台或Notebook中运行以下代码:
  1. import os
  2. import openai
  3. client = openai.OpenAI(
  4.     api_key=os.environ.get("DS_API_KEY"),
  5.     base_url="https://preview.snova.ai/v1",
  6. )
  7. response = client.chat.completions.create(
  8.     model="DeepSeek-R1",
  9.     messages=[{"role":"system","content":"You are a helpful assistant"},
  10.               {"role":"user","content":"Tell me something interesting about human species"}],
  11.     temperature=1
  12. )
  13. print(response.choices[0].message.content)
复制代码
你应该会看到雷同以下内容:
  1. <think>
  2. Okay, so I'm trying to ... <REDACTED>
  3. </think>
  4. 人类物种以其大脑的卓越认知能力而脱颖而出,这些能力支撑了一系列独特的特征。我们大脑的先进结构和功能使复杂的思维、语言和社会组织成为可能。这些能力推动了创新、艺术和复杂社会的形成,使人类在适应、创新和创造方面超越了其他任何物种。这种认知能力是人类成就的基石,也是我们对世界产生深远影响的根本。
复制代码
推理标记将始终包含在答案中。虽然看到思考过程很风趣,但我们的系统只需要答案。这就是我们可以创建一个清理函数来移除<think>标签之间的内容。
  1. def remove_reasoning_from_output(output):
  2.     return output.split("</think>")[-1].strip()
复制代码
简单但很有用。
太好了!我们如今已经设置了SambaNova账户,并了解了DeepSeek R1模子家族的输出布局。接下来,让我们开始实现深度研究代理。

第一部分:定义状态

首先,我们需要定义整个系统的状态,该状态将在代理运行过程中不断演变,并被系统的不同部分选择性地使用。
让我们将状态与代理系统的阶段联系起来:

拓扑状态


  • 阶段1 是创建陈诉大纲的阶段,陈诉布局将在此阶段规划,其状态也会随之演变。我们从一个空状态开始,但最终会演变为雷同以下布局(推理过程在阶段2中描述):
    1. {
    2.     "report_title": "报告标题",
    3.     "paragraphs": [
    4.         {
    5.             "title": "段落标题",
    6.             "content": "段落内容",
    7.             "research": <...>
    8.         },
    9.         {
    10.             "title": "段落标题",
    11.             "content": "段落内容",
    12.             "research": <...>
    13.         }
    14.     ]
    15. }
    复制代码
    使用Python的dataclass可以优雅地实现上述布局。代码如下:
    1. @dataclass
    2. class Paragraph:
    3.     title: str = ""
    4.     content: str = ""
    5.     research: Research = field(default_factory=Research)
    6. @dataclass
    7. class State:
    8.     report_title: str = ""
    9.     paragraphs: List[Paragraph] = field(default_factory=list)
    复制代码
  • 阶段2 是我们迭代每个段落状态的阶段。我们将更改每个段落的“research”字段。每个段落的研究状态布局如下:
    1. {
    2.     "search_history": [{"url": "某个网址", "content": "网页内容"}],
    3.     "latest_summary": "基于搜索历史的最新总结",
    4.     "reflection_iteration": 1
    5. }
    复制代码

  1. 研究步骤
  2. - `search_history`:我们将存储所有执行的搜索结果,包括网址和内容,以便后续去重和在最终报告中引用链接。
  3. - `latest_summary`:基于所有搜索结果的段落总结版本。它将用于反思步骤,以判断是否需要进一步搜索,并在后续的总结和报告生成步骤中使用。
  4. - `reflection_iteration`:用于跟踪当前的反思迭代次数,并在达到限制时强制停止。
  5. 同样,我们可以使用`dataclass`实现研究状态:
  6. ```python
  7. @dataclass
  8. class Search:
  9.     url: str = ""
  10.     content: str = ""
  11. @dataclass
  12. class Research:
  13.     search_history: List[Search] = field(default_factory=list)
  14.     latest_summary: str = ""
  15.     reflection_iteration: int = 0
  16. ```
复制代码

第二部分:创建陈诉大纲

不同版本的模子在输出一致性方面会有所不同。我实验了多次DeepSeek-R1,以下提示似乎可以或许生成格式良好的输出:
  1. output_schema_report_structure = {
  2.     "type": "array",
  3.     "items": {
  4.         "type": "object",
  5.         "properties": {
  6.             "title": {"type": "string"},
  7.             "content": {"type": "string"}
  8.         }
  9.     }
  10. }
  11. SYSTEM_PROMPT_REPORT_STRUCTURE = f"""
  12. 你是一个深度研究助手。给定一个查询,规划一个报告的结构以及要包含的段落。
  13. 确保段落的顺序合理。
  14. 一旦大纲创建完成,你将分别针对每个部分进行网络搜索和反思。
  15. 按照以下JSON模式定义格式化输出:
  16. <OUTPUT JSON SCHEMA>
  17. {json.dumps(output_schema_report_structure, indent=2)}
  18. </OUTPUT JSON SCHEMA>
  19. 标题和内容属性将用于进一步研究。
  20. 确保输出是一个符合上述JSON模式定义的JSON对象。
  21. 只返回JSON对象,不要附加任何解释或额外文本。
  22. """
复制代码
[

段落布局状态
让我们用上述系统提示运行一个示例查询:
  1. response = client.chat.completions.create(
  2.     model="DeepSeek-R1",
  3.     messages=[{"role":"system","content":SYSTEM_PROMPT_REPORT_STRUCTURE},
  4.               {"role":"user","content":"Tell me something interesting about human species"}],
  5.     temperature=1
  6. )
  7. print(response.choices[0].message.content)
复制代码
你将得到雷同以下内容:
  1. [
  2.   {
  3.     "title": "人类适应性的引言",
  4.     "content": "人类拥有独特的适应能力,这在其生存和在各种环境中的主导地位中至关重要。这一引言为探索人类适应性的不同方面奠定了基础。"
  5.   },
  6.   ...
  7.   <REDACTED>
  8.   ...
  9.   {
  10.     "title": "结论:适应性在人类生存中的作用",
  11.     "content": "适应性一直是人类生存和进化的基石,使我们能够面对挑战并探索新的领域,为未来的发展提供了洞见。"
  12.   }
  13. ]
复制代码
  1. 这些JSON标签对我们来说并不友好,因为我们需要将输出转换为Python字典。以下是一个简单的函数,用于移除输出的首行和尾行:
  2. ```python
  3. def clean_json_tags(text):
  4.     return text.replace("```json\n", "").replace("\n```", "")
复制代码
以下是清理后的输出:
  1. json.loads(clean_json_tags(remove_reasoning_from_output(response.choices[0].message.content)))
复制代码
如今,我们可以直接将上述内容作为输入更新到全局状态中。
  1. STATE = State()report_structure = json.loads(clean_json_tags(remove_reasoning_from_output(response.choices[0].message.content)))
  2. for paragraph in report_structure:    STATE.paragraphs.append(Paragraph(title=paragraph["title"], content=paragraph["content"]))
复制代码

第三部分:网络搜索工具

我们将使用Tavily进行网络搜索。你可以在这里获取你的API密钥:Tavily注册。
工具的实现非常简单:
  1. def tavily_search(query, include_raw_content=True, max_results=5):
  2.     tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
  3.     return tavily_client.search(query,
  4.                                 include_raw_content=include_raw_content,
  5.                                 max_results=max_results)
复制代码
每次函数调用将返回最多max_results个网络搜索结果,每个搜索结果将返回:


  • 搜索结果的标题。
  • 搜索结果的网址。
  • 内容摘要。
  • 如果可能的话,返回网页的完备内容。我们盼望获取完备内容以获得最佳结果。

第四部分:规划初次搜索

为了规划初次搜索,我找到了以下提示,它可以或许产生非常一致的结果:
  1. input_schema_first_search = {
  2.     "type": "object",
  3.     "properties": {
  4.         "title": {"type": "string"},
  5.         "content": {"type": "string"}
  6.     }
  7. }
  8. output_schema_first_search = {
  9.     "type": "object",
  10.     "properties": {
  11.         "search_query": {"type": "string"},
  12.         "reasoning": {"type": "string"}
  13.     }
  14. }
  15. SYSTEM_PROMPT_FIRST_SEARCH = f"""
  16. 你是一个深度研究助手。你将获得一个报告段落的标题和预期内容,格式如下所示的JSON模式定义:
  17. <INPUT JSON SCHEMA>
  18. {json.dumps(input_schema_first_search, indent=2)}
  19. </INPUT JSON SCHEMA>
  20. 你可以使用一个网络搜索工具,该工具以“search_query”作为参数。
  21. 你的任务是思考该主题,并提供最优化的网络搜索查询,以丰富你当前的知识。
  22. 按照以下JSON模式定义格式化输出:
  23. <OUTPUT JSON SCHEMA>
  24. {json.dumps(output_schema_first_search, indent=2)}
  25. </OUTPUT JSON SCHEMA>
  26. 确保输出是一个符合上述JSON模式定义的JSON对象。
  27. 只返回JSON对象,不要附加任何解释或额外文本。
  28. """
复制代码
我们在输出模式中要求提供推理内容,只是为了促使模子在查询上进行更多思考。虽然对于推理模子来说这可能有些多余,但对于普通LLM来说可能是个好主意。只管我们在这里使用的是DeepSeek R1,但实在并不愿定需要推理模子。在深度研究代理的第一步中,推理模子主要用于规划陈诉布局。
鉴于我们已经规划了一个包含标题和内容描述的段落列表,我们可以直接将第三部分的输出传递给这个提示,代码如下:
  1. response = client.chat.completions.create(
  2.     model="DeepSeek-R1",
  3.     messages=[{"role":"system","content":SYSTEM_PROMPT_FIRST_SEARCH},
  4.               {"role":"user","content":json.dumps(STATE.paragraphs[0])}],
  5.     temperature=1
  6. )
  7. print(response.choices[0].message.content)
复制代码
STATE.paragraphs[0] 指向第一个段落的状态,此中 research 字段仍然是空的。
我为第一次搜索筹划得到了以下结果:
  1. {
  2.     "search_query": "Homo sapiens characteristics basic biological traits cognitive abilities behavioral traits",
  3.     "reasoning": "To understand the basic characteristics of Homo sapiens, including their biological traits, cognitive abilities, and behavioral patterns."
  4. }
复制代码
我们可以直接将这个查询输入到我们的搜索工具中:
  1. tavily_search("Homo sapiens characteristics basic biological traits cognitive abilities behavioral traits")
复制代码

第五部分:初次总结

初次总结与后续的反思步调不同,因为还没有任何内容可供反思。这一步将生成可供后续反思的内容。以下提示效果不错:
  1. input_schema_first_summary = {
  2.     "type": "object",
  3.     "properties": {
  4.         "title": {"type": "string"},
  5.         "content": {"type": "string"},
  6.         "search_query": {"type": "string"},
  7.         "search_results": {
  8.             "type": "array",
  9.             "items": {"type": "string"}
  10.         }
  11.     }
  12. }
  13. output_schema_first_summary = {
  14.     "type": "object",
  15.     "properties": {
  16.         "paragraph_latest_state": {"type": "string"}
  17.     }
  18. }
  19. SYSTEM_PROMPT_FIRST_SUMMARY = f"""
  20. 你是一个深度研究助手。你将获得一个搜索查询、搜索结果以及你正在研究的报告段落,格式如下所示的JSON模式定义:
  21. <INPUT JSON SCHEMA>
  22. {json.dumps(input_schema_first_summary, indent=2)}
  23. </INPUT JSON SCHEMA>
  24. 你的任务是使用搜索结果撰写段落,使其符合段落主题,并合理地组织结构,以便包含在报告中。
  25. 按照以下JSON模式定义格式化输出:
  26. <OUTPUT JSON SCHEMA>
  27. {json.dumps(output_schema_first_summary, indent=2)}
  28. </OUTPUT JSON SCHEMA>
  29. 确保输出是一个符合上述JSON模式定义的JSON对象。
  30. 只返回JSON对象,不要附加任何解释或额外文本。
  31. """
复制代码
我们需要为LLM提供以下格式的数据:
  1. {
  2.     "title": "标题",
  3.     "content": "内容",
  4.     "search_query": "搜索查询",
  5.     "search_results": []
  6. }
复制代码
我们已经拥有了所需的数据,可以从中构造JSON。假设以下代码运行结果为:
  1. search_results = tavily_search("Homo sapiens characteristics basic biological traits cognitive abilities behavioral traits")
复制代码
那么输入的JSON将如下所示:
  1. input = {
  2.     "title": "人类适应性的引言",
  3.     "content": "人类拥有独特的适应能力,这在其生存和在各种环境中的主导地位中至关重要。这一引言为探索人类适应性的不同方面奠定了基础。",
  4.     "search_query": "Homo sapiens characteristics basic biological traits cognitive abilities behavioral traits",
  5.     "search_results": [result["raw_content"][0:20000] for result in search_results["results"] if result["raw_content"]]
  6. }
复制代码
然后运行以下代码:
  1. response = client.chat.completions.create(
  2.     model="DeepSeek-R1",
  3.     messages=[{"role":"system","content": SYSTEM_PROMPT_FIRST_SUMMARY},
  4.               {"role":"user","content":json.dumps(input)}],
  5.     temperature=1
  6. )
  7. print(remove_reasoning_from_output(response.choices[0].message.content))
复制代码
你将得到雷同以下内容:
  1. {
  2.     "paragraph_latest_state": "Homo sapiens(现代人类所属的物种)是地球上生命进化史中独特而引人入胜的一页。作为Homo属中唯一现存的物种,Homo sapiens以其独特的生物、认知和行为特征与其他灵长类动物和已灭绝的人类近亲区分开来。从生物学上看,人类拥有一个结构复杂且高度发达的大脑,其新皮质相较于我们的祖先有了显著的扩展。这种解剖学上的发展使得人类具备了卓越的认知能力,例如复杂的解决问题能力、抽象思维以及语言和符号交流的能力。从行为学上看,人类展现出复杂的社会结构、文化习俗以及技术创新能力,这些能力对于人类适应多样化环境并繁衍至今至关重要。这些特征共同体现了生物学与行为学之间的复杂相互作用,定义了人类的本质。"
  3. }
复制代码
我们将使用这个结果更新 STATE.paragraphs[0].research.latest_summary 字段。在后续的 第六部分 中,我们将基于这个段落的最新状态进行反思。

第六部分:反思

如今我们已经拥有了陈诉段落内容的最新状态,接下来我们将使用它来改进内容。我们将提示LLM对文本进行反思,并寻找在撰写过程中可能遗漏的任何角度。
以下是一个效果很好的提示:
  1. input_schema_reflection = {
  2.     "type": "object",
  3.     "properties": {
  4.         "title": {"type": "string"},
  5.         "content": {"type": "string"},
  6.         "paragraph_latest_state": {"type": "string"}
  7.     }
  8. }
  9. output_schema_reflection = {
  10.     "type": "object",
  11.     "properties": {
  12.         "search_query": {"type": "string"},
  13.         "reasoning": {"type": "string"}
  14.     }
  15. }
  16. SYSTEM_PROMPT_REFLECTION = f"""
  17. 你是一个深度研究助手。你的任务是为研究报告构建全面的段落内容。你将获得段落标题、计划内容摘要以及你已经创建的段落的最新状态,格式如下所示的JSON模式定义:
  18. <INPUT JSON SCHEMA>
  19. {json.dumps(input_schema_reflection, indent=2)}
  20. </INPUT JSON SCHEMA>
  21. 你可以使用一个网络搜索工具,该工具以“search_query”作为参数。
  22. 你的任务是反思当前段落文本的状态,思考是否遗漏了该主题的某些关键方面,并提供最优化的网络搜索查询,以丰富最新的段落状态。
  23. 按照以下JSON模式定义格式化输出:
  24. <OUTPUT JSON SCHEMA>
  25. {json.dumps(output_schema_reflection, indent=2)}
  26. </OUTPUT JSON SCHEMA>
  27. 确保输出是一个符合上述JSON模式定义的JSON对象。
  28. 只返回JSON对象,不要附加任何解释或额外文本。
  29. """
复制代码
对于当前正在实现的运行,输入将如下所示:
  1. input = {
  2.     "paragraph_latest_state": "Homo sapiens(现代人类所属的物种)是地球上生命进化史中独特而引人入胜的一页。作为Homo属中唯一现存的物种,Homo sapiens以其独特的生物、认知和行为特征与其他灵长类动物和已灭绝的人类近亲区分开来。从生物学上看,人类拥有一个结构复杂且高度发达的大脑,其新皮质相较于我们的祖先有了显著的扩展。这种解剖学上的发展使得人类具备了卓越的认知能力,例如复杂的解决问题能力、抽象思维以及语言和符号交流的能力。从行为学上看,人类展现出复杂的社会结构、文化习俗以及技术创新能力,这些能力对于人类适应多样化环境并繁衍至今至关重要。这些特征共同体现了生物学与行为学之间的复杂相互作用,定义了人类的本质。",
  3.     "title": "引言",
  4.     "content": "人类物种(Homo sapiens)是地球上最独特且引人入胜的物种之一。本节将介绍人类的基本特征,并为探索该物种的有趣方面奠定基础。"
  5. }
复制代码
接下来运行以下代码:
  1. response = client.chat.completions.create(
  2.     model="DeepSeek-R1",
  3.     messages=[{"role":"system","content": SYSTEM_PROMPT_REFLECTION},
  4.               {"role":"user","content":json.dumps(input)}],
  5.     temperature=1
  6. )
  7. print(remove_reasoning_from_output(response.choices[0].message.content))
复制代码
输出结果如下:
  1. {
  2.   "search_query": "Recent research on Homo sapiens evolution, interaction with other human species, and factors contributing to their success",
  3.   "reasoning": "当前段落对Homo sapiens的特征进行了很好的概述,但缺乏对其进化历史以及与其他人类物种相互作用的深入探讨。加入这些主题的最新研究成果,将使段落更加全面,并提供最新的信息。"
  4. }
复制代码
如今,我们运行反思步调中的搜索查询:
  1. search_results = tavily_search("Recent research on Homo sapiens evolution, interaction with other human species, and factors contributing to their success")
复制代码
然后,使用以下代码更新段落的搜索状态:
  1. update_state_with_search_results(search_results, idx_paragraph, state)
复制代码
接下来,我们将反思步调(第6部分)和更新段落状态的步调(第7部分)放入循环中,按照指定的反思次数重复执行。

第七部分:团结反思搜索结果丰富段落的最新状态

在运行反思步调的搜索查询后:
  1. search_results = tavily_search("Recent research on Homo sapiens evolution, interaction with other human species, and factors contributing to their success")
复制代码
我们可以通过以下代码更新段落的搜索历史:
  1. update_state_with_search_results(search_results, idx_paragraph, state)
复制代码
然后,我们将反思步调的搜索结果与段落的最新状态团结起来,进一步丰富段落内容。

第八部分:总结并生成陈诉

我们对每个段落重复执行第4到第7部分的步调。当所有段落的最终状态都准备好后,我们可以将它们拼接在一起,生成完备的陈诉。我们将通过LLM完成这一步,并生成一个格式良好的Markdown文档。以下是提示:
  1. input_schema_report_formatting = {
  2.     "type": "array",
  3.     "items": {
  4.         "type": "object",
  5.         "properties": {
  6.             "title": {"type": "string"},
  7.             "paragraph_latest_state": {"type": "string"}
  8.         }
  9.     }
  10. }
  11. SYSTEM_PROMPT_REPORT_FORMATTING = f"""
  12. 你是一个深度研究助手。你已经完成了研究,并构建了报告中所有段落的最终版本。
  13. 你将获得以下格式的数据:
  14. <INPUT JSON SCHEMA>
  15. {json.dumps(input_schema_report_formatting, indent=2)}
  16. </INPUT JSON SCHEMA>
  17. 你的任务是将报告格式化为Markdown格式。如果报告中缺少结论段落,请从其他段落的最新状态中生成一个结论段落并添加到报告末尾。
  18. """
复制代码
运行以下代码:
  1. report_data = [{"title": paragraph.title, "paragraph_latest_state": paragraph.research.latest_summary} for paragraph in STATE.paragraphs]
  2. response = client.chat.completions.create(
  3.     model="DeepSeek-R1",
  4.     messages=[{"role":"system","content": SYSTEM_PROMPT_REPORT_FORMATTING},
  5.               {"role":"user","content":json.dumps(report_data)}],
  6.     temperature=1
  7. )
  8. print(remove_reasoning_from_output(response.choices[0].message.content))
复制代码
至此,你已经成功生成了一个关于指定主题的深度研究陈诉。

结论

恭喜!你已经从零开始成功实现了一个深度研究代理。
如果你盼望查看更完备的代码实现,可以访问我的GitHub仓库:
GitHub仓库
   原创不易,转发请备注
  此外,还有许多可以改进的地方,以使系统更加稳固:


  • 让系统始终生成格式良好的JSON输出并非易事,因为推理模子在布局化输出方面表现并不理想。
  • 鉴于此,我们可以考虑在系统拓扑的不同任务中使用不同的模子,推理模子主要用于第一步。
  • 我们还可以改进网络搜索的方式以及对检索结果的排序方法。
  • 反思步调的次数可以设置为动态的,让LLM根据需要决定是否需要更多步调。
  • 我们可以在陈诉中返回用于搜索的链接,并为每个段落提供参考来源。
  • ……
   原创不易,转发请备注

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

诗林

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表