赛道 A · 任务 A1
混合检索 应用
在一条 SQL 中融合关键词、语义与过滤条件,为 Apache Doris 文档构建搜索框。
- 60–120 分钟
- 新手友好
- 任意语言 · CLI 或网页
- 欢迎使用 AI 工具
遇到问题? 可在 #dev 提问,频道位于 Apache Doris Slack。维护者和其他参与者会提供帮助。
准备环境
了解构建目标、Doris 如何简化实现,并启动集群。
构建目标
构建同时理解关键词和语义的搜索框。
通过同一张 Doris 表和一条 SQL,实现按 BM25 排序的关键词匹配、基于向量索引的语义近邻,以及结构化过滤,无需 Elasticsearch 或独立向量数据库。
构建一个小型搜索应用,形式可为 CLI、Notebook 或网页,数据来自 Apache Doris 文档的 299 个章节。用户输入英文问题,例如 "how do I store messy JSON?",选择类别,并在三种模式间切换:
- 关键词按 BM25 相关性排序的全文匹配。
- 向量按 Embedding 相似度查找近邻。
- 混合先用关键词预过滤,再按向量排序;进阶目标可加入倒数排名融合(RRF)。
每条结果展示页面、章节、关键词模式的评分或向量/混合模式的距离,以及文档链接。“查看 SQL”开关展示 Doris 实际执行的查询。
草图位于代码面板顶部
Doris 为何适合这个项目
一张表,两种索引。 文本的倒排索引与向量的 HNSW ANN 索引位于同一张表。优化器先通过倒排索引预过滤,再使用 ANN 索引对候选结果排序。
在 SQL 中实现搜索引擎式排序。
score()让普通 SQL 中也能使用 Lucene 同款 BM25 相关性计算,写在SELECT.过滤条件就是 SQL。 类别、页面、
IN列表和范围等条件,可与MATCH_ANY一起写入同一个WHERE子句。
代码面板:表定义中的索引语句
开始之前
docker pull apache/doris:all-in-one-4.1.3docker run -d --name doris -p 9030:9030 -p 8030:8030 -p 8040:8040 apache/doris:all-in-one-4.1.3docker exec -it doris mysql -uroot -h127.0.0.1 -P9030curl -LO https://github.com/morningman/demo-env/releases/download/for-hackathon/doris-hackathon-glasgow-2026.zip && unzip doris-hackathon-glasgow-2026.zip && cd doris-hackathon-glasgow-2026
- 主机
- 127.0.0.1
- 端口
- 9030
- 用户
- root无密码
- 数据库
- hackathon
数据
hackathon.doc_chunks :来自 Doris 4.x 文档的 47 篇关键特性说明,每个章节一行:
CREATE TABLE doc_chunks ( id INT NOT NULL, page VARCHAR(64) NOT NULL, -- doc slug, e.g. 'hybrid-search' page_title VARCHAR(128) NOT NULL, section VARCHAR(256) NOT NULL, category VARCHAR(32) NOT NULL, -- see the 7 values below url VARCHAR(256) NOT NULL, body STRING NOT NULL, embedding ARRAY<FLOAT> NOT NULL, -- 8-dim toy vector, unit length INDEX idx_body (body) USING INVERTED PROPERTIES ("parser" = "english", "support_phrase" = "true"), INDEX idx_category (category) USING INVERTED, INDEX idx_emb (embedding) USING ANN PROPERTIES ( "index_type" = "hnsw", "metric_type" = "l2_distance", "dim" = "8"))DUPLICATE KEY(id)DISTRIBUTED BY HASH(id) BUCKETS 1PROPERTIES ("replication_num" = "1");category 取值 ,用于您的筛选下拉框: search, ai, lakehouse, ingestion, performance, table-design, operations.
关于向量。 为保持离线运行,入门包提供了一个仅基于英文词表的简化 演示嵌入器, a1/toy_embed.py 和 a1/toy_embed.js ,两者输出逐位一致。它通过同义词表将文本映射到 8 个人工定义的概念维度: 搜索、向量与 AI、JSON、湖仓、导入、性能、数据模型、运维 ,再将结果归一化为单位长度。表中向量也由同一函数生成,因此查询向量处于相同空间。这个模型有意保持简单,但已能展示向量检索的作用。例如英文提问 "store messy nested records whose schema keeps changing" ,即使没有出现 JSON 或 VARIANT,也能命中 VARIANT 页面。进阶目标会将其替换为真实模型。
代码面板 → 建表:完整表定义
借助 AI 构建
推荐与 AI 编程助手协作完成任务。 Doris 4.x 的搜索与 AI 能力较新,可能晚于多数模型的训练数据。在让助手编写 SQL 前,请先提供正确上下文:
将下方 上下文提示词 粘贴给编程助手。
让助手阅读本页列出的 参考文档 以及入门包中的
AGENTS.md文件。许多编程助手会自动读取该文件。将 SQL 接入代码前,要求助手先在您的实际集群中运行每条语句。
还可以连接 Doris MCP Server ,参见 任务 A2,让助手自行检查 Schema 和测试查询。
I'm building a hybrid search app on Apache Doris 4.1 for a hackathon.Connection: MySQL protocol, host 127.0.0.1, port 9030, user root, empty password,database `hackathon`. Use a plain MySQL driver and raw SQL (no ORM).Table `doc_chunks` (299 rows): id, page, page_title, section, url,category (search / ai / lakehouse / ingestion / performance / table-design / operations),body (STRING, inverted index, english parser, phrase support),embedding (ARRAY<FLOAT> NOT NULL, 8 dims, unit length, HNSW index, l2_distance).Doris SQL rules — follow them exactly:1. Keyword search uses `body MATCH_ANY 'a b'` (OR), `MATCH_ALL` (AND), `MATCH_PHRASE`. Never LIKE. The parser keeps stopwords, so pass keywords(text) from toy_embed, not the raw question. Bind the user's text as a driver parameter.2. BM25 relevance is `score()`. It only works with a MATCH_* predicate in WHERE and `ORDER BY score() DESC LIMIT n`, selected plain (`score() AS relevance`): no ROUND(), no window function, no JOIN in that SELECT. Round, rank or join it in an outer query.3. Vector search: `ORDER BY l2_distance_approximate(embedding, <vector>) ASC LIMIT n`. Pass the vector as a string and cast it: CAST(%s AS ARRAY<FLOAT>) with '[f1, ..., f8]'. Never pass a list/array as a driver parameter.4. Hybrid = MATCH_* pre-filter + vector ORDER BY; show the distance, do not select score() in that query. For fusion, rank two lists in subqueries and combine with RRF (k = 60); a1/rrf.sql in the starter kit shows the pattern.5. Query vectors come from toy_embed(text) in a1/toy_embed.py (toyEmbed in a1/toy_embed.js). Do not invent embeddings.Build: a CLI (or small web page) with keyword / vector / hybrid modes, a category filter,and a "show SQL" option. Run every SQL statement against the live cluster first.AI 编程助手容易踩到的 Doris 问题
| Trap | What to do instead |
|---|---|
使用 LIKE '%term%' 进行文本检索 | col MATCH_ANY 'a b' (OR)、 MATCH_ALL (AND)、 MATCH_PHRASE 'a b' ,这些运算符会使用倒排索引 |
在任意查询中调用 score() 函数 | score() 要求在查询中使用 MATCH_* 谓词,放在 WHERE 中,并提供 ORDER BY score() DESC LIMIT n,否则 Doris 会拒绝查询 |
在同一查询中对 score() 进行取整、关联或排名 | 在单表查询中直接保留 score() AS relevance 。如需取整、关联或使用 ROW_NUMBER() ,请在外层查询中操作,并使用子查询,而非 WITH CTE |
在同一 ORDER BY中按 BM25 和向量距离排序,或在向量排序查询中选择 score() 的结果 | 不支持。混合检索可采用 MATCH_* 预过滤,再按向量距离 ORDER BY排序并展示距离;也可融合两个排名列表(RRF) |
| 将查询向量直接作为驱动参数传入 | 在 pymysql 中,列表会变成 (…) ,在 mysql2 中则会变成 8 个独立参数。请传入字符串 '[0.1, …]' 并使用 CAST(%s AS ARRAY<FLOAT>),或直接内联向量字面量 |
| 寻找余弦 ANN 度量 | ANN 度量包括 l2_distance 和 inner_product。将向量归一化为单位长度后,L2 距离升序与余弦相似度降序的排名相同 |
开始构建
5 个里程碑, M0 → M4。代码面板会跟随您阅读的步骤。
导入数据
在入门包目录中执行:
M0 · 导入数据bashdocker exec -i doris mysql -uroot -h127.0.0.1 -P9030 < seed/a1_doc_chunks.sql检查点导入结束后会输出每个类别的数量: 7 个类别、299 个章节 。
使用 BM25 进行关键词检索
打开 SQL Shell(
./doris.sh sql,或使用前文的docker exec命令),然后执行:M1 · 使用 BM25 进行关键词检索sqlUSE hackathon;SELECT page_title, section, score() AS relevanceFROM doc_chunksWHERE body MATCH_ANY 'vector index recall'ORDER BY relevance DESCLIMIT 10;再尝试
MATCH_ALL,要求每个词都出现;以及MATCH_PHRASE 'inverted index',要求词语相邻且顺序一致。随后连接输入框 → SQL → 结果列表。用户输入必须作为驱动参数传入(MATCH_ANY %s),不要直接拼接到 SQL 中,否则第一个单引号就可能破坏查询。a1/starter.py和a1/starter.js已按这种方式实现。如果输入完整问题,English 分词器会保留停用词,因此
'how do I …'可能匹配约三分之一的数据。请改用演示嵌入器中的keywords(text),它会去掉停用词,以及几乎每节都会提到的 apache 和 doris。检查点对于
vector index recall,排名靠前的结果来自 Vector Index 和 Hybrid Search 页面,首条为 How does the Apache Doris vector index work?添加过滤条件
M2 · 添加过滤条件sqlSELECT page_title, section, category, score() AS relevanceFROM doc_chunksWHERE body MATCH_ANY 'vector index recall' AND category = 'search'ORDER BY relevance DESCLIMIT 10;将类别暴露为下拉框或 CLI 参数,7 个取值见“数据”部分。额外条件直接使用 SQL:
category IN (…),page = ….检查点Vector Index 章节会消失,因为该页面的分类为
ai,Hybrid Search 则排到首位。向量检索
通过代码或命令行调用演示嵌入器,将问题转换为向量:
M3 · 为问题生成向量pythonfrom toy_embed import toy_embed, to_sql_arrayvec = to_sql_array(toy_embed("store messy nested records whose schema keeps changing"))# '[0.005740, 0.005740, 0.999885, ...]' · CLI: python a1/toy_embed.py "..."M3 · 向量检索sqlSELECT page_title, section, l2_distance_approximate(embedding, [0.005740, 0.005740, 0.999885, 0.005740, 0.005740, 0.005740, 0.005740, 0.005740]) AS distFROM doc_chunksORDER BY dist ASCLIMIT 10;在代码中保持 SQL 不变,将向量字符串作为参数传入:
l2_distance_approximate(embedding, CAST(%s AS ARRAY<FLOAT>))。不能直接将 Python List 或 JavaScript Array 作为该参数。检查点前六条结果均为 VARIANT 章节,尽管问题中没有 JSON 或 VARIANT;相同文本的关键词模式还会混入 Metadata Cache 和 Binlog 章节。
混合检索
问题: "stream kafka changes into doris exactly once"。向量由演示嵌入器生成;关键词过滤要求包含 kafka:
M4 · 混合检索sqlSELECT page_title, section, l2_distance_approximate(embedding, [0.007020, 0.007020, 0.007020, 0.007020, 0.999827, 0.007020, 0.007020, 0.007020]) AS distFROM doc_chunksWHERE body MATCH_ANY 'kafka' -- keyword pre-filter (inverted index) AND category IN ('ingestion', 'lakehouse') -- structured filterORDER BY dist ASC -- vector ranking (ANN)LIMIT 10;- L4
WHERE body MATCH_ANY 'kafka'关键词预过滤(倒排索引) - L5
AND category IN ('ingestion', 'lakehouse')结构化过滤 - L6
ORDER BY dist ASC向量排序(ANN)
在应用中,同一个输入同时用于两部分:
keywords(text)用于MATCH_ANY,toy_embed(text)用于生成向量。MATCH_ANY用于多个词时过滤较宽松;可提供“必须包含”输入框,或使用MATCH_ALL来严格匹配。这里不要选择score(),应展示距离。提供关键词/向量/混合模式切换,并添加 查看 SQL 开关。比较同一个问题的三组结果,观察差异并解释原因。
检查点每条结果都提到 Kafka,首条为 How does the Apache Doris Kafka and CDC integration work? 相同问题仅使用向量模式时,首条结果为 Pipeline Execution Engine › Overview 。
- L4
完成并提交
完成清单,展示成果,领取徽章。
完成标准
您的检查清单提交并领取徽章
参与 → 提交 → 领取徽章。
请将成果放到以 您的 GitHub ID命名的目录中:包括代码和 README,说明项目功能、运行方式、使用的 Doris 能力,并附一张截图或 GIF。
提交 Pull Request ,将成果添加到 morningman/demo-env ,目录为
doris-hackathon-glasgow-2026/<your-github-id>/。可以先 Fork 后推送,或使用 GitHub 的 Add file → Upload files(添加文件 → 上传文件)。无需填写其他内容。分享成果 到
#dev( Apache Doris Slack):附上 Pull Request 或仓库链接,以及截图或简短演示。问题交流、成果提交和徽章领取都在该频道处理。
每位参与任务的开发者都可获得 Apache Doris 贡献者徽章,无需先有 PR 合入 Doris。任务没有截止日期,可按自己的节奏完成。可以组队,也可独立参与;请将每位成员的 GitHub ID 写入 README.
进阶目标
倒数排名融合。 将 BM25 列表与向量列表通过
1/(60 + rank)在一条语句中融合:a1/rrf.sql位于入门包中,使用ROW_NUMBER()为每个列表排名;排名计算在普通score()查询的外层进行,再使用FULL OUTER JOIN合并。 RRF 文档 使用四行数据演示同一种模式。通过
EMBED()生成真实 Embedding,需自备 API Key:创建 AI Resource,然后创建doc_chunks_v2,向量维度采用该模型的dim,使用INSERT INTO doc_chunks_v2 SELECT …, EMBED(body) FROM doc_chunks填充向量。该向量列是NOT NULL,因此应插入新行而非更新,同时在 SQL 中为查询生成向量。参阅 Embedding 文档.边输入边搜索 与
MATCH_PHRASE_PREFIX.高亮匹配词 。Doris 返回评分,不返回高亮片段,需要在应用中自行渲染。
延迟标记:显示每种模式耗时多少毫秒。
故障排查与参考资料
| Symptom | Fix |
|---|---|
docker: command not found 出现在 macOS 上 | 启动 Docker Desktop;若已运行,请为 CLI 创建链接: sudo ln -s /Applications/Docker.app/Contents/Resources/bin/docker /usr/local/bin/docker |
error getting credentials 出现在拉取镜像时 | 请移除 credsStore 字段,所在文件为 ~/.docker/config.json (仅适用于本地开发) |
port is already allocated | 9030、8030 或 8040 已被其他程序占用,常见原因是另一个 Doris 实例。先查看运行中的容器(docker ps),再停止冲突的服务;也可映射其他主机端口,例如 -p 19030:9030,然后连接 19030 |
The container name "/doris" is already in use | 此前已启动过该容器,可执行: docker start doris。如需从头开始,请执行 docker rm -f doris ,此操作会删除容器数据 |
容器退出,或始终未变为 (healthy) | 请查看 docker logs doris。通常是内存不足,请在 Docker Desktop 的 Settings → Resources 中分配至少 6 GB。Apple Silicon 设备不要添加 --platform linux/amd64 |
permission denied: ./doris.sh | 解压工具可能丢失了可执行权限,请执行: chmod +x doris.sh,或运行 bash doris.sh start |
Current database is not set | 请先执行 USE hackathon; ,或在连接时使用 -Dhackathon |
score() function requires WHERE clause with MATCH function, ORDER BY and LIMIT | 请添加 MATCH_* 谓词、 ORDER BY score() DESC 以及 LIMIT,并保持 score() 不被其他表达式包裹,参见前文说明 |
Ann topn query vector cannot be empty | 向量字面量为空或仍是占位符,请使用以下命令的输出: python a1/toy_embed.py "…" |
Can not found function 'l2_distance_approximate' which has 9 arity | 驱动将 Array 参数展开成了 8 个值,请将向量作为字符串传入并使用 CAST(? AS ARRAY<FLOAT>) |
| 超过 10 分钟仍未解决 | 可在 #dev 频道提问,位于 Apache Doris Slack |
参考资料
Apache Doris 黑客松
