在今天的文章中我们以一个 IMDB 电影数据为例来展示如何把数据写入到 Elasticsearch 中,并对它进行 AI 分析。
更多阅读:如何通过 Claude Code 来写入 CSV 数据到 Elasticsearch
准备工作
安装 Elasticsearch 及 Kibana
如果你还没有安装好自己的 Elasticsearch 及 Kibana,你可以启动云服务,比如阿里云,腾讯云,或者 Elastic Cloud。你可以参观看如下的视频:
建议你观看上面的 “为 AI 安装自己的 Elasticsearch 集群” 来进行安装。
为了能够使我们使用向量搜索对 overview 字段进行搜索,我们使用 E5 多语言模型。我们还需要进行如下的安装:
我们需要等后一点时间来等待下载完成。在安装过程中,如果由于任何原因导致安装不成功,你也可以直接使用如下的命令来删除它,然后再次下载:
`DELETE /_ml/trained_models/.multilingual-e5-small-elasticsearch?force=true`AI写代码
一旦下载完毕后,我们可以对它进行测试:
`GET _inference/_all`AI写代码
我们可以看到一个叫做 .multilingual-e5-small-elasticsearch 的 inference_id。我们可以做如下的一个简单的测试:
`
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写代码
很显然,这个推理端点工作正常。它可以帮我们把文字转换为向量。
下载 IMDB 数据
这个数据集可以在地址进行下载:
等下载完毕后,我们可以把它放到我们的目录中:
`
1. $ pwd
2. /Users/liuxg/python/imdb
3. $ ls
4. imdb_movies.csv.zip
`AI写代码
我们接下来把它进行解压缩:
`
1. $ unzip imdb_movies.csv.zip
2. Archive: imdb_movies.csv.zip
3. inflating: imdb_movies.csv
4. $ ls
5. imdb_movies.csv imdb_movies.csv.zip
`AI写代码
我们可以看到上面的被解压缩的文件 imdb_movies.csv。文件的结构如下:
很显然,我们可以看到一个叫做 names, date_x, score, genre, overview 等字段。
写入数据到 Elasticsearch
我们有几种方法来写入数据。我们也可以仿照文章 “Elasticsearch:Retrievers 介绍 - Python Jupyter notebook” 来写入我们的数据。
使用 Kibana 上传写入
我们打开 Kibana:
经过调整后的 mapping 是这样的:
`
1. {
2. "properties": {
3. "budget_x": {
4. "type": "double"
5. },
6. "country": {
7. "type": "keyword"
8. },
9. "crew": {
10. "type": "text"
11. },
12. "date_x": {
13. "type": "date",
14. "format": "MM/dd/yyyy||MM/dd/yyyy[ ]"
15. },
16. "genre": {
17. "type": "keyword"
18. },
19. "names": {
20. "type": "text"
21. },
22. "orig_lang": {
23. "type": "keyword"
24. },
25. "orig_title": {
26. "type": "text"
27. },
28. "overview": {
29. "type": "text",
30. "copy_to": "overview_semantic"
31. },
32. "overview_semantic": {
33. "type": "semantic_text",
34. "inference_id": ".multilingual-e5-small-elasticsearch"
35. },
36. "revenue": {
37. "type": "double"
38. },
39. "score": {
40. "type": "double"
41. },
42. "status": {
43. "type": "keyword"
44. }
45. }
46. }
`AI写代码
完美。我们已经成功地写入了所有的数据。我们可以直接点击 Agent Builder 对数据进行查询。
当然,在进行之前,我们也可以进入到 Kibana 查看数据:
`GET imdb/_mapping`AI写代码
查看我们的 mapping:
`
1. {
2. "imdb": {
3. "mappings": {
4. "_meta": {
5. "created_by": "file-data-visualizer"
6. },
7. "properties": {
8. "budget_x": {
9. "type": "double"
10. },
11. "country": {
12. "type": "keyword"
13. },
14. "crew": {
15. "type": "text"
16. },
17. "date_x": {
18. "type": "keyword"
19. },
20. "genre": {
21. "type": "keyword"
22. },
23. "names": {
24. "type": "text"
25. },
26. "orig_lang": {
27. "type": "keyword"
28. },
29. "orig_title": {
30. "type": "text"
31. },
32. "overview": {
33. "type": "text",
34. "copy_to": [
35. "overview_semantic"
36. ]
37. },
38. "overview_semantic": {
39. "type": "semantic_text",
40. "inference_id": ".multilingual-e5-small-elasticsearch",
41. "model_settings": {
42. "service": "elasticsearch",
43. "task_type": "text_embedding",
44. "dimensions": 384,
45. "similarity": "cosine",
46. "element_type": "float"
47. }
48. },
49. "revenue": {
50. "type": "double"
51. },
52. "score": {
53. "type": "double"
54. },
55. "status": {
56. "type": "keyword"
57. }
58. }
59. }
60. }
61. }
`AI写代码收起代码块
也可以针对数据进行查询:
这样我们的数据就已经写入完成了。
使用 Python 代码写入数据
针对有些开发者不是很喜欢使用 Upload file 上面的方法,你可以使用 Python 代码来实现上面的写入。针对我们的数据,我们可以有如下的代码来实现。在运行下面的代码之前,我们先运行下面的命令来删除已经写入的数据:
`DELETE imdb`AI写代码
我们先在当前目录创建一个叫做 .env 的文件:
`
1. $ pwd
2. /Users/liuxg/python/imdb
3. $ ls -al
4. total 19328
5. drwxr-xr-x 6 liuxg staff 192 Jul 9 11:38 .
6. drwxr-xr-x@ 62 liuxg staff 1984 Jul 9 09:48 ..
7. -rw-r--r-- 1 liuxg staff 105 Jul 9 11:34 .env
8. -rw-r--r--@ 1 liuxg staff 6723214 Apr 28 2023 imdb_movies.csv
9. -rw-r--r--@ 1 liuxg staff 2980385 Jul 9 09:51 imdb_movies.csv.zip
10. -rw-r--r-- 1 liuxg staff 4356 Jul 9 11:38 ingest_imdb.py
`AI写代码
`
1. ES_URL="https://localhost:9200"
2. ES_API_KEY="ckFQc1JKOEJHN2xFUjlaTlNKbUw6alU5MDgxOUlmM0hjLTE0WXdyQVNJUQ=="
`AI写代码
你需要根据自己的配置修改上面的 ES_URL 地址来指向自己的 Elasticsearch 端点。如果大家还不是很清楚如何得到 ES_API_KEY 的话,建议大家观看我之前的视频 “ Elastic 黑客松比赛说明”。我简单展示如下:
拷贝上面的 API key 即可:ckFQc1JKOEJHN2xFUjlaTlNKbUw6alU5MDgxOUlmM0hjLTE0WXdyQVNJUQ==
这样,我们的 .env 配置就完成了。
ingest_imdb.py
`
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写代码收起代码块
运行上面的代码后,我们可以看到如下的结果:
`GET imdb/_mapping`AI写代码
使用 Agent Builder 对数据进行探索
我们首先使用默认的 Elastic AI Agent 来对数据进行探索:
我们选择自己的大模型。如果大家还不知道如何创建连接器,请观看我的视频: 为 AI 安装自己的 Elasticsearch 集群
我们可以看出来大模型可以为我们生成想要的统计结果。我们再来问一下:
`哪一年的生产的电影最多?`AI写代码
我们可以看到所使用的 ES|QL 查询。
`电影的类别有哪些?那个类别的电影最多?`AI写代码
是不是使用默认的 Elastic AI Agent 就可以做很多的事了?
你如果想定制自己的 Agents,那么你可以参考我之前的系列文章:
最后祝大家学习愉快!