课程Apache Doris 黑客松自主安排进度 · 随时开始 · 无需报名

赛道 A · 任务 A1

混合检索 应用

在一条 SQL 中融合关键词、语义与过滤条件,为 Apache Doris 文档构建搜索框。

  • 60–120 分钟
  • 新手友好
  • 任意语言 · CLI 或网页
  • 欢迎使用 AI 工具

遇到问题? 可在 #dev 提问,频道位于 Apache Doris Slack。维护者和其他参与者会提供帮助。

1

准备环境

了解构建目标、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.3
  • docker run -d --name doris -p 9030:9030 -p 8030:8030 -p 8040:8040 apache/doris:all-in-one-4.1.3
  • docker exec -it doris mysql -uroot -h127.0.0.1 -P9030
  • curl -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 篇关键特性说明,每个章节一行:

数据表hackathon.doc_chunks · sql
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 前,请先提供正确上下文:

  1. 将下方 上下文提示词 粘贴给编程助手。

  2. 让助手阅读本页列出的 参考文档 以及入门包中的 AGENTS.md 文件。许多编程助手会自动读取该文件。

  3. 将 SQL 接入代码前,要求助手先在您的实际集群中运行每条语句。

  4. 还可以连接 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 问题

TrapWhat 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 距离升序与余弦相似度降序的排名相同
2

开始构建

5 个里程碑, M0 → M4。代码面板会跟随您阅读的步骤。

  1. 里程碑 05 分钟

    导入数据

    在入门包目录中执行:

    M0 · 导入数据bash
    docker exec -i doris mysql -uroot -h127.0.0.1 -P9030 < seed/a1_doc_chunks.sql
    检查点

    导入结束后会输出每个类别的数量: 7 个类别、299 个章节 。

  2. 里程碑 115 分钟

    使用 BM25 进行关键词检索

    打开 SQL Shell(./doris.sh sql,或使用前文的 docker exec 命令),然后执行:

    M1 · 使用 BM25 进行关键词检索sql
    USE 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?

  3. 里程碑 210 分钟

    添加过滤条件

    M2 · 添加过滤条件sql
    SELECT 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 则排到首位。

  4. 里程碑 315 分钟

    向量检索

    通过代码或命令行调用演示嵌入器,将问题转换为向量:

    M3 · 为问题生成向量python
    from 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 · 向量检索sql
    SELECT 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 章节。

  5. 里程碑 420 分钟

    混合检索

    问题: "stream kafka changes into doris exactly once"。向量由演示嵌入器生成;关键词过滤要求包含 kafka:

    M4 · 混合检索sql
    SELECT 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;
    • L4WHERE body MATCH_ANY 'kafka'关键词预过滤(倒排索引)
    • L5AND category IN ('ingestion', 'lakehouse')结构化过滤
    • L6ORDER 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 。

3

完成并提交

完成清单,展示成果,领取徽章。

完成标准

您的检查清单

提交并领取徽章

参与 → 提交 → 领取徽章。

  1. 请将成果放到以 您的 GitHub ID命名的目录中:包括代码和 README,说明项目功能、运行方式、使用的 Doris 能力,并附一张截图或 GIF。

  2. 提交 Pull Request ,将成果添加到 morningman/demo-env ,目录为 doris-hackathon-glasgow-2026/<your-github-id>/。可以先 Fork 后推送,或使用 GitHub 的 Add file → Upload files(添加文件 → 上传文件)。无需填写其他内容。

  3. 分享成果 到 #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 返回评分,不返回高亮片段,需要在应用中自行渲染。

  • 延迟标记:显示每种模式耗时多少毫秒。

故障排查与参考资料

SymptomFix
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 allocated9030、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

参考资料

混合检索BM25全文检索向量索引倒数排名融合向量嵌入

Apache Doris 黑客松

全部任务

返回课程
  1. A160–120 分钟混合检索应用当前步骤
  2. A245–90 分钟通过 MCP 询问 Doris查看说明
  3. A360–120 分钟日志检索工具查看说明
  4. A460–120 分钟Agent 链路分析工具查看说明
  5. B30–60 分钟赛道 B · 从文档到演示查看说明