在今天的文章里,我来为大家来展示如何在自己的电脑使用一个命令来启动 Elastic Stack,并进行 AI Agents 的开发。在我之前的文章 "使用 start-local 脚本在本地运行 Elasticsearch" 有介绍,但是它不含有对 AI 方面的配置。在今天的文章里,我来一步一步地详述如何安装并进行配置。
警告 :请勿将这些说明用于生产部署
本页上的说明仅适用于本地开发。请勿将此配置用于生产部署,因为它不安全。请参阅部署选项以获取生产部署选项列表。
使用 start-local 脚本在 Docker 中快速设置 Elasticsearch 和 Kibana 以进行本地开发或测试。
此设置附带一个月的试用许可证,其中包括所有 Elastic 功能。试用期过后,许可证将恢复为免费和开放 - Basic 。有关更多信息,请参阅 Elastic 订阅。
更多阅读:Elasticsearch:如何在 Docker 上运行 Elasticsearch 8.x 进行本地开发
先决条件
- 如果你尚未安装 Docker,请下载并安装适用于你的操作系统的 Docker Desktop。
- 如果你使用的是 Microsoft Windows,请安装适用于 Linux 的 Windows 子系统 (WSL)。
运行 start-local
start-local 脚本可以在地址进行下载 GitHub - elastic/start-local: Try Elasticsearch and Kibana locally。要在本地设置 Elasticsearch 和 Kibana,请运行 start-local 脚本。
sql
`curl -fsSL https://elastic.co/start-local | sh`AI写代码
此脚本创建一个 elastic-start-local 文件夹,其中包含:
- docker-compose.yml:Elasticsearch 和 Kibana 的 Docker Compose 配置
- .env:环境设置,包括 Elasticsearch 密码
- start.sh 和 stop.sh:用于启动和停止 Elasticsearch 和 Kibana 的脚本
- uninstall.sh:用于卸载 Elasticsearch 和 Kibana 的脚本
markdown
``
1. $ curl -fsSL https://elastic.co/start-local | sh
3. ______ _ _ _
4. | ____| | | | (_)
5. | |__ | | __ _ ___| |_ _ ___
6. | __| | |/ _` / __| __| |/ __|
7. | |____| | (_| \__ \ |_| | (__
8. |______|_|\__,_|___/\__|_|\___|
9. -------------------------------------------------
10. 🚀 Run Elasticsearch and Kibana for local testing
11. -------------------------------------------------
13. ℹ️ Do not use this script in a production environment
15. ⌛️ Setting up Elasticsearch and Kibana v9.4.4-arm64...
17. - Generated random passwords
18. - Created the elastic-start-local folder containing the files:
19. - .env, with settings
20. - docker-compose.yml, for Docker services
21. - start/stop/uninstall commands
22. - Running docker compose up --wait
24. [+] up 30/30
25. ✔ Image docker.elastic.co/elasticsearch/elasticsearch:9.4.4-arm64 Pulled 85.3s
26. ✔ Image docker.elastic.co/kibana/kibana:9.4.4-arm64 Pulled 96.4s
27. ✔ Network elastic-start-local_default Created 0.0s
28. ✔ Volume elastic-start-local_dev-kibana Created 0.0s
29. ✔ Volume elastic-start-local_dev-elasticsearch Created 0.0s
30. ✔ Container es-local-dev Healthy 22.3s
31. ✔ Container kibana-local-settings Exited 21.9s
32. ✔ Container kibana-local-dev Healthy 31.9s
34. 🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
36. 🌐 Open your browser at http://localhost:5601
38. Username: elastic
39. Password: chy08Gig
41. 🔌 Elasticsearch API endpoint: http://localhost:9200
42. 🔑 API key: RUk1Mnk1OEJnUjJZeWxGLXBza3o6TlRKUVpRaHIyLUd0NGpOMk1IbkdUZw==
44. Learn more at https://github.com/elastic/start-local
``AI写代码
上面展示了超级用户 elastic 及其密码。我们可以保存之后使用。我们还可以看到 Elasticsearch API endpoint 及其 API key。这个都可以在我们之后的代码中进行使用。更多信息:GitHub - elastic/start-local: Try Elasticsearch and Kibana locally · GitHub
我们可以查看一下当前目录下的文档:
markdown
`
1. $ pwd
2. /Users/liuxg/data/local/elastic-start-local
3. $ ls -al
4. total 40
5. drwxr-xr-x 8 liuxg staff 256 Aug 4 14:31 .
6. drwxr-xr-x 3 liuxg staff 96 Aug 4 14:27 ..
7. -rw-r--r-- 1 liuxg staff 587 Aug 4 14:29 .env
8. drwxr-xr-x 3 liuxg staff 96 Aug 4 14:31 config
9. -rw-r--r-- 1 liuxg staff 3262 Aug 4 14:27 docker-compose.yml
10. -rwxr-xr-x 1 liuxg staff 2099 Aug 4 14:27 start.sh
11. -rwxr-xr-x 1 liuxg staff 191 Aug 4 14:27 stop.sh
12. -rwxr-xr-x 1 liuxg staff 2014 Aug 4 14:27 uninstall.sh
`AI写代码
.env
ini
`
1. START_LOCAL_VERSION=0.14.0
2. ES_LOCAL_VERSION=9.4.4-arm64
3. ES_LOCAL_CONTAINER_NAME=es-local-dev
4. ES_LOCAL_PASSWORD=chy08Gig
5. ES_LOCAL_PORT=9200
6. ES_LOCAL_URL=http://localhost:${ES_LOCAL_PORT}
7. ES_LOCAL_DISK_SPACE_REQUIRED=1gb
8. ES_LOCAL_LICENSE_EXPIRE_DATE=1788416849
9. ES_LOCAL_JAVA_OPTS="-Xms128m -Xmx2g"
10. KIBANA_LOCAL_CONTAINER_NAME=kibana-local-dev
11. KIBANA_LOCAL_SETTINGS_CONTAINER_NAME=kibana-local-settings
12. KIBANA_LOCAL_PORT=5601
13. KIBANA_LOCAL_PASSWORD=F0mBEN7D
14. KIBANA_ENCRYPTION_KEY=Zh1HVQHOiLnQDExLQ4j7fSVbTI5KSCq5
15. ES_LOCAL_API_KEY=RUk1Mnk1OEJnUjJZeWxGLXBza3o6TlRKUVpRaHIyLUd0NGpOMk1IbkdUZw==
`AI写代码
在上面,我们可以看到超级用户 elastic 的密码及 API key 等信息。
我们可以使用浏览器来访问 http://localhost:9200:

我们必须输入 elastic 及其密码。如果你能看到上面的信息,那么恭喜你:你已经成功地在自己的电脑里创建了一个 Elasticsearch 实例。
我们也可以访问 Kibana 的地址 http://localhost:5601:

同样地,我们输入 elastic 用户及其密码即可。
我们可以使用脚步 stop.sh 来停止 Elasticsearch 及 Kibana 的运行。
arduino
`./stop.sh`AI写代码
markdown
`1. $ ./stop.sh
2. [+] stop 3/3
3. ✔ Container kibana-local-dev Stopped 0.9s
4. ✔ Container kibana-local-settings Stopped 0.0s
5. ✔ Container es-local-dev Stopped` AI写代码
我们可以使用 start.sh 来重新启动实例的运行:
bash
`./start.sh`AI写代码
markdown
`1. $ ./start.sh
2. [+] up 3/3
3. ✔ Container es-local-dev Healthy 21.7s
4. ✔ Container kibana-local-settings Exited 1.1s
5. ✔ Container kibana-local-dev Healthy` AI写代码
我们可以使用如下的命令来检查容器的运行情况:
go
`docker ps`AI写代码
bash
`
1. $ docker ps
2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3. 306e3e0d8923 docker.elastic.co/kibana/kibana:9.4.4-arm64 "/bin/tini -- /usr/l..." 23 hours ago Up 2 minutes (healthy) 127.0.0.1:5601->5601/tcp kibana-local-dev
4. d62af2553125 docker.elastic.co/elasticsearch/elasticsearch:9.4.4-arm64 "/bin/tini -- /usr/l..." 23 hours ago Up 2 minutes (healthy) 127.0.0.1:9200->9200/tcp es-local-dev
`AI写代码
配置 AI Agent Builder
我们点击如下的图标:

我们可以看到没有现成的 LLM 供我们使用。为了配置这个,我们有如下的几种方式:
1)配置 chat completion endpoint



我们以 DeepSeek 为例:

注意:在点击上面的 Svae 按钮之前,如果你有 V.P.N,保存可能会失败。
在上面,我们使用 DeepSeek 官方的 API key 来进行配置。关于 Inference endpoint ID,你也可以取一个自己喜欢的名字。点击上面的 Save 按钮:

我们看到最新创建的 deep-chat_completion inference endpoint。我们可以在 Kibana 的 Dev Toosl 进行查看:
bash
`GET _inference/_all`AI写代码

我们再次回到 Agents 页面,我们可以看到 deepseek-chat_completion 自动被选上:

我们打入一个如下的问题:

很显然,我们的配置是成功的。
2)配置一个连接器
我们也可以配置一个连机器来工作:




更加详细的步骤,你可以参考文章 "Elasticsearch:在 Elastic 中玩转 DeepSeek R1 来实现 RAG 应用"。点击上面的 Save 按钮。当然,我们也可以点击上面的 Save & test 按钮:

上面显示我们的配置是成功的。我们再次回到 Agents 页面:

这次,我们可以看到多一个选择。选中 deepseek-official:

很显然,我们的配置是成功的。
3)Cloud Connect
对于有 Elastic Cloud 账号的开发者来说,你可以通过 Cloud Connec 来进行连接:





我们再回到 Agents 页面:

这次我们可以看到更多的 模型 可以供选择:

很显然,我们的配置是成功的。
更多有关 Cloud Connect 的知识,请参考文章 "跳过 MLOps:通过 Cloud Connect 使用 EIS 为自管理 Elasticsearch 提供托管云推理"。
下在 E5 嵌入模型
这个部分是可选的。如果你的应用需要用到向量嵌入模型,那么你可以下载 E5 嵌入模型,并使用它做向量搜索。具体的步骤如下:


下周的速度依赖于你的网络速度:

我们可以使用如下的命令来查看:
bash
`GET _inference/_all`AI写代码

我们也可以使用如下的名来查看一下它是否工作:
bash
`
1. POST _inference/.multilingual-e5-small-elasticsearch
2. {
3. "input": "The sky above the port was the color of television tuned to a dead channel."
4. }
`AI写代码

上面显示它是工作的。如果你遇到 ML 不能启动的问题,那么可能就是你的 Docker Desktop 限制了你的内存大小。你可以尝试调整如下的参数:

写入数据到 Elasticsearch 中
我们参考文章 "如何写入 IMDB 电影数据并针对它运用 AI Agent Builder 对它进行分享"。我们使用它里面的 Python 代码写入数据:.
.env
ini
`
1. ES_URL="http://localhost:9200"
2. ES_API_KEY="RUk1Mnk1OEJnUjJZeWxGLXBza3o6TlRKUVpRaHIyLUd0NGpOMk1IbkdUZw=="
`AI写代码
注意:我们可以从刚开始安装 Elastic Stack 输出的部分找到上面的 ES_API_KEY 的值。当然,我们也可以自己去创建一个新的 API key。
ingest_imdb.py
python
`
1. #!/usr/bin/env python3
2. """Ingest imdb_movies.csv into Elasticsearch, using connection settings from .env."""
4. import csv
5. import os
6. import sys
7. import urllib3
9. from dotenv import load_dotenv
10. from elasticsearch import Elasticsearch
11. from elasticsearch.helpers import bulk, BulkIndexError
12. from elastic_transport import TlsError
14. INDEX_NAME = "imdb"
15. CSV_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "imdb_movies.csv")
16. BULK_CHUNK_SIZE = 50
17. REQUEST_TIMEOUT = 300
19. INDEX_MAPPING = {
20. "mappings": {
21. "properties": {
22. "budget_x": {"type": "double"},
23. "country": {"type": "keyword"},
24. "crew": {"type": "text"},
25. "date_x": {"type": "keyword"},
26. "genre": {"type": "keyword"},
27. "names": {"type": "text"},
28. "orig_lang": {"type": "keyword"},
29. "orig_title": {"type": "text"},
30. "overview": {"type": "text", "copy_to": ["overview_semantic"]},
31. "overview_semantic": {
32. "type": "semantic_text",
33. "inference_id": ".multilingual-e5-small-elasticsearch",
34. "model_settings": {
35. "service": "elasticsearch",
36. "task_type": "text_embedding",
37. "dimensions": 384,
38. "similarity": "cosine",
39. "element_type": "float",
40. },
41. },
42. "revenue": {"type": "double"},
43. "score": {"type": "double"},
44. "status": {"type": "keyword"},
45. },
46. }
47. }
50. def build_client(es_url: str, es_api_key: str) -> Elasticsearch:
51. """Connect to Elasticsearch, working for both trusted and self-signed TLS certs."""
52. try:
53. client = Elasticsearch(
54. es_url, api_key=es_api_key, verify_certs=True, request_timeout=REQUEST_TIMEOUT
55. )
56. client.info()
57. return client
58. except TlsError:
59. print("Certificate could not be verified (self-signed?), retrying with verify_certs=False", file=sys.stderr)
60. urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
61. client = Elasticsearch(
62. es_url, api_key=es_api_key, verify_certs=False, request_timeout=REQUEST_TIMEOUT
63. )
64. client.info()
65. return client
68. def ensure_index(client: Elasticsearch) -> None:
69. if client.indices.exists(index=INDEX_NAME):
70. print(f"Index '{INDEX_NAME}' already exists, skipping creation")
71. return
72. client.indices.create(index=INDEX_NAME, body=INDEX_MAPPING)
73. client.cluster.health(index=INDEX_NAME, wait_for_status="yellow", timeout="30s")
74. print(f"Created index '{INDEX_NAME}'")
77. def to_float(value):
78. value = (value or "").strip()
79. if not value:
80. return None
81. try:
82. return float(value)
83. except ValueError:
84. return None
87. def read_docs(csv_path: str):
88. with open(csv_path, newline="", encoding="utf-8") as f:
89. reader = csv.DictReader(f)
90. for row in reader:
91. doc = {
92. "names": (row.get("names") or "").strip(),
93. "date_x": (row.get("date_x") or "").strip(),
94. "score": to_float(row.get("score")),
95. "genre": [g.strip() for g in (row.get("genre") or "").split(",") if g.strip()],
96. "overview": (row.get("overview") or "").strip(),
97. "crew": (row.get("crew") or "").strip(),
98. "orig_title": (row.get("orig_title") or "").strip(),
99. "status": (row.get("status") or "").strip(),
100. "orig_lang": (row.get("orig_lang") or "").strip(),
101. "budget_x": to_float(row.get("budget_x")),
102. "revenue": to_float(row.get("revenue")),
103. "country": (row.get("country") or "").strip(),
104. }
105. yield {"_index": INDEX_NAME, "_source": doc}
108. def main() -> None:
109. load_dotenv()
110. es_url = os.environ["ES_URL"]
111. es_api_key = os.environ["ES_API_KEY"]
113. client = build_client(es_url, es_api_key)
114. ensure_index(client)
116. try:
117. success, errors = bulk(
118. client,
119. read_docs(CSV_PATH),
120. chunk_size=BULK_CHUNK_SIZE,
121. raise_on_error=False,
122. )
123. except BulkIndexError as e:
124. print(f"Bulk indexing failed: {e}", file=sys.stderr)
125. sys.exit(1)
127. print(f"Indexed {success} documents into '{INDEX_NAME}'")
128. if errors:
129. print(f"{len(errors)} documents failed to index", file=sys.stderr)
130. for err in errors[:5]:
131. print(err, file=sys.stderr)
134. if __name__ == "__main__":
135. main()
`AI写代码收起代码块
运行上面的代码:
go
`python ingest_imdb.py`AI写代码
我们再回到 Kibana 中进行查看:
bash
`GET imdb/_search`AI写代码

我们需要等一下时间直到所有的数据都被写入:

当我们看到 10000 个数据被写入到 Elasticsearch 中时,我们的数据已经被成功地写入了。
初试 AI Agent Builder
我们接下来简单地使用 AI Agent Builder:

打开 Agents,我们可以看到一个默认的 Elastic Agent:Elastic AI Agent。使用它,我们就可以做很多的事。



很显然,我们的 大模型 首先是要思考使用哪个索引,然后再进行推理使用哪个 ES|QL 语句来进行计算。这个显然是需要花费时间和 tokens 来完成的。为了能加速我们的这个过程,我们可以创建自己的 Agents 并在它里面使用自己创建的 tools 来完成这个。这样专有的 tools 完成所需要的动作要快,而且每次的调用都会一致。我们拷贝下上面推理所使用的 ES|QL:
markdown
`
1. FROM imdb
2. | STATS movie_count = COUNT(*) BY genre
3. | SORT movie_count DESC
4. | LIMIT 100
`AI写代码
创建自己的 Agent
创建工具


我们填入所需要的信息,并点击 Save 按钮:

这样就创建了我们一个叫做 get_movies_genres 的 tool。
创建 Agent
创建 Agent 也非常容易:


我们接下来在 Tools 窗口选中我们之前创建的工具:

如上所示,在默认的情况下,它已经为我们选中了 6 个通常我们需要的工具:

点击 Save 按钮:

这样我们的 Mives insights Agent 就创建好了。
使用创建的 Agent
我们接下来使用我们刚才已经创建好的 Agent:


这次我们问了同样的问题。我们可以看到在我们的 agent 调用中,它直接使用了 get_movis_genres 来完成相应的查询,而不是需要 LLM 来重新思考,并推理一个相应的 ES|QL 查询语句来完成的。这样做的好处是:
- **速度快。**不需要 LLM 来推理
- **节省 tokens。**因为每次推理都需要大量的 token 来完成
- 一致性。一旦验证我们的 ES|QL 是正确的,每次调用都会使用一致的 ES|QL 查询,而不是每次让 LLM 来推理完成的。LLM 每次推理的查询有可能会不一致,从而导致查询的结果不一致。
- 提高可信性 。我们可以调整 ES|QL 查询的语句,根据我们人类理解的方式来书写,而不是直接由 LLM 来书写,尽管 LLM 在很多情况下能帮助我们书写非常合理而且有用的推理语句。在有些情景中特别有用,比如我们可以通过 ES|QL 的 join 的方式把不同的索引进行 join,再提供给大模型进行推理。详细例程:你的第一个 Elastic Agent:从单个查询到 AI 驱动的聊天(二)
你如果想定制自己的 Agents,那么你可以参考我之前的系列文章:
好了。今天的文章就写到这里。祝大家学习愉快!
