# jev-graph **Repository Path**: mutongli/jev-graph ## Basic Information - **Project Name**: jev-graph - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-12 - **Last Updated**: 2026-10-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # jev-graph 基于业务问题进行语义图谱自动探索的工具:把用户问题语义匹配为图中的起点节点,每次探索一度邻居,把探索上下文(含用户目标)交给 JEV 模型(TypeSafe System One,非生成式)决策——当前子图是否已足以回答问题、扩展哪些节点、限制哪些关系;模型认为子图已满足目标或达到预算/深度等限制时,输出最终子图。 - 设计文档:[docs/design/semantic-graph-auto-exploration-design.md](docs/design/semantic-graph-auto-exploration-design.md) - 任务分解:[docs/tasks/00-任务总览.md](docs/tasks/00-任务总览.md) ## 工作原理 ``` 业务问题 ──► 起点解析(名称匹配 → 语义检索回退)──► 一度加载(Expander)──► 上下文构建(≤ 64 KiB,含用户目标) │ ┌───────────────────────────────────────────────┘ ▼ 问题集构造(should_continue / expand: / allow:) │ ▼ JEV 模型打分(noul ∈ [0,1])──► 阈值组装(assemble)──► Decision │ │ │ continue ──► 按决策扩展一度,前沿更新 ──► 下一轮 │ stop ──────► 收口,输出最终子图 ▼ 终止条件(预算 / 前沿清空 / 深度上限 / 停滞 / 模型 stop=子图已满足目标 / 人工中断) ``` ### 一个例子 以 [data/social_graph](data/social_graph)(人际交往测试图:10 人 + 3 组织 + 2 活动 + 1 地点)为例,输入业务问题「哪些人参加了马拉松?」,探针模式固定 noul=0.85(`jev-graph explore 哪些人 参加了 马拉松 --graph data/social_graph --probe 0.85`): | 步骤 | 发生了什么 | | --- | --- | | ① 起点解析 | 图里没有叫「哪些人参加了马拉松」的节点——名称索引未命中后回退**语义检索**:中文 2-gram 词面相似度把问题对上别名「春季马拉松」(Jaccard≈0.2),唯一命中 e01「2026城市春季马拉松」作起点 | | ② 一度加载 | 取 5 个一度邻居入前沿:林晓雨 / 王大力 / 李静 / 孙浩(ATTENDED,正是问题的答案)+ 杭州(HELD_IN) | | ③ 上下文 + 问题集 | 子图压缩成约 1.4 KiB 上下文,`should_continue` 指令内联用户目标原文,再构造 `expand:王大力?`、`allow:ATTENDED?` … | | ④ 模型打分 | `should_continue=0.85 ≥ 0.5` → **continue**;5 个候选 0.85 ≥ expand 阈值 → 全部扩展;模型只输出分数,不生成文本 | | ⑤ 扩展 | 第 1 轮 +8 节点 +16 边;第 2 轮 +2 节点 +12 边后触及深度上限护栏 | | ⑥ 终止收口 | `max_depth_reached`,输出最终子图 **16 节点 / 33 边**——4 位参赛者全部在图中,足以回答「哪些人参加了马拉松」 | 关键在①⑥:起点不靠记住节点名称,而是业务问题语义匹配;终止也不只看预算——`should_continue` 的判定依据是"当前已收集子图是否足以回答用户目标"(真实模型在子图覆盖答案后会打低分停止;探针恒 0.85 时由深度护栏收口)。桥接结构依然生效:跑团成员「林晓雨」同时就职于星辰科技,探索经由她自然蔓延进同事圈。每轮模型决策独立可观测(`--jsonl` 落盘全事件),分数到动作的组装规则见下。 核心语义(详见设计 §7.4): - **继续**:`should_continue` 指令内联用户目标,模型依据"已收集子图是否足以回答目标"打分;≥ `continue_threshold` 则继续,已满足目标或无实质增益则停;继续但无有效扩展时兜底转 stop(`empty_expand`)。 - **扩展**:候选分数 ≥ `expand_threshold` 且落在模糊带 `[ambiguous_band]` 之外;带内分数保守不扩展。 - **关系过滤**:`allow:` ≥ `allow_threshold` 进白名单(restrict);≤ `deny_threshold` 进黑名单(exclude);白名单优先。 - **规则降级**:Jev 不可用时按关系语义类型(`SEMANTIC_RELATIONS` / `NOISY_RELATIONS`)规则回退,`degraded_decisions` 计数。 ## 核心特性 - **可插拔图存储**:`InMemoryGraphStore` / `MockGraphStore`(JSON 夹具)、`LocalGraphStore`(JSONL,可选 DuckDB 引擎);`GraphStore` 协议可扩展 Neo4j / RDF。 - **确定性主循环**:预算、深度、停滞、前沿清空等终止条件全部显式可测(§9)。 - **事件驱动**:`SyncEventBus` / `AsyncEventBus`,全类型事件(run.started / round.finished / decision.received / subgraph.updated / run.finished …),订阅者异常隔离、背压丢弃策略(§16.4/§16.5)。 - **可观测性**:控制台渲染、JSONL 全事件落盘、轨迹构建、指标统计(轮次、动作分布、stop 原因分布、调用延迟、noul 分布,§15)、人工介入门(`HumanGate`)、SSE 发布。 - **模型评估**(§3.2):标注样本 → 混淆矩阵 / 准确率指标 / 阈值灵敏度曲线 / 模糊带标定 / 参数建议。 - **可复现重放**(§16.7):事件流 + 配置重建各轮状态,逐轮比对验证重放一致性。 - **多格式导出**(§11):JSON / GraphML / Mermaid / Cypher,原子写盘。 - **命令行界面**:`jev-graph` 脚本入口,`explore` / `repl` / `config` 三个子命令,交互式会话、人工介入终端确认、探针模式(`--probe`)无模型演示。 ## 安装 要求 Python ≥ 3.11(开发环境为 3.12)。 ```powershell git clone && cd jev-graph python -m venv .venv .venv\Scripts\Activate.ps1 # Linux/macOS: source .venv/bin/activate pip install -e ".[dev,engines]" # dev=pytest;engines=LocalGraphStore 的 DuckDB 可选引擎 ``` ## 配置 复制 [config.example.yaml](config.example.yaml) 为 `config.yaml` 按需修改。要点: - 取值语法支持 `env:VAR`(必填环境变量)与 `env?:VAR`(可选,缺失取 null)。 - `jev.*`:Jev 端点(`base_url` / `path` / `model`)、超时重试,以及全部阈值(`continue_threshold` / `expand_threshold` / `allow_threshold` / `deny_threshold` / `ambiguous_band`)。 - `store.adapter`:`memory` | `local` | `neo4j`(占位)。 - `events.subscribers`:启用的订阅者(`console` / `jsonl` / `metrics`)。 代码中亦可直接构造:`AppConfig(jev=JevConfig(base_url=..., path="/v1/systemone", model=...))`。 ## 命令行界面 `pip install -e .` 后即可使用 `jev-graph` 命令: ```powershell # 单次探索:业务问题经语义检索匹配起点(探针模式无需 Jev 模型端点即可演示) jev-graph explore 哪些人 参加了 马拉松 --graph data/social_graph --probe 0.85 # 节点名称(或别名)直接查询同样支持——名称匹配优先,语义检索兜底 jev-graph explore 亚当斯 --graph tests/fixtures/authors_graph.json --probe 0.85 # 交互模式:多轮查询 + 元命令(:help / :config / :last / :metrics / :export [formats] / :quit) jev-graph repl --graph data/graph_jsonl # 查看生效配置(密钥遮蔽) jev-graph config ``` 通用参数(`explore` / `repl` 共享): | 参数 | 说明 | | --- | --- | | `--config PATH` | 配置文件路径(须置于子命令前) | | `--graph PATH` | 图数据:`.json` 夹具或 JSONL 目录 | | `--probe FLOAT` | 用固定 noul 探针替代真实 Jev(演示 / 调试) | | `--max-rounds` / `--max-nodes` | 覆盖探索预算 | | `--jsonl PATH` | 全事件 JSONL 落盘 | | `--jev-log PATH` | Jev 调用日志:可重放的 `/v1/systemone` 请求报文 + 原始响应(JSONL 追加,默认 `logs/jev_calls.log`,随 cwd) | | `--no-jev-log` | 关闭 Jev 调用日志落盘 | | `--formats F1,F2 --out DIR` | 导出子图(json / graphml / mermaid / cypher) | | `--quiet` | 关闭控制台渲染 | | `--yes` | 人工介入暂停时自动确认继续 | 退出码:0 成功;1 错误(种子未解析 / Jev 故障 / 配置无效);2 用法错误;130 中断。 ## 快速开始 ```python from jev_graph import GraphExporter, JevClient, JevDecider, explore, load_config from jev_graph.store import MockGraphStore cfg = load_config("config.yaml") # 缺省时自动回退内置默认值 # 图存储:JSON 夹具 / 内存图 / 本地 JSONL graph = MockGraphStore.from_fixture("tests/fixtures/authors_graph.json") # from jev_graph.store import LocalGraphStore # graph = LocalGraphStore("data/graph", format="jsonl") result = explore( "亚当斯", # 用户输入(解析为起点 seed) graph=graph, decider=JevDecider(JevClient(cfg.jev), cfg.jev), config=cfg, ) print(result.stop_reason, result.rounds) # 如 frontier_empty / 2 for node in result.state.nodes.values(): print(f" {node.id} depth={node.depth} score={node.score}") # 导出最终子图 GraphExporter(cfg.export).export(result, formats=["json", "mermaid"]) ``` ### 自定义决策器 `explore` 只依赖最小 `Decider` 协议(`decide(ctx) -> Decision`),可整体替换模型决策逻辑(测试桩 `ProbeJevClient` 即按此思路构造)。 ### 事件与指标 ```python from jev_graph import JsonlRecorder, MetricsCollector, SyncEventBus bus = SyncEventBus() metrics = MetricsCollector() with JsonlRecorder("events.jsonl") as recorder: bus.subscribe(recorder) bus.subscribe(metrics) result = explore(..., bus=bus) metrics.snapshot() # {"rounds": ..., "stop_reasons": {...}, "latency_ms_avg": ..., "noul_distribution": {...}, ...} ``` ### 模型评估与重放 ```python from jev_graph import ModelEvaluator, load_samples, replay samples = load_samples("samples.jsonl") report = ModelEvaluator().suggest(samples) # 各维最优阈值 + 模糊带建议 # 事件流 → 逐轮重建状态并与原运行比对 res = replay("events.jsonl", graph=graph, config=cfg) assert res.reproduced ``` ## 测试 ```powershell .venv\Scripts\python.exe -m pytest tests/ -q # 全量(当前 325 passed,含 CLI 22 例) ``` - **回归快照**:`tests/goldens/` 下逐字比对"同输入同输出";预期变更时用 `JEV_UPDATE_SNAPSHOTS=1` 重新生成并人工审查。 - **真实 Jev 集成测试**:`test_jev_client_integration.py` / `test_integration_e2e.py` 需要本地 Ollama(默认 `http://localhost:11434`,模型 `nimble`);端点不可达时自动跳过。 - 测试覆盖矩阵详见 [docs/tasks/13-测试体系与模型评估.md](docs/tasks/13-测试体系与模型评估.md)。 ## 模块导览 | 模块 | 职责 | | --- | --- | | `schema.py` / `state.py` | 领域模型:Node / Edge / Decision / ExplorationState 等(T02) | | `config.py` | YAML 配置加载、env 注入、默认值(T01) | | `store/` | `GraphStore` 协议 + memory / local 适配器(T03/T04) | | `context.py` | 上下文构建与压缩(full / byte_budget / summary,64 KiB 约束,T05) | | `jev_client.py` | Jev HTTP 客户端(超时 / 重试 / 413 / `ProbeJevClient` 测试桩,T06) | | `jev_log.py` | `JevCallLogger`:包装任意 `JevClient`,逐次落盘可直接重放的 `/v1/systemone` 请求报文(url/headers/payload)与原始响应体(JSONL,错误先记后抛) | | `questions.py` | 问题集构造与答案→Decision 阈值组装(T07) | | `expander.py` | 一度扩展:邻居收集 / 关系过滤 / 前沿排序(T08) | | `seed_resolver.py` | 业务问题 → 起点节点的语义检索(词面 Jaccard 兜底,`SeedResolver` 协议可插拔) | | `explorer.py` | 主循环与全部终止条件、`JevDecider`、`explore()` 入口(T09) | | `events.py` | 事件总线与全类型事件(T10) | | `exporter.py` | 子图导出:JSON / GraphML / Mermaid / Cypher(T11) | | `fallback.py` | Jev 不可用时的规则降级(T12) | | `subscribers/` | console / jsonl / trace / metrics / human_gate / sse(T10/T13) | | `eval.py` | 模型评估:混淆矩阵、阈值扫描、模糊带标定、参数建议(T13) | | `replay.py` | 事件流解析与可复现重放(T13) | | `cli.py` | 命令行界面:explore / repl / config 子命令、REPL 会话、人工介入终端确认 | ## 项目状态 - 版本 0.1.0;T01–T13 全部完成,CLI 交互入口与业务问题语义起点解析就绪,全量测试通过。 - `store/neo4j.py` 为占位实现,RDF 适配器未开工。 - Jev 对接假设 TypeSafe System One(`POST /v1/systemone`)协议;更换端点只需调整 `jev.*` 配置。