面向上下文工程的智能搜索
本文是我在2026年4月8日于伦敦举办的 AI Engineer Europe 2026 上发表的题为"面向上下文工程的智能搜索"研讨会的一个编辑过的长篇版本。幻灯片、代码和图表可在 研讨会仓库 中找到。如果你更喜欢视频,完整录像可在 YouTube 上观看:
如果你之前构建过智能体,你就会知道上下文工程是让智能体返回有意义响应的关键部分。
上下文工程 是决定从所有可能的上下文源中,哪些内容实际进入智能体的上下文窗口,以便 LLM 生成最佳响应的过程。这也称为"上下文策展"过程,在下图中描述为从可能的上下文源到上下文窗口的箭头。
我认为我们没有给予这个上下文策展箭头足够的信任,因为它几乎完成了所有繁重的工作。它背后隐藏的是一组智能体可以决定使用的搜索工具。这就是为什么我的个人热门观点是:
上下文工程大约80%是智能搜索。
1、历史回顾
让我们退后一步,看看搜索在 AI 技术栈中在过去三年是如何从 检索增强生成 (RAG) 发展到智能搜索/智能 RAG,再到现在的上下文工程的。(如果你感兴趣,我还写过关于 从 RAG 到智能体记忆的演进 的文章。)
2、检索增强生成
当我们最初在2024年开始使用 LLM 构建应用时,我们开始实现具有固定检索管道的 RAG 系统:
- 用户消息被或多或少地逐字用作搜索查询(通常是向量搜索)。
- 从数据库中一次性拉取文本块。
- 检索到的块与用户消息结合在提示中,然后输入给 LLM。
这种设计简单直接,对于狭窄的问答场景仍然有效。但它也会以可预测的方式出现问题,因为你只检索一次:
- 即使模型不需要外部上下文,你也会检索一次,不相关的块可能会混淆它。
- 你只检索一次,没有选项来纠正查询。如果返回的结果不包含相关信息,你需要运行第二次搜索怎么办?
- 即使问题需要多跳检索,你也只检索一次。例如,第一批块可能只告诉你接下来要搜索什么,但管道永远不会运行第二次传递。
3、智能 RAG
为了克服这些限制,我们用搜索工具替换了固定管道,并称之为"智能 RAG"、"智能检索"或"智能搜索"。在这种场景下,智能体决定是否调用搜索工具、结果是否相关、是否需要检索更多内容,以及是否重写搜索查询。
在许多设置中,这很简单,因为你通常只有有限数量的上下文源和一个检索工具。
4、上下文工程
在上下文工程中,我们现在有许多不同的上下文源:
- 本地文件(例如你的代码仓库、带
plan.md或todo.md文件的便签本,或智能体技能), - 数据库(例如存储大规模企业数据),
- 网络,
- 长期记忆(我故意在这里保持模糊,因为 记忆是存储在文件中还是数据库中仍在讨论中)
根据上下文源的不同,我们通常有原生搜索工具:
- 本地文件的文件搜索,
- 智能体技能的工具加载,
- 数据库的专用数据库工具(语义搜索或查询执行),
- 网络的网络搜索,以及
- 长期记忆的记忆工具。
如果这还不够让人头疼,我们现在还有一个允许智能体运行终端命令的工具。这个工具有许多不同的名称:LangChain 称之为"shell 工具",Anthropic 称之为"bash 工具",OpenClaw 称之为"exec 工具"。在下文中,我们将称之为 "shell 工具"。
Shell 工具是一个多功能工具,因为它可以与大多数上下文源交互:它可以针对本地文件运行(ls、grep)、数据库(CLI、脚本、curl 到 HTTPS API)和网络(curl)。
这就是为什么目前有很多讨论,争论一个 shell 工具是否是智能体所需的全部。如果你对深入了解这个讨论感兴趣,我在一篇博客中写过,标题是 "Shell 工具不是上下文工程的银弹"。那篇博客的 TLDR 和本次研讨会的核心主题是,实践问题是 你的技术栈中应该包含哪些搜索工具,而不是 shell 工具与其他工具的对立。
如果你从本次研讨会中只记住一件事,那就是:
做好搜索是困难的。这就是为什么我们有这么多不同的搜索技术,以及为什么你要为你的延迟和质量要求策展一个技术栈。
在本次研讨会中,我们将介绍一小部分搜索工具,让你了解它们的优缺点。
5、构建搜索工具的基础知识
在我们深入了解一些具体的搜索工具之前,让我们回顾一下构建有效搜索工具的基础知识。
6、智能搜索的挑战
从理论上讲,智能搜索看起来很简单:
- 用户提出请求
- 智能体使用正确的参数调用正确的工具
- 工具返回搜索结果
- 智能体用正确答案回应。
但实际上,这可能会以许多不同的方式出现问题。在 Elastic,我们帮助团队在 Elasticsearch 数据上构建智能体,这些是我们观察到的三个最常见的失败模式:
- 智能体根本不调用任何工具,而是用参数知识回答。
- 智能体调用了错误的工具(例如,网络搜索而不是公司索引)。
- 智能体使用错误的参数调用了正确的工具。
让我们讨论一些克服这些挑战的一般最佳实践,这样当下我在下一节演示它们时,你就会知道该怎么做。如果你对更多最佳实践感兴趣,我还写了一篇关于 我们在 Elastic 构建有效数据库检索工具所学到的知识 的指南。
7、工具描述
我不喜欢这张幻灯片,因为我觉得每个人都知道 工具描述是任何工具最重要的部分。但是,我要快速给你复习一下,因为任何时候我看到工具描述,都是最少付出的一行描述,然后你就会想知道为什么你的智能体无法使用它。
诚然,这个模板相当长。我并不是说你需要始终遵循这个模板。相反,从一个清晰的核心目的和触发条件开始。当您注意到您的智能体难以使用您的工具时,开始添加这个模板的更多组件,例如操作、关系、限制和示例。
如果这还不够,在系统提示中重复相同的规则。
8、参数复杂性
另一个需要关注的方面是参数复杂性。因为一旦选择了正确的工具,智能体仍然需要填写参数,有些参数比其他参数更容易正确。
例如,当 ID 在消息中时,工具 get_customer_by_id 的参数很容易填写。或者接受任何字符串作为搜索查询的语义搜索工具通常没问题。但是当你开始添加更多参数(如过滤器或 top_k)时,LLM 生成有效参数可能会变得更加困难。但是如果你有一个通用搜索工具,让你的智能体从头开始编写完整的 SQL 或 ES|QL 查询作为参数呢?大多数模型在生成有效 SQL 查询方面表现不错,但这增加了失败的风险。
9、代码演练
让我们看看搜索工具在实践中的一个小选择实现。(我很想向你们展示所有这些,但时间有限。)你可以在 研讨会仓库 中找到完整的代码。
本代码演练使用 LangChain 进行编排,因为它抽象了很多复杂性,这样我们就可以专注于高级概念,而且还因为它开箱即用地提供了 shell 工具和其他有用的东西。
对于演示,我们使用 AI Engineer Europe 2026 日程 数据集进行搜索。
前两个演示使用本地 Elasticsearch 集群,你可以使用 start-local 脚本启动。第三个从磁盘读取相同数据。为了专注于搜索工具的核心机制,我们将跳过精确的数据准备和摄取步骤,这些在笔记本 00_prepare_data.ipynb 中可用。
10、原始智能搜索
让我们先刷新一下在第一个笔记本 01_vanilla_agentic_search.ipynb 中实现的最小原始智能搜索设置的记忆。它由一个针对 conference_schedule 索引的语义搜索工具组成,每个会话包含一个文档。
首先,我们设置一个 LLM,它是任何智能体的核心。这里我们通过 LiteLLM 使用 gpt-5.4-nano:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
openai_api_base=LITELLM_API_BASE,
api_key=LITELLM_API_KEY,
model="llm-gateway/gpt-5.4-nano",
temperature=0.5,
)
然后我们定义一个最小的系统提示,告诉智能体它是一个搜索智能体、何时检索以及索引的结构:
You are a search agent tasked with answering questions about the AI Engineer Europe 2026 Conference.
You have access to different context retrieval tools to help you answer user queries.
Before answering a question decide whether or not you need to retrieve additional context to answer the question correctly.
If the retrieved context does not contain relevant information to answer the query, say that you don't know.
## Elasticsearch (`conference_schedule`)
The conference sessions are indexed in Elasticsearch. One document per session.
| Field | Description |
|--------------|------------|
| `text` | The string that was embedded: each session's title plus description (blank line between them). It does not include day, time, room, or speakers. |
| `vector` | Dense embedding of `text`, used for similarity search. |
| `metadata` | Structured fields (see metadata description below) |
| Field | Description |
|--------------|------------|
| `metadata.title` | Title of the session|
| `metadata.day` | Date of the session (Example format: April 10) |
| `metadata.time` | Time slot of the session (Example format: 12:40-1:00pm) |
| `metadata.room` | Room where the session takes place |
| `metadata.type` | One of 'keynote', 'workshop', 'talk', 'track_keynote', 'lightning', 'expo_session' |
| `metadata.track` | Track |
| `metadata.speakers` | Name(s) of the speaker(s) as a single comma-separated string |
接下来,我们准备 Elasticsearch 客户端。为此,我们准备 jina-embeddings-v5-text-small 作为嵌入模型,它在搜索时对查询进行嵌入,然后连接到我们的 ElasticsearchStore:
from langchain_community.embeddings import JinaEmbeddings
from langchain_elasticsearch import ElasticsearchStore
embeddings = JinaEmbeddings(
jina_api_key=JINA_API_KEY,
model_name="jina-embeddings-v5-text-small",
)
vector_store = ElasticsearchStore(
es_url=ES_URL,
es_user=ES_USERNAME,
es_password=ES_PASSWORD,
index_name="conference_schedule",
embedding=embeddings,
)
接下来是有趣的部分:让我们定义搜索工具,它接受搜索查询,运行语义搜索,并返回前 3 个结果。
LangChain 的 @tool 装饰器让我们将任何 Python 函数转换为智能体工具,默认使用函数名作为工具名,文档字符串作为描述。
如下所示,我已经违反了我之前关于工具描述的个人建议,因为这里我只使用了一个简短的文档字符串。然而,在这种情况下,这已经足够了,因为我们只有一个工具和一个原始的演示用例。
from langchain.tools import tool
@tool()
def semantic_search_conference_sessions(query: str) -> str:
"""Runs a semantic search query to find conference sessions by concept or topic.
Args:
query: The topic or concept used as search query to search for.
Returns:
A string containing the information about the sessions.
"""
docs = vector_store.similarity_search(query, k=3)
return "\n\n".join(
f"**{doc.metadata['type']} by {doc.metadata['speakers']}**: "
f"{doc.metadata['title']}\nDescription:{doc.page_content}"
for doc in docs
)
现在,我们有了组成 AI 智能体的所有组件(通常,你还需要"记忆",但这里为了专注于搜索工具而故意省略了),我们需要做的就是将它们插在一起。
from langchain.agents import create_agent
agent = create_agent(
model=llm,
system_prompt=SYSTEM_PROMPT,
tools=[semantic_search_conference_sessions],
)
对于一个示例语义搜索查询 "哪些会话讨论 AI 系统中的监管约束?",智能体现在调用该工具并正确找到了 Bilge Yücel 关于在主权约束下构建 AI 系统的演讲。
================================ Human Message =================================
Which sessions discuss regulatory constraints in AI systems?
================================= Ai Message ==================================
Tool Calls:
semantic_search_conference_sessions (call_FE570VnExWZOmupAtjduUcIB)
Args:
query: regulatory constraints in AI systems law regulation compliance governance EU AI Act policy constraints
================================ Tool Message =================================
Name: semantic_search_conference_sessions
**talk by Bilge Yücel**: Engineering AI Systems Under Sovereignty Constraints
Description: Regulatory and jurisdictional constraints are no longer an edge case in AI system design; [...]
**talk by Dan James**: Building the Justice AI Unit: Shipping Production AI Inside Government
Description: [...]
**workshop by Liam McGarrigle**: Building Your Own Secure AI Workflows: Human-in-the-Loop Automation with n8n
Description: [...]
================================= Ai Message ==================================
The conference sessions that discuss regulatory constraints / legal or jurisdictional limitations on AI systems are:
- **Bilge Yücel — "Engineering AI Systems Under Sovereignty Constraints"** (talk)
Focus: how legal/jurisdictional limits affect AI architecture—e.g., **data localization, auditability, model transparency, and jurisdictional control**.
- **Dan James — "Building the Justice AI Unit: Shipping Production AI Inside Government"** (talk)
Focus: building and deploying production AI **inside government**, where regulatory/operational constraints apply.
If you want, tell me whether you mean **EU AI Act–specific compliance** or **broader regulation/governance**, and I can narrow it further.
这是大多数智能搜索演示停止的地方。
这也是它开始变得有趣的地方。
要打破这个建立在语义搜索工具上的演示,你所要做的就是问它一些关键词搜索比语义搜索更合适的问题。例如,如果我问 "我应该参加哪些会议来了解更多关于 GEPA——EPA?",语义搜索返回不相关的会话,而不是正确的 Samuel Colvin 的那个。
================================ Human Message =================================
Which sessions should I visit to learn more about GEPA?
================================= Ai Message ==================================
Tool Calls:
semantic_search_conference_sessions
Args:
query: GEPA
================================ Tool Message =================================
I searched the conference schedule for **"GEPA"**, but the only matching items I found were about **DeepMind's "Gemma"** (an open models family), not GEPA itself. So I **don't know which sessions are specifically about GEPA** based on the available schedule data.
If you meant **Gemma** (DeepMind), the relevant session is:
- **Keynote — "Gemma, DeepMind's Family of Open Models"** (Omar Sanseviero)
11、使用通用数据库搜索工具的智能搜索
如果智能体可以完全自主地从零开始编写整个搜索查询呢?让我们用更通用的工具(execute_esql_query)替换窄范围的语义搜索工具,如笔记本 02_agentic_search_with_db_query_tool.ipynb 中实现的那样。
首先,我们从 gpt-5.4-nano 切换到 gpt-5.4-mini,因为查询生成比传递主题更难。
然后,我们定义一个通用数据库查询工具,让智能体针对 Elasticsearch 运行完整的 ES|QL 查询。
@tool()
def execute_esql_query(esql_query: str) -> str:
"""Execute an ES|QL query against the conference_schedule index in Elasticsearch.
Args:
esql_query: The ES|QL query to execute
Returns:
A string containing the information about the sessions.
"""
try:
response = es_client.esql.query(query=esql_query, format="csv")
return response.body
except Exception as e:
return f"Error executing ES|QL query: {e}"
需要注意的是,我们在这里还添加了一个 try/except 块,如果 ES|QL 查询抛出错误,它会返回一些有意义的信息,这样智能体可以自我纠正。
现在,当我们运行与之前相同的 "我应该参加哪些会议来了解更多关于 GEPA?" 问题,我们可以看到智能体生成的 ES|QL 乍一看是有效的:
FROM conference_schedule
| WHERE text LIKE '%GEPA%'
| KEEP metadata.speakers, metadata.title, text
| LIMIT 3
但是,这个工具调用返回零个搜索结果,因为 %GEPA% 使用了 % 通配符,这是 SQL 的习惯。ES|QL 反而使用 * 作为通配符。
请注意,你还应该考虑如何处理零个搜索结果。在你的产品中决定零行意味着"没找到"还是"用不同参数重试"。
有许多选项可以克服这个问题,例如改进指令或添加特定于 ES|QL 的智能体技能。对于本教程,我们将定义一个最小的技能。对于生产环境,我建议查看 Elastic Agent Skills。
from typing import TypedDict
class Skill(TypedDict):
name: str
description: str
content: str
SKILLS: list[Skill] = [
{
"name": "elasticsearch-esql",
"description": """Execute ES|QL (Elasticsearch Query Language) queries, use when the user wants to
query Elasticsearch data, analyze logs, aggregate metrics, explore data, or create
charts and dashboards from ES|QL results.""",
"content": """
# Elasticsearch ES|QL
...
### Pattern Matching
- `LIKE` Wildcard pattern (`*` zero or more chars, `?` single char)
...
ES|QL uses **double quotes** for string literals, never single quotes.
""",
},
]
为了使用智能体技能,我们将复制 LangChain 文档中智能体技能的基本实现,用于技能加载工具 load_skill 和 SkillMiddleware,它注入技能描述并注册 load_skill。
@tool
def load_skill(skill_name: str) -> str:
"""Load the full content of a skill into the agent's context."""
for skill in SKILLS:
if skill["name"] == skill_name:
return f"Loaded skill: {skill_name}\n\n{skill['content']}"
available = ", ".join(s["name"] for s in SKILLS)
return f"Skill '{skill_name}' not found. Available skills: {available}"
现在我们还必须告诉智能体在调用 execute_esql_query 工具之前调用 ES|QL 技能。为此,我们通过添加这个关系来调整工具描述:
@tool()
def execute_esql_query(esql_query: str) -> str:
"""Execute an ES|QL query against an index in Elasticsearch.
Always use the Elasticsearch ES|QL skill to generate the ES|QL query
before using this tool to execute the query.
...
"""
然后我们在系统提示中加强这一点:
SYSTEM_PROMPT_WITH_SKILL = SYSTEM_PROMPT + (
"If you need to execute an ES|QL query, use the Elasticsearch ES|QL skill "
"to generate the query before using this tool to execute the query. "
"If an ESQL query returns an error use the Elasticsearch ES|QL skill "
"to generate a new query."
)
现在我们插入所有组件,包括 SkillMiddleware:
agent = create_agent(
model=llm,
system_prompt=SYSTEM_PROMPT_WITH_SKILL,
middleware=[SkillMiddleware()],
tools=[execute_esql_query],
)
追踪现在显示智能体首先调用 ES|QL 技能,并在调用 execute_esql_query 工具时使用它来编写有效的 ES|QL 查询。结果表明,通过这种方式,智能体能够找到正确的会话。
================================ Human Message =================================
Which sessions should I visit to learn more about GEPA?
================================= Ai Message ==================================
Tool Calls:
load_skill -> elasticsearch-esql
execute_esql_query -> FROM conference_schedule
| WHERE text LIKE "*GEPA*" OR metadata.title LIKE "*GEPA*"
| KEEP metadata.title, metadata.day, metadata.time, metadata.room, metadata.type, metadata.track, metadata.speakers
| SORT metadata.day, metadata.time
================================ Tool Message =================================
metadata.title,metadata.day,metadata.time,metadata.room,metadata.type,metadata.track,metadata.speakers
Playground in Prod - Optimising Agents in Production Environments,April 8,10:40am-12:00pm,Westminster,workshop,,Samuel Colvin
================================= Ai Message ==================================
The session I found related to GEPA is:
- **Playground in Prod - Optimising Agents in Production Environments**
- **When:** April 8, 10:40am–12:00pm
- **Where:** Westminster
- **Type:** Workshop
- **Speaker:** Samuel Colvin
由于这个工具是通用目的的,智能体也可以将它用于 分析问题,例如 "4月8日有多少场会议?" 通过使用 STATS。这特别有用,因为否则智能体将不得不过滤 4 月 8 日的所有会议,然后计算它们。我们都知道 LLM 在计数方面非常差。
================================ Human Message =================================
How many sessions are on April 8th?
================================= Ai Message ==================================
Tool Calls:
load_skill -> elasticsearch-esql
execute_esql_query -> FROM conference_schedule | WHERE metadata.day == "April 8" | STATS count = COUNT()
================================ Tool Message =================================
count
27
================================= Ai Message ==================================
There are **27 sessions** on **April 8th**.
正如你所看到的,通用目的的 execute_esql_query 比窄范围的 semantic_search 工具更强大,因为它可以处理更模糊或复杂的查询。同时,添加技能加载工具会增加成本和延迟,并且需要更强大的 LLM。
12、使用 Shell 工具的智能搜索
最后,让我们谈谈目前智能搜索最热门的话题:"Bash + 文件系统就是你所需的全部"。
你可以在 03_agentic_search_with_shell_tool.ipynb 中找到相关实现。为此,我们将相同的数据从 Elasticsearch 集群移动到 ../data/session_data/ 下的本地文件(每个会话一个 .txt 文件),并让智能体使用 LangChain 的 ShellTool 在它们上面搜索。
首先,我们实例化 shell 工具:
from langchain_community.tools import ShellTool
shell_tool = ShellTool() # no safeguards by default; sandbox in production
请注意,shell 工具默认没有任何安全防护,建议仅在沙盒环境中使用。
本例的系统提示与之前描述的类似,只是我们现在描述文件系统中的数据而不是 Elasticsearch 集群中的数据。
对于 "有关于 GEPA 的会议吗?",智能体现在首先探索文件系统,然后简单地用 grep 搜索 "GEPA" 并找到正确的会话。
================================ Human Message =================================
Are there any sessions about GEPA?
================================= Ai Message ==================================
Tool Calls:
terminal -> ls -R ../data/session_data | head
terminal -> grep -Ril "GEPA" ../data/session_data | head -n 50
terminal -> cat "../data/session_data/workshop/Playground in Prod - Optimising Agents in Production Environments.txt"
================================ Tool Message =================================
Yes—there is at least one session that mentions **GEPA**:
- **Playground in Prod - Optimising Agents in Production Environments** (workshop)
- **Day/Time:** April 8, 10:40am–12:00pm
- **Room:** Westminster
- **Speaker:** Samuel Colvin
- **Where GEPA appears:** The session includes **"Autonomous optimisation with GEPA"** and explains how GEPA can be wired into Logfire's managed variables to create an optimisation loop that improves agent prompts based on production traces.
但现在让我们用之前那个语义搜索查询 "哪些会议讨论处理监管约束?" 来试试,看看智能体如何用 grep 在语义搜索中作弊。你可以看到智能体首先查找 "regulat",我猜这是公平的,因为这样它可以找到 "regulatory" 和 "regulation" 这样的词。但随后它开始组合相关同义词(compliance、constraints、GDPR、governance),直到有东西命中。
这几乎不合理地有效,但这是最有效的方法吗?例如,如果我让它找"有动物超级英雄的电影",智能体将不得不组合所有动物。
这就是为什么我们目前看到许多 grep 的语义搜索替代品:LlamaIndex 的 semtools、LightOn 的 colgrep 和 Jina AI 的 jina-grep-cli。
让我们给智能体访问 jina-grep-cli 的权限,看看它是如何工作的。首先,你需要按照仓库中的说明安装 CLI。
然后,你要做的就是在系统提示中告诉你的智能体关于它以及如何使用它。
对于带有此提示的 "哪些会议讨论处理监管约束?",智能体现在能够更有效地找到正确的会话:
================================ Human Message =================================
Which sessions discuss handling regulatory constraints?
================================= Ai Message ==================================
Tool Calls:
terminal -> jina-grep -r --top-k 10 "regulatory constraints" ../data/session_data
================================ Tool Message =================================
../data/session_data/talk/Engineering AI Systems Under Sovereignty Constraints.txt:1:# Engineering AI Systems Under Sovereignty Constraints ... [0.374]
../data/session_data/talk/How to build a personal computer use agent and where the limits are.txt:1:# How to build a personal computer use agent and where the limits are ... [0.227]
...
================================ Ai Message ==================================
The conference sessions that discuss handling **regulatory / jurisdictional constraints** include:
- **"Engineering AI Systems Under Sovereignty Constraints"** (Talk) — *Apr 9, 12:40–1:00pm, St. James* — Speaker: **Bilge Yücel**
*Focus:* how legal/jurisdictional requirements like data localization, auditability, and jurisdictional control translate into AI system architecture decisions.
实践建议
在本次研讨会中,我们研究了上下文工程搜索工具的广泛领域,然后探索了其中的一小部分以了解它们的优缺点。本次研讨会的目标不是倡导一种最佳搜索工具。相反,我想强调策展一组匹配智能体实际行为的工具的重要性。
一个有用的框架是"低门槛,高天花板":
- 专用工具(例如,带主题字符串的语义搜索、按 ID 查找)具有简单的参数、更少的失败频率和更少的 token 消耗。
- 通用工具(shell、原始 ES|QL)处理边缘情况,但可能需要更多迭代和更强的模型。
理想情况下,你两者都想要:常见查询的简单成功和长尾问题的逃生舱口。
如果你已经知道智能体的查询模式和使用案例,你可以直接选择专用工具。
如果你不知道查询模式,我建议从通用工具开始。然后记录工具调用和错误,并在看到重复时添加专用工具。对于一个简单问题进行四五次工具调用通常意味着当前工具对模型来说太难了。这就是我演进技术栈的方式:上线、阅读追踪、用目的构建的工具缩小范围,而不是在第一天猜测 API。
原文链接: Agentic Search for Context Engineering
汇智网翻译整理,转载请标明出处