Skip to content

Latest commit

 

History

History
661 lines (508 loc) · 18.9 KB

L2G6000.md

File metadata and controls

661 lines (508 loc) · 18.9 KB

1. 在 codespace 上部署

1.1 配环境

conda create -n mindsearch python=3.10 -y
conda init

conda activate mindsearch

cd /workspaces/codespaces-blank
git clone https://github.com/InternLM/MindSearch.git && cd MindSearch && git checkout ae5b0c5

pip install -r requirements.txt # 这个requirements.txt需要修改,见附录

1.2 启动前后端

先启动后端

export SILICON_API_KEY=<API KEY>
conda activate mindsearch
cd /workspaces/codespaces-blank/MindSearch
python -m mindsearch.app --lang cn --model_format internlm_silicon --search_engine DuckDuckGoSearch --asy

再启动前端

conda activate mindsearch
cd /workspaces/codespaces-blank/MindSearch
python frontend/mindsearch_gradio.py

1.3 运行的效果

通过 duckduckgo 搜索:

国际象棋世界棋王赛的最新赛况

image

image


通过brave search API搜索(申请API,设置环境变量WEB_SEARCH_API_KEY):

image

export WEB_SEARCH_API_KEY=BSAU***
python -m mindsearch.app --lang cn --model_format internlm_silicon --search_engine BraveSearch --asy
俄罗斯大贝塔是什么梗

image

2. 部署到HF的space

直接 duplicate 官方的space,即可。

我的个人账户的空间地址是

https://huggingface.co/spaces/hhhhhjjjjj/MindSearch

image

但是依然存在requirements.txt的问题需要修改,具体原因见附录。

需要duplicate 官方的space之后,修改requirements.txt,然后会自动重新构建space。

另外,对于某些比较复杂的问题,可能会调用过多查询,超过了搜索API接口的速率限制,导致报错

附录

附录1:lagent依赖报错

一句话说,0.5.0rc2 版本的lagent调用搜索引擎API会报错,而且问题已经被后序PR解决。 所以 requirements.txt 指定安装了0.5.0rc2 版本的lagent会导致报错,我们要安装这个PR之后的lagent。

对于 requirements.txt:

- lagent==0.5.0rc2
# 使用主分支最新版本
+ lagent @ git+https://github.com/InternLM/lagent.git
# 或者甚至指定安装某个commit
+ lagent @ git+https://github.com/InternLM/lagent.git@bcc62ed

下面是具体的报错信息:

如果你使用的是 duckduckgo 的API,你会看到:

报错:API 方法名不匹配

AttributeError: 'AsyncDDGS' object has no attribute 'atext'. Did you mean: 'text'?
  • 原因:代码使用了旧的 API 方法名 atext,但新版本已经改成了 text
  • 本质:这是一个典型的 API breaking change 问题,库的接口发生变化导致旧代码不兼容
  • 解决方法:在 lagent 库的源代码中,将 atext 改为 text

如果你使用的是brave search 的API,你会看到:

TypeError: Invalid variable type: value should be str, int or float, got True of type <class 'bool'>

这是一个类型错误,出现在使用 lagent 库调用 Brave Search API 时。

问题原因:

  • lagent 库在构建 API 请求时,直接使用了布尔值作为参数(extra_snippets: True
  • 但底层的 yarl 库(用于构建 URL)只接受字符串、整数或浮点数作为参数值
  • 这是一个依赖包兼容性问题

附录2:how it works?

1. 核心架构组件

MindSearch采用多代理(Multi-Agent)框架,主要包含两个核心组件:

  • 采用WebPlanner和WebSearcher两类代理协同工作
  • WebPlanner负责高层规划,WebSearcher执行具体搜索
  • 使用有向无环图(DAG)建模问题求解过程
  • 支持子任务的并行执行

1.1 WebPlanner

  • 功能:高级规划器,负责任务分解和推理编排
  • 实现要点:
    • 将复杂问题建模为有向无环图(DAG)结构
    • 使用代码生成方式构建推理图
    • 支持并行任务分发
    • 通过图构建动态管理上下文

1.2 WebSearcher

  • 功能:执行具体的网络搜索和信息提取
  • 实现要点:
    • 采用分层检索策略(Hierarchical Retrieval)
    • 包含查询重写、内容聚合、页面选择等步骤
    • 支持多搜索引擎API(Google, Bing, DuckDuckGo)

2. 关键工作流程

2.1 查询分解与规划

  1. WebPlanner接收用户查询
  2. 将查询分解为原子级子问题
  3. 构建DAG表示问题求解路径
  4. 生成Python代码实现图的动态构建

2.2 并行信息检索

  1. WebSearcher接收子问题
  2. 生成多个相似查询扩展搜索范围、查询重写
  3. 调用搜索API获取初步结果
  4. 基于URL合并搜索结果
  5. 选择最相关页面深入分析

2.3 上下文管理

  1. 在代理间显式传递上下文状态
  2. 使用图边关系处理上下文依赖
  3. 为每个WebSearcher添加父节点和根节点响应前缀
  4. 保持子任务焦点的同时维护整体上下文

3. 一些细节

  1. 分层检索是什么
  • 想象成"三重过滤"系统:
    • 第一层(粗筛):快速浏览搜索结果的标题和简介,筛选出可能相关的页面
    • 第二层(精筛):对筛选出的页面进行更详细的分析,确定真正相关的内容
    • 第三层(深度分析):只对最相关的页面进行完整内容的下载和深入分析

image

举个例子:如果你搜索"苹果公司最新iPhone"

  • 第一层可能返回100个结果
  • 第二层筛选出20个相关度高的页面
  • 第三层可能只分析5个最权威的源网页
  1. 基于URL合并是什么
  • 目的:去除重复内容,整合相似信息
  • 工作方式:
    例如搜索返回这些URL:
    - news.com/iphone15-review
    - news.com/iphone15-review?source=home
    - othernews.com/iphone15-review
    
    系统会:
    1. 识别出前两个URL指向同一内容
    2. 只保留一个版本
    3. 合并不同网站的补充信息
    
  1. 上下文管理是怎么工作的? 来看一个具体例子:

image

在这个例子中:

  1. 显式传递上下文:

    原始问题: "分析iPhone 15的优缺点"
    子任务A: "总体评价" + 原始问题
    子任务B: "优点分析" + 原始问题
    子任务C: "缺点分析" + 原始问题
    

    每个子任务都知道自己在解决什么大问题

  2. 图边关系处理:

    • B1(性能提升)知道自己是B(优点分析)的一部分
    • C1(价格)知道自己是C(缺点分析)的一部分
  3. 父节点和根节点响应:

    当C2(续航)执行时,它知道:
    - 根节点是"iPhone 15分析"
    - 父节点是"缺点分析"
    - 自己的任务是"分析续航问题"
    
  4. 保持焦点同时维护整体:

    • 每个小任务专注于自己的部分(如"分析续航")
    • 但回答时会考虑整体上下文(这是在分析iPhone 15的缺点)
    • 避免答非所问或失去重点

这种设计让系统能够:

  1. 准确理解每个子任务的目标
  2. 保持回答的连贯性
  3. 避免重复工作
  4. 产生结构化的完整答案

这样的设计类似于人类写研究报告:

  • 知道整体是什么(报告主题)
  • 明确自己负责哪部分(具体章节)
  • 理解和其他部分的关系(章节关联)
  • 既能专注细节又不丢失整体视角

4. 后端文件逻辑

简单分析 /MindSearch/mindsearch/app.py 的逻辑。

主要组件

  • FastAPI作为Web框架
  • EventSourceResponse用于流式响应
  • janus.Queue用于异步/同步队列转换
  • 支持CORS跨域请求
  1. 输入阶段 当你输入一个问题,比如"分析iPhone 15的优缺点":
# 通过 /solve 接口接收你的问题
@app.post('/solve')
async def run(request: GenerationParams):
    inputs = request.inputs  # 这里接收到你的问题
    # 初始化核心处理引擎
    agent = init_agent(lang=args.lang, 
                      model_format=args.model_format,
                      search_engine=args.search_engine)
  1. 处理阶段
# 开始处理你的问题
for response in agent.stream_chat(inputs):
    # agent.stream_chat 是核心处理过程,它会:
    # 1. 分解你的问题(比如拆分为"性能"、"相机"、"电池"等子问题)
    # 2. 对每个子问题进行网络搜索
    # 3. 分析搜索结果
    # 4. 整合信息形成答案
  1. 实时反馈 系统会实时返回处理进度:
# 每当有新的进展,就会通过这个机制返回给你
response_json = json.dumps({
    'response': agent_return,  # 当前的处理结果
    'current_node': node_name  # 正在处理哪个部分
})
yield {'data': response_json}

MindSearch的核心魔法在 agent.stream_chat(inputs) 内部。

真正的核心在 init_agent() 创建的 agent 对象中,这才是 MindSearch 的"大脑":

image

MindSearch的核心流程是:

  1. WebPlanner大脑
# 比如你问:"分析iPhone 15的优缺点"
WebPlanner会思考- "需要查什么?" -> 拆解成多个方面
- "如何查?" -> 设计搜索策略
- "查完后怎么整合?" -> 规划结果处理
  1. WebSearcher执行器
# WebPlanner规划后,WebSearcher负责:
- 并行执行多个搜索任务
- 对每个搜索结果进行筛选
- 提取有价值的信息
  1. 信息整合
# 所有搜索完成后:
- 整合各个方面的信息
- 去除重复内容
- 组织成连贯的答案

/MindSearch/mindsearch/app.py 这个文件是一个"调度员":

  • 它负责接收用户问题
  • 启动处理流程
  • 实时反馈结果
  • 但真正的"智慧"在 Agent 内部

如果想了解 MindSearch 最核心的部分,我们应该看 Agent 的代码。

5. agent逻辑

文件在这里:

(mindsearch) root@intern-studio-50007388:~/mindsearch/MindSearch/mindsearch/agent# ls
init.py  mindsearch_agent.py  mindsearch_prompt.py  models.py

这些就是核心代码文件,解释一下每个文件的作用:

  1. mindsearch_agent.py - 这是最核心的实现文件
这里包含了:
- WebPlanner的实现:如何分析问题、规划搜索
- WebSearcher的实现:如何执行搜索、处理结果
- 整个Agent的协调逻辑
  1. mindsearch_prompt.py - 整个过程用到的全部LLM提示词
  2. models.py - 是LLM的接口规范
  3. init.py - 这是包的初始化文件,负责导出主要的类和函数和配置初始化

MindSearch的核心工作机制:

image

从代码来看,MindSearch是由两个主要组件构成的:

  1. WebPlanner (大脑)
class MindSearchAgent(BaseAgent):
    def stream_chat(self, message, **kwargs):
        # 1. 接收用户问题
        # 2. 构建搜索图(把大问题分解成小问题)
        # 3. 协调多个WebSearcher执行搜索
        # 4. 整合结果形成最终答案
  1. WebSearcher (搜索执行器)
class SearcherAgent(Internlm2Agent):
    def stream_chat(self, question, root_question=None, parent_response=None):
        # 1. 执行具体的搜索任务
        # 2. 分析搜索结果
        # 3. 返回答案给Planner

工作流程是这样的:

  1. 问题接收
def init_agent(lang='cn', model_format='internlm_server',search_engine='DuckDuckGoSearch'):
    # 初始化大模型和搜索引擎
    # 设置提示词模板(中文/英文)
    # 配置搜索参数
  1. 问题分解(使用 WebSearchGraph):
class WebSearchGraph:
    def add_root_node(self, node_content):
        # 添加主问题
    
    def add_node(self, node_name, node_content):
        # 添加子问题节点
        # 启动搜索线程处理子问题
  1. 并行搜索
# 在WebSearchGraph中
def model_stream_thread():
    agent = SearcherAgent(**self.searcher_cfg)
    for answer in agent.stream_chat(
            node_content,  # 子问题
            self.nodes['root']['content'],  # 原始问题
            parent_response=parent_response):  # 前序节点的答案
        # 返回搜索结果
  1. 结果整合
# 在MindSearchAgent中
def _generate_reference(self, agent_return, code, as_dict):
    # 收集所有搜索结果
    # 整理引用信息
    # 生成最终答案

以一个实际例子来说明:

假设用户问:"分析iPhone 15的优缺点"

  1. WebPlanner会:
# 1. 创建搜索图
graph.add_root_node("分析iPhone 15的优缺点")

# 2. 分解子问题
graph.add_node("性能分析", "iPhone 15的性能参数和实测表现如何?")
graph.add_node("相机评测", "iPhone 15的相机系统有什么特点?")
graph.add_node("续航分析", "iPhone 15的电池续航表现如何?")
  1. WebSearcher会:
  • 并行搜索每个子问题
  • 分析搜索结果
  • 返回有价值的信息
  1. 最后:
  • 整合所有搜索结果
  • 生成结构化的完整答案
  • 附带信息来源引用

这就是为什么MindSearch能快速处理复杂问题 - 它通过多代理并行处理,像团队合作一样高效工作。


整理 mindsearch_prompt.py 中的提示词。这些提示词可以分为三大核心部分:

image

  1. 搜索者角色提示词 (Searcher System Prompt)
# 中英文两个版本,主要包含:
searcher_system_prompt_cn/en = """
A. 角色定义
- 你是一个可以调用网络搜索工具的智能助手

B. 工具说明
- 可用工具列表:{tool_info}

C. 回复格式
- 调用工具的格式:
<|action_start|><|plugin|>{"name": "tool_name", "parameters": {"param1": "value1"}}<|action_end|>

D. 具体要求
1. 每个关键点需标注引用来源 [[1]][[2]]
2. 回答要基于搜索结果,详细完备
"""
  1. 图构建提示词 (Graph Prompt)
GRAPH_PROMPT_CN/EN = """
A. API文档
- WebSearchGraph类的详细说明
- 所有可用方法的参数和返回值

B. 任务说明
- 如何将问题拆分成子问题
- 如何构建搜索图
- 如何步骤化处理问题

C. 注意事项
1. 每个搜索节点必须是单一问题
2. 不要杜撰搜索结果
3. 避免重复提问
4. response节点单独添加
5. 代码块规范
"""
  1. 最终回答提示词 (Final Response Prompt)
FINAL_RESPONSE_CN/EN = """
A. 格式要求
- 标注引用来源 [[id]]
- 不使用模糊表达

B. 内容规范
- 逻辑清晰,层次分明
- 内容全面且完备
- 不包含原始问答对

C. 风格指导
- 专业严谨
- 避免口语化
- 保持一致性
"""
  1. 模板和示例
# 输入模板
searcher_input_template = """
## 主问题/Final Problem
{topic}
## 当前问题/Current Problem
{question}
"""

# 上下文模板
searcher_context_template = """
## 历史问题/Historical Problem
{question}
回答/Answer:{answer}
"""

# 搜索示例
fewshot_example = """
包含具体的搜索和选择示例:
1. 如何搜索
2. 如何选择结果
"""

这些提示词共同构成了一个完整的指导系统:

  1. 搜索者提示词:指导如何搜索和处理信息
  2. 图构建提示词:指导如何分解问题和构建搜索路径
  3. 最终回答提示词:确保输出高质量的答案

每种提示词都有中英文版本,并且包含详细的格式要求和示例。


让我通过分析这两个核心类来解释MindSearch的工作原理:

  1. MindSearchAgent (总指挥官)
class MindSearchAgent(BaseAgent):
    def __init__(self, llm, searcher_cfg, protocol=MindSearchProtocol(), max_turn=10):
        self.local_dict = {}  # 存储本地变量
        self.llm = llm        # 大语言模型
        self.max_turn = max_turn  # 最大对话轮次
        WebSearchGraph.searcher_cfg = searcher_cfg  # 搜索配置

核心方法是 stream_chat,工作流程:

image

def stream_chat(self, message, **kwargs):
    # 1. 初始化会话
    inner_history = message[:]
    agent_return = AgentReturn()
    
    # 2. 主循环(最多max_turn轮)
    for _ in range(self.max_turn):
        # 2.1 生成提示词
        prompt = self._protocol.format(inner_step=inner_history)
        
        # 2.2 获取LLM响应
        for model_state, response, _ in self.llm.stream_chat(prompt):
            # 处理响应
            _, language, action = self._protocol.parse(response)
            code = action['parameters']['command'] if action else ''
            
        # 2.3 如果有代码要执行
        if code:
            # 执行代码(通常是构建和更新搜索图)
            yield from self._process_code(agent_return, inner_history,
                                      code, as_dict, return_early)
        else:
            # 如果没有代码,说明回答完成
            agent_return.state = AgentStatusCode.END
            yield deepcopy(agent_return)
            return
  1. SearcherAgent (搜索执行者)
class SearcherAgent(Internlm2Agent):
    def __init__(self, template='{query}', **kwargs):
        super().__init__(**kwargs)
        self.template = template
        
    def stream_chat(self, question, root_question=None, parent_response=None):
        # 1. 构建搜索查询
        message = self.template['input'].format(
            question=question,
            topic=root_question
        )
        
        # 2. 如果有父节点的响应,添加上下文
        if parent_response:
            if 'context' in self.template:
                parent_response = [
                    self.template['context'].format(**item)
                    for item in parent_response
                ]
                message = '\n'.join(parent_response + [message])
                
        # 3. 执行搜索并处理结果
        for agent_return in super().stream_chat(message):
            agent_return.type = 'searcher'
            agent_return.content = question
            yield deepcopy(agent_return)
  1. 关键机制说明

  2. 问题处理机制

  • MindSearchAgent接收问题
  • 使用LLM分析和分解问题
  • 通过代码构建搜索图
  1. 并行搜索机制
  • 每个子问题创建一个SearcherAgent
  • 多个SearcherAgent并行工作
  • 通过线程池管理搜索任务
  1. 结果整合机制
  • 使用队列收集搜索结果
  • 维护搜索图的状态
  • 最终生成完整答案
  1. 状态管理
  • 跟踪每个节点的状态
  • 管理搜索任务的生命周期
  • 确保结果的完整性

这就是MindSearch的核心工作方式:它像一个指挥官(MindSearchAgent)带领多个搜索专家(SearcherAgent),共同完成信息搜索和整合的任务。每个组件都有明确的职责,通过精心的协调实现高效的并行处理。