ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

与你的 Elasticsearch 数据对话:用 Google ADK 和 MCP 搭建实时语音 agent 的 3 个组件

与你的 Elasticsearch 数据对话:用 Google ADK 和 MCP 搭建实时语音 agent 的 3 个组件 1. 厨房里那台“看不见的电脑”实时语音 agent 到底解决什么问题想象一下晚餐高峰期的后厨主厨双手沾满面粉灶台上还炖着高汤这时候他突然想起来要确认一件事——“海鲜烩饭里到底有没有贝类”如果还要擦手、解锁手机、打开 App、输入关键词这一套动作下来锅都可能糊了。实时语音 agent的价值就在这里它让检索这件事从“动手操作”变成“开口提问”而Elasticsearch负责在后台把语义搜索做掉Google ADK负责把语音流接起来MCP负责把两边粘在一起。这套组合能做什么简单说你对着麦克风说一句自然语言agent 把这句话转成对 Elasticsearch 的语义查询拿到命中结果后再用语音念给你听。适合谁适合任何“手忙但脑子需要查资料”的场景后厨查过敏原、仓库查库存、护士查病历摘要、运维查维护手册。它的核心不是“语音识别”本身而是语音输入 → MCP 工具调用 → ES 查询回传这条三段链路能不能稳定跑通。我试过把这套链路拆开看发现真正容易出问题的不是模型而是中间那层工具声明和连接参数。所以这篇不聊虚的直接把三个组件——语音输入层、MCP 工具调用层、ES 查询回传层——的配置和排障都摊开讲。你跟着做最后能对着麦克风问一句“无乳制品的甜点有哪些”然后听到 agent 把结果念出来。先明确三个组件的分工后面每一段都会围绕它们展开组件角色关键技术语音输入层双向实时语音流Google ADK Gemini Live APIMCP 工具调用层把 agent 和 ES 连起来McpToolset StdioConnectionParamsES 查询回传层语义搜索并返回命中Agent Builder hosted MCP server semantic_text这三段里第一段决定“听不听得清”第二段决定“调不调得动”第三段决定“查不查得准”。任何一段断了端到端就失败。下面按顺序拆。2. 前置准备Elasticsearch 索引、inference endpoint 与 Agent Builder 工具声明在写 agent 代码之前得先把 Elasticsearch 这边的“地基”打好。这一步很多人会跳过结果后面 agent 调工具时一直返回空命中还以为是 MCP 配置错了。其实问题往往出在索引 mapping 和 inference endpoint 上。2.1 创建 inference endpoint 与 semantic_text 字段语义搜索的前提是有一个 embedding 模型把文本转成向量。这里用jina-embeddings-v5-text-small通过 Elasticsearch 的 inference API 注册INFERENCE_ID jina-embeddings inference_config { service: elastic, service_settings: {model_id: jina-embeddings-v5-text-small}, } es_client.inference.put( task_typetext_embedding, inference_idINFERENCE_ID, bodyinference_config, ) print(fInference endpoint {INFERENCE_ID} created successfully)注册完之后索引 mapping 里要有一个semantic_text类型的字段并指定它用哪个 inference endpoint。同时用copy_to把需要被检索的字段内容都汇总到这个语义字段里INDEX_NAME knowledge knowledge_mapping { properties: { name: {type: text, copy_to: semantic_field}, ingredients: {type: text, copy_to: semantic_field}, allergens: {type: keyword, copy_to: semantic_field}, procedure: {type: text, copy_to: semantic_field}, prep_time_minutes: {type: integer}, category: {type: keyword, copy_to: semantic_field}, dietary: {type: keyword, copy_to: semantic_field}, semantic_field: { type: semantic_text, inference_id: INFERENCE_ID, }, } }这里有个细节值得说semantic_field本身不接收你手动写入的数据它靠copy_to从其他字段“吸”内容。所以你在 bulk 导入时只需要写原始字段语义字段会自动生成。如果你发现语义搜索命中率低先检查copy_to有没有漏字段。2.2 用 bulk API 导入菜谱数据数据导入用 helpers.bulk注意加refreshTrue否则刚导入的数据可能搜不到import json from elasticsearch import helpers def build_bulk_actions(documents, index_name): for doc in documents: yield {_index: index_name, _source: doc} with open(dataset/knowledge.json, r) as f: docs json.load(f) success, failed helpers.bulk( es_client, build_bulk_actions(docs, INDEX_NAME), refreshTrue, ) print(f{success} documents indexed successfully)2.3 在 Agent Builder 里声明搜索工具Agent Builder 开箱即用提供 hosted MCP server你只需要通过 API 声明一个 tool告诉它去查哪个索引、用什么检索方式recipe_search_tool { id: recipe_semantic_search, type: index_search, description: Search kitchen recipes including ingredients, allergens, dietary restrictions, preparation procedures, and cooking times. Uses semantic search to find relevant recipes even without exact keyword matches., tags: [semantic], configuration: { pattern: INDEX_NAME, }, } response requests.post( f{KIBANA_ENDPOINT}/api/agent_builder/tools, headersKIBANA_HEADERS, jsonrecipe_search_tool, )description这个字段千万别随便写。它是给 agent 看的“工具说明书”agent 会根据它判断什么时候该调这个工具。你写得越清楚agent 选工具的准确率越高。tags里的semantic决定了这个工具走语义检索而不是关键词匹配。注意如果你用的是非 serverless 的 Elasticsearch 集群需要先手动启用 Agent Builderserverless 部署默认已启用。另外你的 API key 必须包含feature_agentBuilder.read权限否则调工具时会直接 403。到这里ES 侧的三件事——inference endpoint、索引 mapping、Agent Builder tool——就齐了。接下来才是把 Google ADK 接上去。3. 可复制配置Google ADK 通过 MCP 连接 Elasticsearch 的完整 agent.py这一段是整篇的核心。Google ADK 通过 MCP 连接 Agent Builder本质上只用到三个组件Agent、McpToolset、StdioConnectionParams。整个连接代码大约 30 行但每一行都有讲究。3.1 完整 agent.py 配置import os from dotenv import load_dotenv from google.adk.agents import Agent from google.adk.tools.mcp_tool import McpToolset from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams from mcp.client.stdio import StdioServerParameters load_dotenv() KIBANA_ENDPOINT os.getenv(KIBANA_ENDPOINT) ELASTIC_API_KEY os.getenv(ES_API_KEY) AUTH_HEADER fApiKey {ELASTIC_API_KEY} root_agent Agent( modelgemini-2.5-flash-native-audio-latest, namekitchen_assistant_agent, instructionYou are a kitchen assistant that helps chefs during busy dinner service. You can answer questions about recipes. Use the Elasticsearch tools to search the cooking-recipes index and provide quick answers like: - Does a dish contain specific allergens (e.g., shellfish)? - Recipes that can be prepared with given ingredients - I would like to prepare a seafood dish - Find recipes by category or dietary restrictions Always be concise and practical - chefs need quick answers!, tools[ McpToolset( connection_paramsStdioConnectionParams( server_paramsStdioServerParameters( commandnpx, args[ -y, mcp-remote, f{KIBANA_ENDPOINT}/api/agent_builder/mcp, --header, fAuthorization:{AUTH_HEADER}, ], ), timeout30, session_read_timeout_seconds120, ), tool_filter[recipe_semantic_search], ) ], )3.2 三个组件各自在干什么Agent定义了这个语音助手是谁用什么模型、叫什么名字、收到什么指令、能用哪些工具。instruction里我特意写了“chefs need quick answers”因为语音场景下回答太长反而没用模型会倾向于简短输出。McpToolset是 ADK 提供的 MCP client 接口。它让 agent 能连到任意 MCP server这里连的是 Agent Builder 的 MCP endpoint。tool_filter参数指定只用recipe_semantic_search这一个工具。别小看这个过滤——如果不加agent 每次都要先做一轮工具发现响应会明显变慢。StdioConnectionParams负责建立连接。它通过启动一个本地进程mcp-remote来桥接通信把 Kibana endpoint 和认证头传进去。timeout30是连接超时session_read_timeout_seconds120是会话读取超时。语音场景下第一次调用可能比较慢这两个值别设太小。3.3 模型选择与 .env 配置模型用的是gemini-2.5-flash-native-audio-latest这是针对 Live API 实时语音对话优化的 Gemini 模型支持原生音频输入输出不需要额外的 text-to-speech 转换。用latest别名的好处是 Google 推新版本时你不用改代码如果要固定版本可以调 models API 列出所有可用 ID。.env文件里需要两个变量KIBANA_ENDPOINThttps://your-deployment.kb.region.elastic-cloud.com ES_API_KEYyour_elastic_api_key_here安装依赖并启动pip install google-adk google-genai python-dotenv pyaudio adk web --port 8000启动后打开 Web 界面左侧菜单选中kitchen_assistant_agent就可以对着麦克风说话了。第一次调 Live API 时响应可能要等约 30 秒而且 ADK Web 界面在处理过程中不会显示进行中的提示别以为是卡死了。提示如果你只想先用文本模式验证链路通不通可以用adk run在终端里测试省去麦克风调试的干扰。等文本模式能正常返回 ES 结果了再切到语音模式。4. 验证请求一次语音提问如何端到端返回结果配置写完最关键的是验证。我建议分两步走先用文本模式确认 MCP 工具调用和 ES 查询回传没问题再切语音模式确认语音输入层正常。4.1 文本模式验证 MCP 链路用adk run启动后直接输入一句自然语言Does the seafood risotto have shellfish?如果链路正常agent 会调用recipe_semantic_search工具向 Elasticsearch 发起语义查询然后返回类似这样的结果Seafood Risotto includes shellfish and shrimp.这一步验证的是第二段和第三段链路MCP 工具调用有没有成功、ES 查询有没有命中。如果这里返回空或者报错先别急着调语音把 MCP 和 ES 的问题解决掉。4.2 语音模式验证完整链路文本模式通了之后用adk web --port 8000启动 Web 界面选中 agent对着麦克风问同样的问题。正常的话你会听到 agent 用语音把答案念出来。在 Web 界面的 Events 视图里你能看到完整的调用链函数调用里是发往 Elasticsearch 的查询函数响应里是语义搜索返回的命中结果。这个视图是排障神器——如果语音没反应先看 Events 里有没有函数调用记录如果有调用但没命中问题在 ES 侧如果连调用都没有问题在 MCP 连接或模型选择上。4.3 几个能问出效果的问题类型你问agent 回答Does the seafood risotto have shellfish?Seafood Risotto includes shellfish and shrimp.How do I make the house vinaigrette?念出完整制作步骤What can I make for a vegan guest?推荐 vegan 菜品Any nut-free desserts?列出无坚果甜点语义搜索的好处是你不需要精确匹配关键词。问“无乳制品的菜”和问“不含奶的食谱”都能命中同一批结果。这也是为什么semantic_field要把 ingredients、allergens、dietary 都 copy 进去——语义检索看的是整体语义不是单个字段。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth这一段是我踩过的坑合集。实时语音 agent 这条链路涉及的组件多报错信息又往往指向不明确所以把最常见的几类错误和对应排查方法列出来。5.1 401 Unauthorized最常见的就是 401。原因通常是 API key 权限不够或者认证头格式不对。先检查AUTH_HEADER的格式。Elasticsearch 的 API key 认证头是ApiKey base64注意ApiKey和值之间是一个空格不是冒号。如果你写成Authorization:ApiKey xxx而中间少了空格就会 401。再检查 API key 的权限。Agent Builder 需要feature_agentBuilder.read权限语义搜索还需要feature_inference.all。如果你是在 serverless 环境创建 API key 时要带上完整的 role descriptor{ name: google-adk-api-key, expiration: 30d, role_descriptors: { mcp-access: { cluster: [all], indices: [ { names: [*], privileges: [all], allow_restricted_indices: false } ], applications: [ { application: kibana-.kibana, privileges: [ feature_agentBuilder.all, feature_actions.read, feature_inference.all, feature_advancedSettings.read ], resources: [space:default] } ] } } }5.2 local proxy failed 与 mcp-remote 连接问题local proxy failed通常出现在mcp-remote启动阶段。这个报错的意思是本地代理进程没能成功连上远端 MCP endpoint。排查顺序先确认KIBANA_ENDPOINT能不能在浏览器里打开。如果 endpoint 本身不可达后面都白搭。再确认npx命令能不能正常执行——有些环境里 npx 不在 PATH 里或者 Node 版本太低。可以在终端里手动跑一遍npx -y mcp-remote https://your-endpoint/api/agent_builder/mcp --header Authorization:ApiKey your_key如果手动跑也失败报错信息会比 agent 里更清楚。常见的是证书问题或者网络超时。timeout30如果不够可以适当调大。5.3 reading choices 报错reading choices这类报错通常和模型返回格式有关。Gemini 的 native audio 模型在返回工具调用时格式和纯文本模型不太一样。如果你用的模型 ID 不对或者模型不支持工具调用就会在解析响应时出错。确认你用的是gemini-2.5-flash-native-audio-latest或对应的 preview 版本。如果你固定了某个旧版本而那个版本不支持 Live API 的工具调用就会出这个问题。另外检查tool_filter里的工具名和 Agent Builder 里创建的工具 ID 是否完全一致——大小写、下划线都要对上。5.4 OAuth 与认证方式混淆有些 MCP server 用 OAuth 认证有些用 API key。Agent Builder 的 MCP endpoint 用的是 API key所以你在--header里传的是Authorization:ApiKey xxx。如果你误用了 OAuth 的 bearer token 格式就会认证失败。另外注意mcp-remote这个工具本身可能会尝试做一些 OAuth 流程。如果你看到它弹出浏览器要求登录说明它没识别到你的 header 认证检查 header 参数有没有正确传进去。5.5 工具调用成功但返回空结果这种情况最隐蔽Events 视图里能看到函数调用但命中结果是空的。问题几乎都出在 ES 侧。检查三件事索引里有没有数据refreshTrue有没有加、semantic_field的copy_to有没有漏字段、inference endpoint 有没有创建成功。可以单独用 ES 的查询 API 测一下语义搜索能不能返回结果把 MCP 这层排除掉。6. 把这条链路用到你自己的场景从菜谱到库存、病历与维护手册跑通菜谱这个例子之后你会发现这套架构的通用性很强。三个组件的分工不变变的只是 Elasticsearch 索引里的数据和 agent 的 instruction。如果你要查库存把索引换成仓库物料表semantic_field里 copy 进物料名称、规格、库位agent 的 instruction 改成“你是仓库助手帮工人快速查库存”。如果你要查维护手册把索引换成设备文档语义搜索能处理“这个型号的泵怎么换密封圈”这类自然语言问题。有几个实用技巧值得记一下。第一tool_filter一定要用工具越多 agent 选错的概率越大。第二instruction里明确告诉 agent 回答要简短语音场景下长回答没人听得完。第三Events 视图是你最好的朋友任何链路问题先看它。关于接入方式如果你只是想在本地快速验证模型和 MCP 链路可以直接用模型对话功能试一下语义搜索的返回效果如果你打算把这条链路长期跑在编码或 Agent 工作流里Coding Plan 会更合适而 API Key 和接入文档则是你配置认证头、排查 401 时最该先翻的两页。这三个入口分别对应验证、长期使用和排障三种需求按你当前卡在哪一步去选就行。最后说一个我自己的经验第一次调 Live API 时那 30 秒的等待很容易让人以为配置错了其实只是模型在建立会话。耐心等一次后面就快了。真正需要警惕的是 Events 视图里一直没有函数调用记录——那才是链路断了的信号。
返回列表