<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Tomás Murúa - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Tomás Murúa - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/cn/search-labs/author/tomas-murua</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/author/tomas-murua</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/author/tomas-murua.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Wed, 16 Sep 2026 20:07:37 GMT</lastBuildDate>
  <item>
    <title><![CDATA[使用 Elasticsearch 构建 ChatGPT 连接器以查询 GitHub 问题]]></title>
    <description><![CDATA[学习如何构建自定义 ChatGPT 连接器并部署使用混合搜索查询内部 GitHub 问题的 Elasticsearch MCP 服务器。]]></description>
    <content:encoded><![CDATA[<p>最近，OpenAI 宣布为专业版/商务版/企业版和教育版 ChatGPT 提供<a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">自定义连接器</a>功能。除了提供开箱即用的连接器来获取 Gmail、GitHub、Dropbox 等平台上的数据。还可以使用 MCP 服务器创建自定义连接器。</p><p>定制连接器使您能够将现有的 ChatGPT 连接器与其他数据源（如 Elasticsearch）结合，以获得全面的答案。</p><p>在本文中，我们将构建一个 <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> 服务器，将 ChatGPT 连接到包含内部 GitHub 问题和拉取请求信息的 Elasticsearch 索引。这样就可以使用 Elasticsearch 数据回答自然语言查询。</p><p>我们将在 Google Colab 上使用 <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> 和 ngrok 部署 MCP 服务器，以获取 ChatGPT 可以连接的公共 URL，从而省去复杂的基础架构设置。</p><p>有关 MCP 及其生态系统的全面概述，请参阅《<a href="https://www.elastic.co/search-labs/blog/mcp-current-state">MCP 的现状</a>》。</p><h2>准备工作</h2><p>在开始之前，您需要：</p><ul><li><p>Elasticsearch 集群（8.X 或更高版本）</p></li><li><p>Elasticsearch API密钥，具有对您的索引的读取访问权限</p></li><li><p>Google 账户（用于 Google Colab）</p></li><li><p>Ngrok账户 （免费套餐可用）</p></li><li><p>拥有专业版/企业版/商务版或教育版套餐的 ChatGPT 账户</p></li></ul><h2>了解 ChatGPT MCP 连接器的要求</h2><p>ChatGPT MCP 连接器需要实现两个工具：<code>search</code> 和 <code>fetch</code>。有关更多详情，请参阅 <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI 文档</a>。</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">搜索工具</a></h3><p>根据用户查询，从 Elasticsearch 索引中返回相关结果列表。</p><h4>接收的内容：</h4><ul><li><p>一个单一的字符串，包含用户的自然语言查询。</p></li><li><p>示例：“查找与 Elasticsearch 迁移相关的问题。”</p></li></ul><h4>返回的内容：</h4><ul><li><p>一个对象，其<code>result</code> 关键字包含一个结果对象数组。每个结果包括：</p><ul><li><p><code>id</code> - 唯一文档标识符</p></li><li><p><code>title</code> - 问题或拉取请求标题</p></li><li><p><code>url</code> - 链接到问题或 PR</p></li></ul></li></ul><h4>在我们的实现中：</h4>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}<h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">获取工具</a></h3><p>获取指定文档的完整内容。</p><h4>接收的内容：</h4><ul><li><p>搜索结果中包含 Elasticsearch 文档 ID 的单个字符串</p></li><li><p>示例：“获取 PR-578 的详细信息。”</p></li></ul><h4>它返回的内容：</h4><ul><li><p>一个完整的文档对象，包含：</p><ul><li><p><code>id</code> - 唯一文档标识符</p></li><li><p><code>title</code> - 问题或拉取请求标题</p></li><li><p><code>text</code> - 完整的问题/PR描述和详细信息</p></li><li><p><code>url</code> - 链接到问题或 PR</p></li><li><p><code>type</code> - 文档类型（问题、pull_request）</p></li><li><p><code>status</code> - 当前状态（打开、进行中、已解决）</p></li><li><p><code>priority</code> - 优先级别（低、中、高、关键）</p></li><li><p><code>assignee</code> - 负责此问题/PR 的人员</p></li><li><p><code>created_date</code> - 何时创建</p></li><li><p><code>resolved_date</code> - 何时解决（如适用）</p></li><li><p><code>labels</code> - 与文件相关的标签</p></li><li><p><code>related_pr</code> － 相关拉取请求 ID</p></li></ul></li></ul>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}<p><strong>注意</strong>：本示例使用扁平结构，其中所有字段都位于根级别。OpenAI 的要求非常灵活，还支持嵌套的元数据对象。</p><h2>GitHub 问题和 PR 数据集</h2><p>在本教程中，我们将使用包含问题和拉取请求的内部 GitHub 数据集。这代表了一个您希望通过 ChatGPT 查询私有、内部数据的场景。</p><p>数据集可以在<a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">此处</a>找到。我们将使用<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">批量 API</a> 更新数据索引。</p><p>这个数据集包含：</p><ul><li><p>有关描述、状态、优先级和分配人员的问题</p></li><li><p>包含代码更改、审查和部署信息的拉取请求</p></li><li><p>问题与 PR 之间的关系（例如，PR-578 修复了 ISSUE-1889）</p></li><li><p>标签、日期和其他元数据</p></li></ul><h3>索引映射</h3><p>该索引使用以下<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">映射</a>来支持使用 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a> 的混合搜索。<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a> 用于语义搜索，而其他字段用于关键字搜索。</p>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}<h2>构建MCP服务器</h2><p>我们的 MCP 服务器按照 OpenAI 规范实现了两个工具，使用混合搜索将语义和文本匹配相结合，以获得更好的结果。</p><h3>搜索工具</h3><p>利用 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a>（倒数排序融合）进行混合搜索，将语义搜索与文本匹配相结合：</p>@mcp.tool()
    async def search(query: str) -&gt; Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

        logger.info(f"Searching for: '{query}'")

        try:
            # Hybrid search with RRF (Reciprocal Rank Fusion)
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                size=10,
                source=["id", "title", "url", "type", "priority"],
                retriever={
                    "rrf": {
                        "retrievers": [
                            {
                                # Semantic search with ELSER
                                "standard": {
                                    "query": {
                                        "semantic": {
                                            "field": "text_semantic",
                                            "query": query
                                        }
                                    }
                                }
                            },
                            {
                                # Text search (BM25) for keyword matching
                                "standard": {
                                    "query": {
                                        "multi_match": {
                                            "query": query,
                                            "fields": [
                                                "title^3",
                                                "text^2",
                                                "assignee^2",
                                                "type",
                                                "labels",
                                                "priority"
                                            ],
                                            "type": "best_fields",
                                            "fuzziness": "AUTO"
                                        }
                                    }
                                }
                            }
                        ],
                        "rank_window_size": 50,
                        "rank_constant": 60
                    }
                }
            )

            results = []
            if response and 'hits' in response:
                for hit in response['hits']['hits']:
                    source = hit['_source']
                    results.append({
                        "id": source.get('id', hit['_id']),
                        "title": source.get('title', 'Unknown'),
                        "url": source.get('url', '')
                    })

            logger.info(f"Found {len(results)} results")
            return {"results": results}

        except Exception as e:
            logger.error(f"Search error: {e}")
            raise ValueError(f"Search failed: {str(e)}")<h3>要点：</h3><ul><li><p><strong>使用 RRF 的混合搜索：</strong>结合语义搜索 (ELSER) 和文本搜索 (BM25)，以获得更好的结果。</p></li><li><p><strong>多匹配查询：</strong><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">在多个字段中进行搜索</a>，并使用增强功能（标题^3、文本^2、分配人员^2）。插入符号 (^) 会乘以相关性分数，优先考虑标题中的匹配项而非内容中的匹配项。</p></li><li><p><strong>模糊匹配：</strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#fuzziness"><code>fuzziness: AUTO</code></a> 通过允许近似匹配来处理错别字和拼写错误。</p></li><li><p><strong>RRF 参数调整：</strong></p><ul><li><p><code>rank_window_size: 50</code> - 指定在合并之前从每个检索器（语义和文本）中考虑最靠前结果的数量。</p></li><li><p><code>rank_constant: 60</code> - 该值决定了单个结果集中的文档对最终排序结果的影响程度。</p></li></ul></li><li><p><strong>仅返回必填字段：</strong>根据 OpenAI 规范返回 <code>id</code>、<code>title</code>、<code>url</code>，避免不必要地暴露其他字段。</p></li></ul><h3>获取工具</h3><p>按文档 ID（如果存在）检索文档详细信息：</p>@mcp.tool()
    async def fetch(id: str) -&gt; Dict[str, Any]:
        """
        Retrieve complete issue/PR details by ID.
        Returns id, title, text, url.
        """
        if not id:
            raise ValueError("ID is required")

        logger.info(f"Fetching: {id}")

        try:
            # Search by the 'id' field (not _id) since IDs are stored as a field
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                body={
                    "query": {
                        "term": {
                            "id": id  # Search by your custom 'id' field
                        }
                    },
                    "size": 1
                }
            )

            if not response or not response['hits']['hits']:
                raise ValueError(f"Document with id '{id}' not found")

            hit = response['hits']['hits'][0]
            source = hit['_source']

            result = {
                "id": source.get('id', id),
                "title": source.get('title', 'Unknown'),
                "text": source.get('text', ''),
                "url": source.get('url', ''),
                "type": source.get('type', ''),
                "status": source.get('status', ''),
                "priority": source.get('priority', ''),
                "assignee": source.get('assignee', ''),
                "created_date": source.get('created_date', ''),
                "resolved_date": source.get('resolved_date', ''),
                "labels": source.get('labels', ''),
                "related_pr": source.get('related_pr', '')
            }

            logger.info(f"Fetched: {result['title']}")
            return result

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")<h3>要点：</h3><ul><li><p><strong>按文档 ID 字段进行搜索：</strong>使用自定义 <code>id</code> 字段上的术语查询</p></li><li><p><strong>返回完整文档：</strong>包含完整的 <code>text</code> 字段及其所有内容</p></li><li><p><strong>扁平结构：</strong>所有字段均位于根级别，与 Elasticsearch 的文档结构相匹配。</p></li></ul><h2>在 Google Colab 上部署</h2><p>我们将使用 Google Colab 来运行 MCP 服务器，并使用 ngrok 将其公开，以便 ChatGPT 可以连接到它。</p><h3>步骤 1：打开 Google Colab 笔记本</h3><p>访问我们预配置的笔记本<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">适用于 ChatGPT 的 Elasticsearch MCP</a>。</p><h3>步骤 2：配置您的凭据</h3><p>您需要三项信息：</p><ul><li><p><strong>Elasticsearch URL：</strong>您的 <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">Elasticsearch 集群 URL</a>。</p></li><li><p><strong>Elasticsearch API 密钥：</strong>具有索引读取权限的 <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">API 密钥</a>。</p></li><li><p><strong>Ngrok 身份验证令牌：来自 </strong><a href="https://ngrok.com/">ngrok</a> 的免费令牌。我们将使用 ngrok 将 MCP URL 公开到互联网，以便 ChatGPT 可以连接到它。</p></li></ul><h4>获取 ngrok 令牌</h4><ol><li><p>在 <a href="https://ngrok.com/">ngrok</a> 注册免费账户</p></li><li><p>前往您的 <a href="https://dashboard.ngrok.com/">ngrok 仪表板</a></p></li><li><p>复制您的身份验证令牌</p></li></ol><h4>为 Google Colab 添加机密</h4><p>在 Google Colab 笔记本中：</p><ol><li><p>点击左侧边栏中的“<strong>密钥图标</strong>”以打开“<strong>机密</strong>”。</p></li><li><p>添加这三个秘密：</p></li></ol>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token<p>3. 为每个机密启用笔记本访问权限</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5acae97b386277f8/6a17f08f5ea30f74c964b6c2/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" alt="向 Google Collab 添加敏感信息" /><h3>步骤 3：运行 Notebook</h3><ol><li><p>点击“<strong>运行时</strong>”，然后点击“<strong>全部运行</strong>”，以执行所有单元格</p></li><li><p>等待服务器启动（约30秒）</p></li><li><p>查找显示您的公开 ngrok URL 的输出</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd11aacf2deab67c/6a17f091e8fbce81f13a1a41/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" alt="" /><p>4. 该输出将显示如下内容：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8891d917fdbaaf48/6a17f092abe0f208c7dfeaf6/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" alt="在 Google Collab 中运行笔记本的输出结果" /><h2>连接 ChatGPT</h2><p>现在我们将 MCP 服务器连接到您的 ChatGPT 账户。</p><ol><li><p>打开 ChatGPT，前往“<strong>设置</strong>”。</p></li><li><p>导航到<strong>“连接器”。</strong>如果您使用的是专业版账户，则需要在连接器中打开“<a href="https://platform.openai.com/docs/guides/developer-mode">开发者模式</a>”。</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95efdcb2c39307e7/6a17f094abe0f24d8edfeafa/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" alt="将 MPC 服务器连接到 ChatGPT 账户" /><p><em>如果您使用的是 ChatGPT 企业版或商业版，您需要将连接器发布到您的工作场所。</em></p><p>3. 点击“<strong>创建</strong>”。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c8fc8dd6033918/6a17f095631730de19585b7b/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" alt="向 ChatGPT 添加连接器" /><p><em><strong>注意</strong></em><em>：在商业版、企业版和教育版工作区中，只有工作区所有者、管理员和已启用相应设置（针对企业版/教育版）的用户才能添加自定义连接器。具有普通成员角色的用户无法自行添加自定义连接器。</em></p><p><em>一旦连接器被所有者或管理员用户添加并启用，工作区中的所有成员即可使用该连接器。</em></p><p>4. 输入所需信息和以 <code>/sse/</code> 结尾的 ngrok URL。请注意“sse”后面的“/”。没有它就无法正常工作：</p><ul><li><p><strong>名字：</strong> Elasticsearch MCP</p></li><li><p><strong>描述：</strong>用于搜索和获取 GitHub 内部信息的自定义 MCP。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd716ad0beeeb1d35/6a17f09714d90c11cc79b6d7/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" alt="创建一个 Elastic MCP 连接器 " /><p>5. 按下“<strong>创建</strong>”保存自定义 MCP。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857794237d7d3b5a/6a17f0983e03d729b74f2d54/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" alt="点击创建，保存自定义 MCP 连接器" /><p>如果您的服务器正在运行，则连接是即时的。无需额外的身份验证，因为 Elasticsearch API 密钥已在服务器中配置。</p><h2>测试 MCP 服务器</h2><p>在提问之前，您需要先选择 ChatGPT 应该使用的连接器。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" alt="选择 ChatGPT 应使用的连接器" /><h3>提示 1: 搜索问题</h3><p>提问：“<strong>查找与 Elasticsearch 迁移相关的问题”</strong>并确认操作工具调用。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c204ceacf897f61/6a17f09c9da390fb1de4657d/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" alt="让 ChatGPT“查找与 Elasticsearch 迁移相关的问题&quot;并确认调用工具的操作。" /><p>ChatGPT 将调用<code>search</code> 工具处理您的查询。你可以看到它正在查找可用工具，并准备调用 Elasticsearch 工具，在对该工具执行任何操作之前与用户确认。</p><h4>工具调用请求：</h4>{
  "query": "Elasticsearch migration issues"
}<h4>工具响应：</h4>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}<p>ChatGPT 会处理这些结果，并以自然对话的形式呈现。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b4378e7d26b4ad0/6a17f09ddbb4ff4de1fb57bf/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" alt="ChatGPT 如何处理工具调用请求和工具调用响应的结果" /><h3>幕后</h3><h4>提示：“查找与 Elasticsearch 迁移相关的问题”</h4><p>1. ChatGPT 调用 <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearch 执行混合搜索。</p><ul><li><p><strong>语义搜索</strong>能理解“升级”和“<em>版本兼容性</em>”等概念。</p></li><li><p><strong>文本搜索</strong>可查找与“<em>Elasticsearch</em>”和“迁移”完全匹配的内容。</p></li><li><p><strong>RRF</strong> 将两种方法的结果进行合并和排序</p></li></ul><p>3. 返回与 <code>id</code>、<code>title</code> 匹配度最高的 10 个事件。 <code>url</code></p><p>4. ChatGPT 将“<em>ISSUE-1712：从 Elasticsearch 7.x 迁移到 8.x</em>”作为最相关的结果</p><h3>提示 2：获取完整的详细信息</h3><p>问：<em><strong>“请提供有关 ISSUE-1889 的详细信息”</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1a53db8bfe8326/6a17f09f445de966104d021a/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" alt="ChatGPT 识别到您需要有关特定问题的详细信息，并调用 fetch 工具，在对该工具采取任何行动前与用户确认。" /><p>ChatGPT 识别到您需要有关特定问题的详细信息，并调用 <code>fetch</code> 工具，在对该工具采取任何行动前与用户确认。</p><h4>工具调用请求：</h4>{
  "id": "ISSUE-1889"
}<h4>工具响应：</h4>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}<p>ChatGPT 会整合信息并清晰呈现。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt560958fa3bd212d0/6a17f0a0faa91355ba93c974/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" alt="ChatGPT 如何综合信息并显示 " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcccf35a584e8373b/6a17f0a2505ac3471cad8c2e/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" alt="ChatGPT 如何呈现信息" /><h3>幕后</h3><h4>提示：“获取有关 ISSUE-1889 的详细信息”</h4><ol><li><p>ChatGPT 调用 <code>fetch(“ISSUE-1889”)</code></p></li><li><p>Elasticsearch 会检索完整文档</p></li><li><p>返回一个包含所有字段在根级别的完整文档</p></li><li><p>ChatGPT会综合信息并提供正确的引用。</p></li></ol><h2>结论</h2><p>在本文中，我们构建了一个自定义 MCP 服务器，使用专用的<strong>搜索</strong>和<strong>获取</strong> MCP 工具将 ChatGPT 连接到 Elasticsearch，从而实现对私有数据的自然语言查询。</p><p>这种 MCP 模式适用于任何您想通过自然语言查询的 Elasticsearch 索引、文档、产品、日志或其他数据。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
    <category><![CDATA[智能体 AI]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用弹性代理生成器和 GPT-OSS 构建人力资源人工智能代理]]></title>
    <description><![CDATA[了解如何使用 Elastic Agent Builder 和 GPT-OSS 构建一个人工智能代理，回答有关员工人力资源数据的自然语言查询。]]></description>
    <content:encoded><![CDATA[<h2>引言</h2><p>本文将向您展示如何使用<a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS</a>和 Elastic Agent Builder 为人力资源部门构建人工智能代理。代理可以回答你的问题，而无需向 OpenAI、Anthropic 或任何外部服务发送数据。</p><p>我们将使用 LM Studio 在本地为 GPT-OSS 提供服务，并将其连接到 Elastic Agent Builder。</p><p>本文结束时，您将拥有一个定制的人工智能代理，可以回答有关员工数据的自然语言问题，同时保持对信息和模型的完全控制。</p><h2>准备工作</h2><p>这篇文章需要</p><ul><li><p><a href="https://www.elastic.co/cloud">弹性云</a>托管 9.2，无服务器或<a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">本地</a>部署</p></li><li><p>建议使用 32GB 内存的机器（GPT-OSS 20B 最低 16GB 内存）</p></li><li><p>已安装<a href="https://lmstudio.ai/">LM 工作室</a></p></li><li><p>已安装<a href="https://www.docker.com/products/docker-desktop/">Docker 桌面</a></p></li></ul><h2>为什么使用 GPT-OSS？</h2><p>有了本地 LLM，您就可以将其部署到自己的基础设施中，并根据自己的需求进行微调。当然，您也不必向外部供应商支付许可费。</p><p>作为对开放模型生态系统承诺的一部分，OpenAI 于 2025 年 8 月 5 日<a href="https://openai.com/index/introducing-gpt-oss/">发布了 GPT-OSS</a>。</p><p>20B 参数模型提供</p><ul><li><p><strong>工具使用能力</strong></p></li><li><p><strong>高效推理</strong></p></li><li><p><strong>兼容 OpenAI SDK</strong></p></li><li><p><strong>与代理工作流程兼容</strong></p></li></ul><p>基准比较：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58fab956edb40412/6a170cfcb0367da43a72bd80/29160e3345352088e8213297630882f252b00c47-1600x680.png" alt="" /><h2>解决方案架构</h2><p>该架构完全在本地计算机上运行。Elastic（在 Docker 中运行）通过 LM Studio 与本地 LLM 直接通信，Elastic Agent Builder 利用这种连接创建可查询员工数据的自定义人工智能代理。</p><p>有关详细信息，请参阅本<a href="https://www.elastic.co/docs/solutions/observability/connect-to-own-local-llm">文档</a>。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80db5bb0a797f51b/6a170cfd0e2e492f2c41a16f/a4a886750ff25fa8bb7aefc7448161e52cf73ed3-1600x896.png" alt="" /><h2>为人力资源部门建立人工智能代理：步骤</h2><p>我们将把实施分为 5 个步骤：</p><ol><li><p>使用本地模型配置 LM 工作室</p></li><li><p>使用 Docker 部署本地弹性</p></li><li><p>在 Elastic 中创建 OpenAI 连接器</p></li><li><p>将员工数据上传到 Elasticsearch</p></li><li><p>构建并测试人工智能代理</p></li></ol><h2>步骤 1：使用 GPT-OSS 20B 配置 LM Studio</h2><p>LM Studio 是一款用户友好型应用程序，可让您在本地计算机上运行大型语言模型。它提供了与 OpenAI 兼容的 API 服务器，无需复杂的设置过程即可轻松与 Elastic 等工具集成。有关详细信息，请参阅<a href="https://lmstudio.ai/docs/app">LM Studio 文档</a>。</p><p>首先，从官方网站下载并安装LM Studio。安装完成后，打开应用程序。</p><h3>在 LM Studio 界面：</h3><ol><li><p>转到搜索选项卡，搜索 "GPT-OSS</p></li><li><p>从 OpenAI 选择<code>openai/gpt-oss-20b</code> </p></li><li><p>点击下载</p></li></ol><p>该模型的大小约为<strong>12.10GB</strong>。下载可能需要几分钟时间，具体取决于您的网络连接。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2dc341a6625e34b7/6a170cff839dfa2eb4dcff44/5d01bc4dcb377b5259fc6b521fe2425a31b90ca4-1312x872.png" alt="" /><h4>下载模型后</h4><ol><li><p>转到本地服务器选项卡</p></li><li><p>选择 openai/gpt-oss-20b</p></li><li><p>使用默认端口 1234</p></li><li><p>在右侧面板上，转到 "<strong>加载 </strong>"，将上下文长度设置为<strong>40K</strong>或更高</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3704ca1b28465cc4/6a170d00d7c022ed8fde64ef/e546033f916381647b876815b2c1f1ae2a08365f-326x337.png" alt="" /><p>5.单击启动服务器</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b9170a4945ff857/6a170d0266c4f9ffadf8c0a6/28ee78a3caa84d14e04db3d42f30acbe4d4d005a-1312x872.png" alt="" /><p>如果服务器正在运行，您应该会看到这个提示。</p>[LM STUDIO SERVER] Success! HTTP server listening on port 1234
[LM STUDIO SERVER] Supported endpoints:
[LM STUDIO SERVER] -&gt;	GET  http://localhost:1234/v1/models
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/responses
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/chat/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/embeddings
Server started.<h2>第 2 步：使用 Docker 部署本地弹性</h2><p>现在，我们将使用 Docker 在本地设置 Elasticsearch 和 Kibana。Elastic 提供了一个方便的脚本来处理整个设置过程。更多详情，请参阅<a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">官方文档</a>。</p><h3>运行启动本地脚本</h3><p>在终端中执行以下命令</p>curl -fsSL https://elastic.co/start-local | sh<p>该脚本将</p><ul><li><p>下载并配置 Elasticsearch 和 Kibana</p></li><li><p>使用 Docker Compose 启动两个服务</p></li><li><p>自动激活 30 天白金试用版许可证</p></li></ul><h3>预期产出</h3><p>只需等待以下信息并保存显示的密码和 API 密钥；访问 Kibana 时需要它们：</p>🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: KSUlOMNr
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: cnJGX0pwb0JhOG00cmNJVklUNXg6cnNJdXZWMnM4bncwMllpQlFlUTlWdw==
Learn more at https://github.com/elastic/start-local<h3>访问 Kibana</h3><p>打开浏览器并导航至</p>http://localhost:5601<p>使用终端输出中获得的证书登录。</p><h3>启用代理生成器</h3><p>登录 Kibana 后，导航至<strong>管理 </strong>&gt;<strong> AI </strong>&gt;<strong> Agent Builder </strong>并激活 Agent Builder。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a934bd99fa6a0ce/6a170d046234e019c3db1a5a/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" alt="" /><h2>第 3 步：在 Elastic 中创建 OpenAI 连接器</h2><p>现在，我们将配置 Elastic 以使用本地 LLM。</p><h3>接入连接器</h3><ol><li><p>在 Kibana 中</p></li><li><p>转到<strong>项目设置</strong> &gt; <strong>管理</strong></p></li><li><p>在<strong>"警报和洞察 "</strong>下，选择 "<strong>连接器</strong></p></li><li><p>单击创建连接器</p></li></ol><h3>配置连接器</h3><p>从连接器列表中选择<strong>OpenAI</strong>。LM Studio 使用 OpenAI SDK，因此与 OpenAI 兼容。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt762023c39781eb78/6a170d06a29299a59ed01087/5ac87042e086c7a2bd47a8039e646ec831f0dcc6-923x974.png" alt="" /><p>用这些值填写字段：</p><ul><li><p><strong>连接器名称： </strong>LM Studio - GPT-OSS 20B</p></li><li><p><strong>选择 OpenAI 提供商： </strong>其他（OpenAI 兼容服务）</p></li><li><p><strong>URL： </strong><code>http://host.docker.internal:1234/v1/chat/completions</code></p></li><li><p><strong>默认型号： </strong>openai/gpt-oss-20b</p></li><li><p><strong>API 密钥：</strong>testkey-123（任何文本都可以，因为 LM Studio 服务器不要求验证。）</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt980e595f80e2be2e/6a170d086f7f0468a19148cc/2084ac32fcf1fb810c8b54ecab1c85a1e3e8905b-672x1302.png" alt="" /><p>要完成配置，请单击<strong>保存&amp; 测试</strong>。</p><p><strong>重要：</strong>打开 "<strong>启用本地函数调用</strong>"；这是使代理生成器正常工作的必要条件。如果不启用，就会出现<strong><code>No tool calls found in the response</code></strong> 错误。</p><h3>测试连接</h3><p>Elastic 会自动测试连接。如果一切配置正确，您将看到如下成功信息：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d2e815dd558f881/6a170d090e2e49076541a177/f567d767f1969c4730c1daa92f651789dc3742ac-1042x812.png" alt="" /><p>响应：</p>{
  "status": "ok",
  "data": {
    "id": "chatcmpl-flj9h0hy4wcx4bfson00an",
    "object": "chat.completion",
    "created": 1761189456,
    "model": "openai/gpt-oss-20b",
    "choices": [
      {
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Hello! 👋 How can I assist you today?",
          "reasoning": "Just greet.",
          "tool_calls": []
        },
        "logprobs": null,
        "finish_reason": "stop"
      }
    ],
    "usage": {
      "prompt_tokens": 69,
      "completion_tokens": 23,
      "total_tokens": 92
    },
    "stats": {},
    "system_fingerprint": "openai/gpt-oss-20b"
  },
  "actionId": "ee1c3aaf-bad0-4ada-8149-118f52dad757"
}<h2>第 4 步：将员工数据上传到 Elasticsearch</h2><p>现在，我们将上传<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">人力资源员工数据集</a>，以演示代理如何处理敏感数据。我用这种结构生成了一个虚构的数据集。</p><h3>数据集结构</h3>{
  "employee_id": "0f4dce68-2a09-4cb1-b2af-6bcb4821539b",
  "full_name": "Daffi Stiebler",
  "email": "lscutchings0@huffingtonpost.com",
  "date_of_birth": "1975-06-20T15:39:36Z",
  "hire_date": "2025-07-28T00:10:45Z",
  "job_title": "Physical Therapy Assistant",
  "department": "HR",
  "salary": "108455",
  "performance_rating": "Needs Improvement",
  "years_of_experience": 2,
  "skills": "Java",
  "education_level": "Master's Degree",
  "manager": "Carl MacGibbon",
  "emergency_contact": "Leigha Scutchings",
  "home_address": "5571 6th Park"
}<h3>使用映射创建索引</h3><p>首先，创建具有适当映射的索引。请注意，我们对一些关键字段使用了<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text 字段</a>；这样就能为我们的索引提供语义搜索功能。</p>​​PUT hr-employees
{
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "employee_id": {
        "type": "keyword"
      },
      "full_name": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "email": {
        "type": "keyword"
      },
      "date_of_birth": {
        "type": "date",
        "format": "iso8601"
      },
      "hire_date": {
        "type": "date",
        "format": "iso8601"
      },
      "job_title": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "department": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "salary": {
        "type": "double"
      },
      "performance_rating": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "years_of_experience": {
        "type": "long"
      },
      "skills": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "education_level": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "manager": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "emergency_contact": {
        "type": "keyword"
      },
      "home_address": {
        "type": "keyword"
      },
      "employee_semantic": {
        "type": "semantic_text"
      }
    }
  }
}<h3>使用批量 API 索引</h3><p>将<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">数据集</a>复制并粘贴到 Kibana 的 Dev Tools 中并执行：</p>POST hr-employees/_bulk
{"index": {}}
{"employee_id": "57728b91-e5d7-4fa8-954a-2384040d3886", "full_name": "Filide Gane", "email": "vhallahan1@booking.com", "job_title": "Business Systems Development Analyst", "department": "Marketing", "salary": "$52330.27", "performance_rating": "Meets Expectations", "years_of_experience": 12, "skills": "Java", "education_level": "Bachelor's Degree", "date_of_birth": "2000-02-07T16:49:32Z", "hire_date": "2023-11-07T13:03:16Z", "manager": "Freedman Kings", "emergency_contact": "Vilhelmina Hallahan", "home_address": "75 Dennis Junction"}
{"index": {}}
{"employee_id": "...", ...}<h3>验证数据</h3><p>运行查询进行验证：</p>GET hr-employees/_search<h2>第 5 步：构建并测试人工智能代理</h2><p>一切配置完成后，就可以使用 Elastic Agent Builder 创建自定义人工智能代理了。有关详细信息，请参阅<a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">Elastic 文档</a>。</p><h3>添加连接器</h3><p>在创建新代理之前，我们必须将代理生成器设置为使用名为<code>LM Studio - GPT-OSS 20B</code> 的自定义连接器，因为默认连接器是<a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a>。为此，我们需要进入 "<strong>项目设置</strong>"&gt; <strong>"管理</strong>"&gt; <strong>"GenAI 设置"</strong>；现在选择我们创建的设置，然后单击 "<strong>保存"</strong>。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc42f079c5e756057/6a170d0acf4f2501d9b2d1c7/11e830c3e2fb4c298b020c928fa5422f3397ba08-1600x1152.png" alt="" /><h3>访问代理生成器</h3><ol><li><p>前往<strong>代理商</strong></p></li><li><p>点击<strong>创建新代理</strong></p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8e734817c5a7c6a/6a170d0ca929cf867cae0a34/c1e60541563650163f972ac9088dc1ed1de759a7-1600x1054.png" alt="" /><h3>配置代理</h3><p>要创建新代理，必须填写<strong>代理 ID</strong>、<strong>显示名称</strong>和<strong>显示说明</strong>。</p><p>但还有更多的自定义选项，比如 "自定义指令"，它可以指导代理如何与工具进行交互，类似于系统提示，但适用于我们的自定义代理。标签可帮助您组织代理人、头像颜色和头像符号。</p><p>我根据数据集为我们的代理选择的<strong>代理编号</strong>是：

Agent ID： <code>hr_assistant</code></p><p><strong>自定义说明：</strong></p>You are an HR Analytics Assistant that helps answer questions about employee data.
When responding to queries:
- Provide clear, concise answers
- Include relevant employee details (name, department, salary, skills)
- Format monetary values with currency symbols
- Be professional and maintain data confidentiality<p>
标签：<code>Human Resources</code> 和 <code>GPT-OSS</code></p><p>显示名称： <code>HR Analytics Assistant</code></p><p>显示说明：</p>A specialized AI assistant for Human Resources that helps analyze employee data, compensation, performance metrics, and talent management. Ask questions about employees, departments, salaries, or performance analytics.<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23fb011e5b4f4d49/6a170d0e7d8d67f47a70e77f/f94bb2bf08497e5e756ca76b30a3a51f42927756-1424x1217.png" alt="" /><p>有了所有数据，我们就可以点击 "<strong>保存</strong>新代理"。</p><h3>测试代理</h3><p>现在，您可以就员工数据提出自然语言问题，GPT-OSS 20B 将理解您的意图并生成适当的回复。</p><h4>提示：</h4>Which employee is the one with the highest salary in the hr-employees index?<h4>请回答：</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0c52faacf63b583/6a170d0f0e2e497bfd41a17b/94ad19f80b96304028a59f60beca51dfc9aecc8a-899x631.png" alt="" /><p>代理过程是</p><p>1.使用 GPT-OSS 连接器了解您的问题</p><p>2.生成适当的 Elasticsearch 查询（使用内置工具或自定义<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL）</a></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte32a8a7e6363c7f2/6a170d115091680077e1bb44/6f2961d0d1b97475f6dda300acee84da540938e6-844x466.png" alt="" /><p>3.检索匹配的员工记录</p><p>4.以自然语言和适当的格式呈现结果</p><p>与传统的词法搜索不同，由 GPT-OSS 支持的代理可以理解意图和上下文，从而在不知道确切字段名称或查询语法的情况下更容易找到信息。有关代理人思维过程的更多详情，请参阅<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance">本文</a>。</p><h2>结论</h2><p>在本文中，我们使用 Elastic 的代理生成器（Agent Builder）构建了一个自定义人工智能代理，以连接到本地运行的 OpenAI GPT-OSS 模型。通过在本地机器上部署 Elastic 和 LLM，这种架构可以让您利用生成式人工智能功能，同时保持对数据的完全控制，而无需向外部服务发送信息。</p><p>我们使用 GPT-OSS 20B 作为实验，但<a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">此处</a>参考了官方推荐的 Elastic Agent Builder 模型。如果您需要更高级的推理能力，还可以选择<a href="https://huggingface.co/openai/gpt-oss-120b">120B 参数变体</a>，它在复杂情况下的表现更好，不过需要更高级的机器才能在本地运行。更多详情，请参阅<a href="https://openai.com/open-models/">OpenAI 官方文档</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</guid>
    <category><![CDATA[智能体 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt664f490053e46e6b/6a170d13b0367d2d7e72bd84/05d2d0513fff67d975f9223d75108aa9f50646bc-1600x914.png" length="0" type="image/png"/>
    <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[人工智能驱动的仪表盘：从设想到 Kibana]]></title>
    <description><![CDATA[使用 LLM 生成仪表盘，处理图像并将其转化为 Kibana 仪表盘。
]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana/kibana-lens">Kibana Lens</a>让仪表盘的拖放变得非常简单，但当你需要几十个面板时，点击次数就会增加。如果你能勾画出一个仪表盘，截图后让法律硕士为你完成整个过程，那会怎么样？</p><p>在本文中，我们将实现这一目标。我们将创建一个应用程序，它可以获取仪表盘的图像，分析映射，然后生成仪表盘，而无需接触 Kibana！</p><p><strong>步骤</strong>：</p><ol><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#background-&amp;-application-workflow">后台&amp; 应用程序工作流程</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#prepare-data">准备数据</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#llm-configuration">LLM 配置</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#application-functions">应用功能</a></p></li></ol><h2>后台&amp; 应用程序工作流程</h2><p>我首先想到的是让 LLM 生成整个 NDJSON 格式的 Kibana<a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/saved-objects">保存对象</a>，然后将它们导入 Kibana。</p><p>我们尝试了几种型号：</p><ul><li><p>双子座 2.5 pro</p></li><li><p>GPT o3 / o4-mini-high / 4.1</p></li><li><p>克劳德 4 号十四行诗</p></li><li><p>Grok 3</p></li><li><p>Deepseek (Deepthink R1)</p></li></ul><p>至于提示语，我们从最简单的开始：</p>You are an Elasticsearch Saved-Object generator (Kibana 9.0).
INPUTS
=====
1. PNG screenshot of a 4-panel dashboard (attached).
2. Index mapping (below) – trimmed down to only the fields present in the screenshot.
3. Example NDJSON of *one* metric visualization (below) for reference.

TASK
====
Return **only** a valid NDJSON array that recreates the dashboard exactly:
* 2 metric panels (Visits, Unique Visitors)
* 1 pie chart (Most used OS)
* 1 vertical bar chart (State Geo Dest)
* Use index pattern `kibana_sample_data_logs`.
* Preserve roughly the same layout (2×2 grid).
* Use `panelIndex` values 1-4 and random `id` strings.
* Kibana version: 9.0<p>尽管我们看了<a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic#:~:text=Few%2Dshot%20prompting%20involves%20providing%20examples%20of%20the%20types%20of%20queries%20you%20want%20it%20to%20return%2C%20which%20helps%20in%20increasing%20consistency.">一些简单的示例</a>，并详细解释了如何建立每种可视化，但我们还是一无所获。如果您对这项实验感兴趣，请<a href="https://gist.github.com/TomasMurua/a78dc283e115624731beffc98984b70b">点击此处</a>了解详情。</p><p>采用这种方法的结果是，在尝试将 LLM 生成的文件上传到 Kibana 时看到了这些信息：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9ea005966a783057/6a1707d266c4f90e4ef8bf88/2b599443b5613c9f0fc3235581614add5b4b3900-891x98.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e5632d6d95b998c/6a1707d3a6c2b9441de79661/d87ccfc033bc00ee8188c5cae18043fbca22784c-741x233.png" alt="" /><p>这意味着生成的 JSON 无效或格式不当。最常见的问题是 LLM 生成不完整的 NDJSON、产生参数幻觉，或者返回普通 JSON 而非 NDJSON，无论我们如何努力去执行其他操作。</p><p>受<a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">这篇文章</a>的启发--<a href="https://www.elastic.co/docs/solutions/search/search-templates">搜索模板</a>比 LLM 自由式更有效--我们决定给 LLM 提供模板，而不是要求它生成完整的 NDJSON 文件，然后我们在代码中使用 LLM 给出的参数来创建适当的可视化。</p><p>申请工作流程如下：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f7738a4c7ddd0cd/6a1707d52b835f0a25f4b166/52c587cf0cf3517fdd4ee7ab95581dd4f2bce030-725x668.png" alt="" /><p></p><p><em>为简单起见，我们将省略一些代码，但您可以在 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/from-image-idea-to-kibana-dashboard-using-ai.ipynb"><em><strong>本</strong></em></a><em> 笔记本</em>上找到完整应用程序的工作代码  。</p><h2>准备工作</h2><p>在开始开发之前，您需要具备以下条件：</p><ol><li><p>Python 3.8 或更高版本</p></li><li><p><a href="https://docs.python.org/3/library/venv.html">Venv</a>Python 环境</p></li><li><p>运行的 Elasticsearch 实例及其端点和 API 密钥</p></li><li><p>存储在环境变量 OPENAI_API_KEY 下的 OpenAI API 密钥：</p></li></ol>export OPENAI_API_KEY="your-openai-api-key"<h2>准备数据</h2><p>在数据方面，我们将保持简单，使用 Elastic 样本网络日志。您可以<a href="https://www.elastic.co/docs/manage-data/ingest/sample-data#add-sample-data-sets">在此</a>了解如何将这些数据导入群集。</p><p>每份文档都包含向应用程序发出请求的主机的详细信息，以及请求本身及其响应状态的信息。下面是一个文件示例：</p>{
    "agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24",
    "bytes": 8509,
    "clientip": "70.133.115.149",
    "extension": "css",
    "geo": {
        "srcdest": "US:IT",
        "src": "US",
        "dest": "IT",
        "coordinates": {
            "lat": 38.05134111,
            "lon": -103.5106908
        }
    },
    "host": "cdn.elastic-elastic-elastic.org",
    "index": "kibana_sample_data_logs",
    "ip": "70.133.115.149",
    "machine": {
        "ram": 5368709120,
        "os": "osx"
    },
    "memory": null,
    "message": "70.133.115.149 - - [2018-08-30T23:35:31.492Z] \"GET /styles/semantic-ui.css HTTP/1.1\" 200 8509 \"-\" \"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24\"",
    "phpmemory": null,
    "referer": "http://twitter.com/error/john-phillips",
    "request": "/styles/semantic-ui.css",
    "response": 200,
    "tags": [
        "success",
        "info"
    ],
    "@timestamp": "2025-07-03T23:35:31.492Z",
    "url": "https://cdn.elastic-elastic-elastic.org/styles/semantic-ui.css",
    "utc_time": "2025-07-03T23:35:31.492Z",
    "event": {
        "dataset": "sample_web_logs"
    },
    "bytes_gauge": 8509,
    "bytes_counter": 51201128
}<p>现在，让我们抓取刚刚加载的索引的映射，<code>kibana_sample_data_logs</code> ：</p>INDEX_NAME = "kibana_sample_data_logs"

es_client = Elasticsearch(
    [os.getenv("ELASTICSEARCH_URL")],
    api_key=os.getenv("ELASTICSEARCH_API_KEY"),
)

result = es_client.indices.get_mapping(index=INDEX_NAME)
index_mappings = result[list(result.keys())[0]]["mappings"]["properties"]<p>我们将把映射与稍后加载的图像一起传递。</p><h2>LLM 配置</h2><p>让我们对 LLM 进行配置，使其使用<a href="https://python.langchain.com/docs/concepts/structured_outputs/">结构化输出</a>来输入图像，并接收包含我们需要传递给函数的信息的 JSON，以生成 JSON 对象。</p><p>我们安装依赖项：</p>pip install elasticsearch pydantic langchain langchain-openai -q<p>Elasticsearch 将帮助我们检索<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">索引映射</a>。Pydantic 允许我们在 Python 中定义模式，然后要求 LLM 遵循这些模式，而<a href="https://www.elastic.co/search-labs/integrations/langchain">LangChain</a>框架则有助于更轻松地调用 LLM 和人工智能工具。</p><p>我们将创建一个 Pydantic 模式，以定义我们希望从 LLM 得到的输出。我们需要从图片中了解图表类型、字段、可视化标题和仪表盘标题：</p>class Visualization(BaseModel):
    title: str = Field(description="The dashboard title")
    type: List[Literal["pie", "bar", "metric"]]
    field: str = Field(
        description="The field that this visualization use based on the provided mappings"
    )


class Dashboard(BaseModel):
    title: str = Field(description="The dashboard title")
    visualizations: List[Visualization]<p>对于图像输入，我们将发送一个我刚刚画好的仪表盘：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7870f6421986d11d/6a1707d78b73cb3408189fa3/36441d7b5dc1f3ff2ac2a30710208d57ad41c716-1600x898.jpg" alt="" /><p>现在我们声明 LLM 模型调用和图像加载。该函数将接收 Elasticsearch 索引的映射和我们要生成的仪表盘图像。</p><p>通过<code>with_structured_output</code> ，我们可以使用 Pydantic<code>Dashboard</code> 模式作为 LLM 生成的响应对象。通过<a href="https://docs.pydantic.dev/latest/">Pydantic</a>，我们可以定义带有验证功能的数据模型，从而确保 LLM 输出与预期结构相匹配。</p><p>要将图像转换为 base64 并作为输入发送，可以使用<a href="https://www.base64-image.de/">在线转换器</a> <a href="https://www.geeksforgeeks.org/python-convert-image-to-string-and-vice-versa/">或用代码</a>完成。</p>prompt = f"""
    You are an expert in analyzing Kibana dashboards from images for the version 9.0.0 of Kibana.

    You will be given a dashboard image and an Elasticsearch index mapping.

    Below are the index mappings for the index that the dashboard is based on.
    Use this to help you understand the data and the fields that are available.

    Index Mappings:
    {index_mappings}

    Only include the fields that are relevant for each visualization, based on what is visible in the image.
    """

message = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": prompt},
            {
                "type": "image",
                "source_type": "base64",
                "data": image_base64,
                "mime_type": "image/png",
            },
        ],
    }
]


try:
    llm = init_chat_model("gpt-4.1-mini")
    llm = llm.with_structured_output(Dashboard)
    dashboard_values = llm.invoke(message)

    print("Dashboard values generated by the LLM successfully")
    print(dashboard_values)
except Exception as e:
    print(f"Failed to analyze image and match fields: {str(e)}")<p>LLM 已经掌握了 Kibana 面板的上下文，因此我们不需要在提示中解释所有内容，只需提供一些细节，确保它不会忘记自己正在使用 Elasticsearch 和 Kibana。</p><p>让我们来分析一下提示：</p><p>部门</p><p>原因</p><p>您是根据 Kibana 9.0.0 版本的图像分析 Kibana 仪表板的专家。</p><p>通过强化 Elasticsearch 和 Elasticsearch 版本，我们降低了 LLM 产生旧参数/无效参数的可能性。</p><p>您将获得一个仪表盘图像和一个 Elasticsearch 索引映射。</p><p>我们解释说，图片是关于仪表盘的，以避免法律硕士做出任何错误的解释。</p><p>下面是仪表盘所基于的索引的索引映射，使用它可以帮助你理解数据和可用字段。索引映射： {index_mappings}</p><p>提供映射至关重要，这样 LLM 才能动态选择有效字段。否则，我们就可能在这里硬编码映射，这太死板了，或者依靠图像包含正确的字段名，这也不可靠。</p><p>根据图像中可见的内容，只包含与每个可视化相关的字段。</p><p>我们必须添加这一增强功能，因为有时它会尝试添加与图像无关的字段。</p><p>这将返回一个包含要显示的可视化数组的对象：</p>"Dashboard values generated by the LLM successfully
title=""Client, Extension, OS, and Response Keyword Analysis""visualizations="[
   "Visualization(title=""Count of Client IP",
   "type="[
      "metric"
   ],
   "field=""clientip"")",
   "Visualization(title=""Extension Keyword Distribution",
   "type="[
      "pie"
   ],
   "field=""extension.keyword"")",
   "Visualization(title=""Most Used OS",
   "type="[
      "bar"
   ],
   "field=""machine.os.keyword"")",
   "Visualization(title=""Response Keyword Distribution",
   "type="[
      "bar"
   ],
   "field=""response.keyword"")"
]<h2>处理 LLM 答复</h2><p>我们在上创建了一个 2x2 面板仪表盘示例，然后使用 "<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-dashboards-dashboard">获取仪表盘 API "</a>将其导出为 JSON 格式，然后将面板存储为可视化模板（饼状、条状、度量），在这些模板中，我们可以替换部分参数，根据问题创建带有不同字段的新可视化。</p><p>您可以<a href="https://github.com/Delacrobix/elasticsearch-labs/tree/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/templates"><strong>在此处</strong></a>查看模板 JSON 文件。请注意我们是如何用 {<code>variable_name</code>} 更改我们稍后要替换的对象值的。
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc55d69d84a08e668/6a1707d8a2929903acd00fb8/ec7e1ac0cd8b470df13e60940162b56778acb386-315x234.png" alt="" /><p>根据 LLM 提供的信息，我们可以决定使用哪个模板，替换哪些值。</p><p><code>fill_template_with_analysis</code> 将接收单个面板的参数，包括可视化的 JSON 模板、标题、字段和可视化在网格上的坐标。</p><p>然后，它会替换模板的值，并返回最终的 JSON 可视化。</p>def fill_template_with_analysis(
    template: Dict[str, Any],
    visualization: Visualization,
    grid_data: Dict[str, Any],
):
    template_str = json.dumps(template)
    replacements = {
	 "{visualization_id}": str(uuid.uuid4()),
        "{title}": visualization.title,
        "{x}": grid_data["x"],
        "{y}": grid_data["y"],
    }

    if visualization.field:
        replacements["{field}"] = visualization.field

    for placeholder, value in replacements.items():
        template_str = template_str.replace(placeholder, str(value))

    return json.loads(template_str)<p>为了简单起见，我们将为 LLM 决定创建的面板分配静态坐标，并生成如上图所示的 2x2 网格仪表盘。</p># Filling templates fields
panels = []    
grid_data = [
    {"x": 0, "y": 0},
    {"x": 12, "y": 0},
    {"x": 0, "y": 12},
    {"x": 12, "y": 12},
]


i = 0

for vis in dashboard_values.visualizations:
    for vis_type in vis.type:
        template = templates.get(vis_type, templates.get("bar", {}))
        filled_panel = fill_template_with_analysis(template, vis, grid_data[i])
        panels.append(filled_panel)
        i += 1<p>根据 LLM 决定的可视化类型，我们将选择一个 JSON 文件模板，并使用<code>fill_template_with_analysis</code> 替换相关信息，然后将新面板追加到稍后用于创建仪表盘的数组中。</p><p>仪表盘准备就绪后，我们将使用<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-dashboards-dashboard-id"> 创建 仪表盘 API</a> 将新的 JSON 文件推送到 Kibana 以生成仪表盘：
</p>try:
    dashboard_id = str(uuid.uuid4())

    # post request to create the dashboard endpoint
    url = f"{os.getenv('KIBANA_URL')}/api/dashboards/dashboard/{dashboard_id}"

    dashboard_config = {
        "attributes": {
            "title": dashboard_values.title,
            "description": "Generated by AI",
            "timeRestore": True,
            "panels": panels,  # Visualizations with the values generated by the LLM
            "timeFrom": "now-7d/d",
            "timeTo": "now",
        },
    }

    headers = {
        "Content-Type": "application/json",
        "kbn-xsrf": "true",
        "Authorization": f"ApiKey {os.getenv('ELASTICSEARCH_API_KEY')}",
    }

    requests.post(
        url,
        headers=headers,
        json=dashboard_config,
    )

    # Url to the generated dashboard
    dashboard_url = f"{os.getenv('KIBANA_URL')}/app/dashboards#/view/{dashboard_id}"

    print("Dashboard URL: ", dashboard_url)
    print("Dashboard ID: ", dashboard_id)

except Exception as e:
    print(f"Failed to create dashboard: {str(e)}")<p>要执行脚本并生成仪表盘，请在控制台中运行以下命令：</p>python &lt;file_name&gt;.py<p>最终结果将是这样的</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ceffed004153a4f/6a1707d9a929cf9147ae0901/e909afbf0e47d9a6e0f7bd07dfb2efcfa5cf06ac-921x715.png" alt="" /><h2>结论</h2><p>在将文本转化为代码或将图像转化为代码时，LLM 展示了其强大的视觉能力。仪表盘 API 还能将 JSON 文件转化为仪表盘，而通过 LLM 和一些代码，我们就能将图片转化为 Kibana 仪表盘。</p><p>下一步是通过使用不同的网格设置、仪表盘大小和位置来提高仪表盘视觉效果的灵活性。此外，为更复杂的可视化和可视化类型提供支持也是对该应用程序的有益补充。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-powered-dashboards</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-powered-dashboards</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo,Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt41727cbee6155a68/6a1707dbb0367dd2fd72bc86/eb60ceb2fbc3941745b21ae3357cbb6ea8fab18c-1443x811.png" length="0" type="image/png"/>
    <pubDate>Wed, 16 Jul 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>