Product Sync — 2026-05-20
【免费下载链接】cocoindexIncremental engine for long horizon agents 🌟 Star if you like it!项目地址: https://gitcode.com/GitHub_Trending/co/cocoindex
Organizer: Alice Chen Participants: Bob Lee, Alice C.
Notes: The team reviewed the release checklist and prioritized docs polish.
Tasks:
- Bob Lee: Update the migration guide.
- Alice Chen: Confirm launch metrics.
Infra Review — 2026-05-21
Organizer: Robert Lee Participants: Alice Chen, Carol Singh
Notes: The group reviewed graph database hosting options and fallback plans.
Tasks:
- Carol Singh: Prepare the FalkorDB deployment notes.
- Bob Lee: Validate backup restore.
从源码 [main.rs](https://link.gitcode.com/i/e022096f66735eccc72bf5a29091eb90) 的 `extract_meeting` 可以反推每一段的字段规范: - **标题行**:以 `#`/`##` 开头,末尾以 `" — "`(em dash)或 `" - "` 分隔出 `YYYY-MM-DD` 日期;日期用 `NaiveDate::parse_from_str(date.trim(), "%Y-%m-%d")` 解析,解析失败会抛出 `invalid meeting date` 引擎错误。 - **`Organizer:` 前缀行**:记录组织者姓名,缺省会直接报 `meeting section lacks organizer` 错误。 - **`Participants:` 前缀行**:逗号分隔的参与者列表,解析时会 `split(',')` 后逐项 `trim` 并过滤空串。 - **`Notes:` 前缀行**:整段纪要的摘要文本。 - **`Tasks:` 段**:其后的 `- ` 列表项被视为任务;每行按**第一个冒号**切出负责人与任务描述(`task.split_once(':')`),冒号缺失会报 `task lacks assignee` 错误。 这个格式刻意与 Python 版 LLM 抽取产物的语义对齐(组织者/参与者/任务/负责人),只是把"LLM 从自然语言抽取"换成了"确定性结构化解析",从而在本地无需模型即可复现同一张图谱。 ## 三、切分与解析:从纯文本到结构化记录 ### 3.1 按标题切分会议段落 [split_meetings](https://link.gitcode.com/i/010a00d8ef2faa778484d0ece97bf093) 遍历文本的每一行,遇到以 `# ` 或 `## ` 开头的行即开启新段落,把切出的各段去除首尾空白后返回。对 `notes.md` 而言,最终得到"Product Sync — 2026-05-20"与"Infra Review — 2026-05-21"两个独立段落,后续每个段落独立建一个 `Meeting` 节点。 ### 3.2 逐段抽取字段 [extract_meeting](https://link.gitcode.com/i/e022096f66735eccc72bf5a29091eb90) 在段落内部用前缀匹配逐行扫描,产出 `ExtractedMeeting { time, note, organizer, participants, tasks }` 结构。需要留意几个工程细节: - 用 `in_tasks` 状态位标记是否进入 `Tasks:` 区域,避免把 `Tasks:` 之后的普通文本误当任务; - 任务负责人最初被建模为 `Vec<String>`(`assigned_to`),为后续规范化与去重留下空间; - 所有字段缺失都有显式报错,保证"坏数据快失败"而不是写进半成品图。 ### 3.3 人员名称规范化 示例用 [canonical_person](https://link.gitcode.com/i/84ece03e83512a343216328b9ee75125) 做**确定性实体消歧**,它等价于 Python 版中"嵌入 + LLM 实体解析"这一阶段的本地替身: ```rust fn canonical_person(name: &str) -> String { match name.trim() { "Alice C." => "Alice Chen".to_string(), "Bob Lee" => "Robert Lee".to_string(), other => other.to_string(), } }对notes.md的实际效果:
- 第一次会议中
Participants: Bob Lee, Alice C.被归一为Robert Lee与Alice Chen; - 第二次会议中
Organizer: Robert Lee与任务负责人Bob Lee也统一落到Robert Lee。
于是最终Person节点只有三个:Alice Chen、Robert Lee、Carol Singh;同一人在不同会议、不同角色(组织者/参与者/负责人)下都会被折叠成同一个节点——这正是知识图谱"人作为共享实体"的核心诉求。
四、图谱构建:节点、关系与稳定主键
4.1 挂载三类节点表
build_graph 首先用falkordb::mount_table_target声明节点表。以Meeting表为例:
let meeting_table = falkordb::mount_table_target( ctx, graph, "Meeting", schema(&[ ("id", "integer"), ("note_file", "string"), ("time", "string"), ("note", "string"), ], "id")?, ).await?;schema辅助函数(main.rs L56-L63)把列名 + Cypher 类型组装成falkordb::TableSchema。对应到 SDK 侧,TableSchema::new 会校验列名与主键是否合法(validate_ident),并要求主键必须存在于列集合中,从根上规避注入与拼写错误。Person与Task表同理,主键分别是name与description。
4.2 挂载三条关系
关系目标通过 mount_relation_target 挂在两个端点表之上:
let attended_rel = falkordb::mount_relation_target(ctx, graph, "ATTENDED", &person_table, &meeting_table).await?; let decided_rel = falkordb::mount_relation_target(ctx, graph, "DECIDED", &meeting_table, &task_table).await?; let assigned_rel = falkordb::mount_relation_target(ctx, graph, "ASSIGNED_TO", &person_table, &task_table).await?;在 SDK 实现中,relation_target_state 会把关系建模为一张"没有 schema 的表":TableSpec::relation只保留from_table/to_table端点信息,主键固定为id。这正是 Python 版注释中所说的"端点派生主键"机制——每条关系的主键由(from_table, from_id, to_table, to_id)组合自动生成(见 RelationTarget::declare_relation_record 中的relation_key),保证同一对端点之间至多一条边,天然去重。
4.3 稳定主键与数据声明
会议 id 由 IdGenerator::next_id 基于(note_file, time)确定性生成,因此同一个文件里同一日期的会议在多次运行中保持同一 id,这是增量对账能工作的前提。
节点与关系的声明接口统一为declare_record/declare_relation:
meeting_table.declare_record(ctx, meeting_id, &Meeting { id: meeting_id, note_file, time, note })?; task_table.declare_record(ctx, task.description.as_str(), &Task { description })?; decided_rel.declare_relation(ctx, meeting_id, task.description.as_str())?;人员相关的ATTENDED还携带属性载荷AttendedRel { is_organizer },通过 declare_relation_record 写入。这里有一个值得一提的聚合细节:同一会议中,某人在组织者与参与者列表里同时出现时(如第二次会议的Robert Lee既当组织者又是Bob Lee的归一目标),示例用BTreeMap<String, bool>聚合,组织者标志true优先(main.rs L253-L260),最终只落一条ATTENDED边。
4.4 目录扫描
输入文件通过fs::walk(input_dir, &["**/*.md"])收集(main.rs L191),每个.md文件独立读取文本,因而支持把纪要按文件/日期拆分存放,示例默认扫描input/目录。
五、运行方式与图谱查询
5.1 启动 FalkorDB
镜像自带 3000 端口的浏览器 UI:
docker run -d --name cocoindex-falkordb-rust \ -p 6379:6379 -p 3000:3000 \ falkordb/falkordb:latest5.2 构建图谱
export FALKORDB_URI=falkor://localhost:6379 export FALKORDB_GRAPH=meeting_notes cargo run两个环境变量都有默认值兜底(见 main.rs L288-L291):FALKORDB_URI默认falkor://localhost:6379,FALKORDB_GRAPH默认meeting_notes;同时支持把输入目录作为第一个命令行参数传入,默认{CARGO_MANIFEST_DIR}/input。应用状态库(.cocoindex_db)落在项目目录下(main.rs L293-L296),用于记录"上次声明了什么",是增量对账的本地底座。
依赖声明见 Cargo.toml:核心是开启falkordbfeature 的cocoindex本地 SDK,配合tokio、serde、chrono(解析会议日期)与dotenvy(读取.env)。
5.3 用 redis-cli 查询图谱
FalkorDB 本质是 Redis 模块,Cypher 查询直接走GRAPH.QUERY:
# 导出全部边(节点与关系的连接形态) redis-cli GRAPH.QUERY meeting_notes "MATCH p=()-->() RETURN p" # 谁参加了哪次会议 redis-cli GRAPH.QUERY meeting_notes \ "MATCH (p:Person)-[:ATTENDED]->(m:Meeting) RETURN p.name, m.time, m.note_file"【免费下载链接】cocoindexIncremental engine for long horizon agents 🌟 Star if you like it!项目地址: https://gitcode.com/GitHub_Trending/co/cocoindex
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考