AI搜索之问题分解 智谱清言、Felo、360 AI搜索、mindsearch
问题分解是当前大部分AI搜索应用具备的高级搜索功能,通过对用户问题分解、解决、总结,能提供更完备、更准确的搜索结果。
我们测试一个问题案例——“哆啦A梦的作者还有什么别的作品?”,该问题是一个典型的两跳问题,需要做两次推理(第一次是“哆啦A梦的作者是谁?”,第二次是“该作者还有什么别的作品”)
智谱清言
Felo
360 AI搜索
头一次见在AI搜索里插广告的。。。,360厉害了
mindsearch
原理
步骤如下:
- Web planner:将当前问题分解成子问题,利用LLM的代码生成功能,将原问题、子问题建立图谱
- 一旦子问题建立成功,执行web searcher。调用搜索引擎,获得title、url、summary
- 按照title、url、summary信息,利用LLM来选择网页,执行爬虫程序,拿到网页内的所有内容
- 生成单个子问题的回复
- 不断迭代
- 基于所有中间结果,生成最终回答
代码
目前开源的具备问题分解的AI搜索工作非常少,mindsearch是一个,难能可贵。
服务启动
python -m mindsearch.app --lang en --model_format internlm_silicon --search_engine DuckDuckGoSearch --port 18882
建议在硅基流动上新建一个api_key,可免费调用internlm/internlm2_5-7b-chat
API调用
python backend_example.py
agent代码
- 核心代码:MindSearch/mindsearch/agent/init.py
mode = "async" if use_async else "sync"
llm = LLM.get(model_format, {}).get(mode)
if llm is None:
llm_cfg = deepcopy(getattr(llm_factory, model_format))
if llm_cfg is None:
raise NotImplementedError
if use_async:
cls_name = (
llm_cfg["type"].split(".")[-1] if isinstance(
llm_cfg["type"], str) else llm_cfg["type"].__name__)
llm_cfg["type"] = f"lagent.llms.Async{cls_name}"
llm = create_object(llm_cfg)
LLM.setdefault(model_format, {}).setdefault(mode, llm)
date = datetime.now().strftime("The current date is %Y-%m-%d.")
plugins = [(dict(
type=AsyncWebBrowser if use_async else WebBrowser,
searcher_type=search_engine,
topk=6,
secret_id=os.getenv("TENCENT_SEARCH_SECRET_ID"),
secret_key=os.getenv("TENCENT_SEARCH_SECRET_KEY"),
) if search_engine == "TencentSearch" else dict(
type=AsyncWebBrowser if use_async else WebBrowser,
searcher_type=search_engine,
topk=6,
api_key=os.getenv("WEB_SEARCH_API_KEY"),
))]
agent = (AsyncMindSearchAgent if use_async else MindSearchAgent)(
llm=llm,
template=date,
output_format=InterpreterParser(
template=GRAPH_PROMPT_CN if lang == "cn" else GRAPH_PROMPT_EN),
searcher_cfg=dict(
llm=llm,
plugins=plugins,
template=date,
output_format=PluginParser(
template=searcher_system_prompt_cn
if lang == "cn" else searcher_system_prompt_en,
tool_info=get_plugin_prompt(plugins),
),
user_input_template=(searcher_input_template_cn if lang == "cn"
else searcher_input_template_en),
user_context_template=(searcher_context_template_cn if lang == "cn"
else searcher_context_template_en),
),
summary_prompt=FINAL_RESPONSE_CN
if lang == "cn" else FINAL_RESPONSE_EN,
max_turn=10,
)
- Web Planner
对应的prompt为
output_format=InterpreterParser(
template=GRAPH_PROMPT_CN if lang == "cn" else GRAPH_PROMPT_EN),
output_format
的作用原理是联合聊天历史、template等信息构成prompt,送到LLM中
class StreamingAgent(StreamingAgentMixin, Agent):
"""Base streaming agent class"""
def forward(self, *message: AgentMessage, session_id=0, **kwargs):
formatted_messages = self.aggregator.aggregate(
self.memory.get(session_id),
self.name,
self.output_format,
self.template,
)
for model_state, response, _ in self.llm.stream_chat(
formatted_messages, session_id=session_id, **kwargs
):
yield AgentMessage(
sender=self.name,
content=response,
formatted=self.output_format.parse_response(response),
stream_state=model_state,
) if self.output_format else (model_state, response)
GRAPH_PROMPT_CN
的具体内容为
GRAPH_PROMPT_CN = """## 人物简介
你是一个可以利用 Jupyter 环境 Python 编程的程序员。你可以利用提供的 API 来构建 Web 搜索图,最终生成代码并执行。
## API 介绍
下面是包含属性详细说明的 `WebSearchGraph` 类的 API 文档:
### 类:`WebSearchGraph`
此类用于管理网络搜索图的节点和边,并通过网络代理进行搜索。
#### 初始化方法
初始化 `WebSearchGraph` 实例。
**属性:**
- `nodes` (Dict[str, Dict[str, str]]): 存储图中所有节点的字典。每个节点由其名称索引,并包含内容、类型以及其他相关信息。
- `adjacency_list` (Dict[str, List[str]]): 存储图中所有节点之间连接关系的邻接表。每个节点由其名称索引,并包含一个相邻节点名称的列表。
#### 方法:`add_root_node`
添加原始问题作为根节点。
**参数:**
- `node_content` (str): 用户提出的问题。
- `node_name` (str, 可选): 节点名称,默认为 'root'。
#### 方法:`add_node`
添加搜索子问题节点并返回搜索结果。
**参数:
- `node_name` (str): 节点名称。
- `node_content` (str): 子问题内容。
**返回:**
- `str`: 返回搜索结果。
#### 方法:`add_response_node`
当前获取的信息已经满足问题需求,添加回复节点。
**参数:**
- `node_name` (str, 可选): 节点名称,默认为 'response'。
#### 方法:`add_edge`
添加边。
**参数:**
- `start_node` (str): 起始节点名称。
- `end_node` (str): 结束节点名称。
#### 方法:`reset`
重置节点和边。
#### 方法:`node`
获取节点信息。
```python
def node(self, node_name: str) -> str
```
**参数:**
- `node_name` (str): 节点名称。
**返回:**
- `str`: 返回包含节点信息的字典,包含节点的内容、类型、思考过程(如果有)和前驱节点列表。
## 任务介绍
通过将一个问题拆分成能够通过搜索回答的子问题(没有关联的问题可以同步并列搜索),每个搜索的问题应该是一个单一问题,即单个具体人、事、物、具体时间点、地点或知识点的问题,不是一个复合问题(比如某个时间段), 一步步构建搜索图,最终回答问题。
## 注意事项
1. 注意,每个搜索节点的内容必须单个问题,不要包含多个问题(比如同时问多个知识点的问题或者多个事物的比较加筛选,类似 A, B, C 有什么区别,那个价格在哪个区间 -> 分别查询)
2. 不要杜撰搜索结果,要等待代码返回结果
3. 同样的问题不要重复提问,可以在已有问题的基础上继续提问
4. 添加 response 节点的时候,要单独添加,不要和其他节点一起添加,不能同时添加 response 节点和其他节点
5. 一次输出中,不要包含多个代码块,每次只能有一个代码块
6. 每个代码块应该放置在一个代码块标记中,同时生成完代码后添加一个<|action_end|>标志,如下所示:
<|action_start|><|interpreter|>```python
# 你的代码块
```<|action_end|>
7. 最后一次回复应该是添加node_name为'response'的 response 节点,必须添加 response 节点,不要添加其他节点
"""
期望LLM生成图结构的问题解决代码,实现原问题、子问题之间的依赖,具体的图结构实现见MindSearch/mindsearch/agent/graph.py)
- Web Searcher
相关的配置为
searcher_cfg=dict(
llm=llm,
plugins=plugins,
template=date,
output_format=PluginParser(
template=searcher_system_prompt_cn
if lang == "cn" else searcher_system_prompt_en,
tool_info=get_plugin_prompt(plugins),
),
user_input_template=(searcher_input_template_cn if lang == "cn"
else searcher_input_template_en),
user_context_template=(searcher_context_template_cn if lang == "cn"
else searcher_context_template_en),
),
Web Searcher的prompt为searcher_system_prompt_cn
,具体指
searcher_system_prompt_cn = """## 人物简介
你是一个可以调用网络搜索工具的智能助手。请根据"当前问题",调用搜索工具收集信息并回复问题。你能够调用如下工具:
{tool_info}
## 回复格式
调用工具时,请按照以下格式:
```
你的思考过程...<|action_start|><|plugin|>{{"name": "tool_name", "parameters": {{"param1": "value1"}}}}<|action_end|>
```
## 要求
- 回答中每个关键点需标注引用的搜索结果来源,以确保信息的可信度。给出索引的形式为`[[int]]`,如果有多个索引,则用多个[[]]表示,如`[[id_1]][[id_2]]`。
- 基于"当前问题"的搜索结果,撰写详细完备的回复,优先回答"当前问题"。
"""
将该prompt送到LLM中,促使LLM具备工具调用功能,调用plugin工具。plugin工具的定义为:
plugins = [(dict(
type=AsyncWebBrowser if use_async else WebBrowser,
searcher_type=search_engine,
topk=6,
secret_id=os.getenv("TENCENT_SEARCH_SECRET_ID"),
secret_key=os.getenv("TENCENT_SEARCH_SECRET_KEY"),
) if search_engine == "TencentSearch" else dict(
type=AsyncWebBrowser if use_async else WebBrowser,
searcher_type=search_engine,
topk=6,
api_key=os.getenv("WEB_SEARCH_API_KEY"),
))]
这个工具是lagent/lagent/actions/web_browser.py/lagent,能够实现网页搜索。
mindsearch和lagent
Mindsearch代码和lagent耦合程度太深了,比如web_browser直接用lagent里实现的,但lagent是作为一个库供mindsearch使用的,如果想新增搜索源,需要看lagent的底层代码,一层一层改。。。
总结
子问题分解是AI搜索的一个较核心功能,在大多数AI搜索应用中都有体现。Mindsearch借助lagent智能体框架,完成了子问题分解的初步实现,具备较强的借鉴意义。但mindsearch和lagent的代码耦合程度太高,较难自定义,也是在实现层面需要注意的问题。
最令人震惊的是,360 AI搜索居然有广告,可谓是AI搜索商业化排头兵!!!