demo架构

AI相关组件整体架构 #

flowchart TB
    U["前端 / 调用方"]
    M["master<br/>统一入口 / 鉴权 / 反向代理"]

    U --> M

    subgraph PS["pyaiservice 进程(默认 / 桌面模式)"]
        P["pyaiservice 主应用<br/>原生 AI API<br/>/ai/v1/*"]
        C["chat-server<br/>会话编排层<br/>/chat/v1/*"]
        X["mcp-server<br/>工具服务层<br/>/mcp/*"]
    end

    M -->|" /ai/v1/* "| P
    M -->|" /chat/* "| C
    M -->|" /mcp/* "| X

    P --> L["LLM / OpenAI 兼容接口"]
    P --> A["AIEngine"]

    C -->|" GET /internal/v1/app/options "| M
    C -->|" MCP transport: /mcp/ "| M
    M --> X

    X -->|" GET /internal/v1/app/options "| M
    X -->|" /api/v1/data* /api/v1/datatype/config "| M
    X -->|" /api/v1/file/content "| M

    M --> S["storageservice<br/>结构化数据查询层"]
    M --> G["manager<br/>文件访问层"]

一次聊天请求真实调用流程 #

sequenceDiagram
    participant U as 前端/用户
    participant M as master
    participant C as chat-server
    participant X as mcp-server
    participant S as storageservice
    participant G as manager
    participant N as netfileserver

    U->>M: POST /chat/v1/runs/{thread_id}/stream
    M->>C: 转发原路径
    C->>M: GET /internal/v1/app/options
    M-->>C: 返回 LLM / MCP 配置

    C->>M: MCP transport /mcp/
    M->>X: 转发到 mcp-server

    X->>M: GET /api/v1/data/node 或 /api/v1/data
    M->>S: /internal/v1/nodes/param 或 /internal/v1/data
    S-->>X: 返回结构化数据

    X->>M: GET /api/v1/file/content
    M->>G: /internal/v1/file/content
    G->>N: 打开镜像内文件
    N-->>G: 返回文件流
    G-->>X: 返回文件内容

    X-->>C: 返回工具执行结果
    C-->>U: SSE 流式输出

mcp服务架构图 #

flowchart TB
    A["mcp-server"]

    A --> B["HTTP 路由层<br/>真正定义接口"]
    A --> C["MCP 协议层<br/>把这些接口包装成 MCP tools"]
    A --> D["VFS 子系统<br/>把 GoldenEyes 数据组织成“文件视图”"]
    A --> E["向量检索子系统<br/>做语义搜索"]

    D --> F["Tree<br/>管结构"]
    D --> G["DataFetcher<br/>管数据"]
    D --> H["CursorRegistry<br/>管分页"]

    E --> I["Embedding"]
    E --> J["Qdrant"]
    E --> K["Rerank"]

启动 #

flowchart TB
    A["create_app()<br/>创建 mcp-server 应用"]

    A --> B["init_router(app)<br/>先把所有 HTTP 路由注册进 FastAPI"]
    B --> C["init_mcp(app)<br/>再从这些已注册路由生成 MCP tools"]
    C --> D["init_config()<br/>初始化 GES 配置、向量配置、缓存目录"]
    D --> E["init_llm_cache()<br/>初始化 LLM 本地缓存"]

两种入口汇入 #

flowchart TB
    A["入口一:普通 HTTP 请求<br/>/mcp/tools/v1/* 或 /mcp/tools/v1/vfs/*"]
    A --> B["直接进入 FastAPI API 函数"]
    B --> C["进入具体工具实现"]

    D["入口二:MCP 请求<br/>/mcp/"]
    D --> E["list_tools / call_tool"]
    E --> F["根据 operation_id 找到对应 HTTP API"]
    F --> B

GesClient路由规则图 #

flowchart TB
    A["GESClient.get()/post()"]

    A --> B{"请求路径长什么样?"}

    B --> C["/internal/v1/app/options"]
    C --> D["走 base_url<br/>通常是 GES/master 地址"]

    B --> E["其他 /internal/v1/*"]
    E --> F["走 storage_host<br/>通常是 storage 侧地址"]

    B --> G["/api/v1/*"]
    G --> H["走 base_url<br/>通常先到 master 再转发"]

VFS内部结构 #

flowchart TB
    A["/mcp/tools/v1/vfs/*<br/>VFS 接口入口"]

    A --> B["vfs_tools.py<br/>统一入口"]
    B --> C["get_vfs(cid)<br/>拿案件对应的 GesFileSystem"]
    B --> D["get_registry()<br/>拿全局 CursorRegistry"]

    C --> E["GesFileSystem<br/>VFS 核心对象"]
    D --> F["CursorRegistry<br/>分页句柄管理"]

    E --> G["Tree<br/>负责路径、目录、节点结构"]
    E --> H["DataFetcher<br/>负责读取叶子节点数据"]
    E --> I["pickle 缓存<br/>减少重复建树"]

    B --> J["LS / READ / GLOB / GREP / SEARCH / OUTLINE"]
    J --> E
    J --> F

Tree构建流程 #

flowchart TB
    A["Tree.build()<br/>开始构建整棵案件树"]

    A --> B["_initialize_tree()<br/>清空旧数据,创建根节点"]
    B --> C["_fetch_evidences()<br/>读取案件下所有检材"]
    C --> D["_build_tree_bfs()<br/>通过 /api/v1/data/node 做广度优先建树"]

    D --> E["insert_evidence_layer()<br/>把首层节点重新挂到“检材层”下面"]
    E --> F["insert_filesystem()<br/>补建“文件系统”子树"]
    F --> G["insert_location()<br/>给每个检材插入位置虚拟节点"]
    G --> H["remove_filesystem_redundant_layer()<br/>去掉冗余中间层"]
    H --> I["_initialize_entity_mapper()<br/>初始化实体映射"]
  1. _initialize_tree() 清空旧树,重新创建根节点 /。
  2. _fetch_evidences() 先把案件下面有哪些检材拿回来。 接口是 /internal/v1/evidence/cid。
  3. _build_tree_bfs() 用 /api/v1/data/node 去做广度优先遍历,把应用节点、业务节点先搭起来。
  4. insert_evidence_layer() 这一步会把树重新整理成“按检材分组”的样子。 也就是让每个检材变成根下面的一层。
  5. insert_filesystem() 找到文件系统起始节点,再继续用 /api/v1/data?datatype=file 去补文件系统目录树。
  6. insert_location() 为每个检材插入一组位置虚拟节点,例如:
    • 位置信息
    • 位置点信息.csv
    • 位置点聚类.csv
    • 轨迹数据.csv
  7. remove_filesystem_redundant_layer() 去掉文件系统下多余的重复层级,让路径更干净。
  8. _initialize_entity_mapper() 给后续向量检索和实体处理准备映射器。

读取vfs文件流程 #

flowchart TB
    A["POST /mcp/tools/v1/vfs/read"]

    A --> B["vfs_read API"]
    B --> C["get_vfs(cid)"]
    C --> D["GesFileSystem"]

    D --> E["_ensure_tree_loaded()<br/>先保证 Tree 已加载"]
    E --> F["resolve_path(path)<br/>把路径解析成叶子节点"]

    F --> G["GesFileSystem.read_chunk()"]
    G --> H["_ensure_data_fetcher()<br/>拿到 DataFetcher"]
    H --> I["fetch_leaf_data() / fetch_location_leaf_data()"]
    I --> J["把结构化记录转成可见字段"]
    J --> K["转成 CSV 文本"]

    K --> L["READ._read_csv()<br/>按列、过滤、排序处理"]
    L --> M["注册 StaticListCursor"]
    M --> N["返回第一页 + handle_id"]

vector/search流程 #

flowchart TB
    A["POST /mcp/tools/v1/vector/search"]

    A --> B["vector_search API"]
    B --> C["_parse_timerange()<br/>先解析时间条件"]
    C --> D["search_documents()"]

    D --> E["update_from_ges(cid)<br/>按案件更新向量搜索配置"]
    E --> F["创建 SearchClient"]
    F --> G["按 cid 重建 Qdrant collection 名"]

    G --> H["client.search(request)"]
    H --> I["加载 Tree pickle"]
    I --> J["用 entity_mapper 提取实体条件"]

    J --> K["EmbeddingClient<br/>把 query 变成向量"]
    K --> L["QdrantStorage<br/>做向量召回"]
    L --> M["RerankerClient<br/>做重排"]

    M --> N["按阈值过滤结果"]
    N --> O["返回最终搜索结果"]

主协作时序图 #

sequenceDiagram
    participant U as 前端/用户
    participant M as master
    participant API as chat-server API
    participant RM as RunManager
    participant G as ReAct Graph
    participant TM as ToolManager
    participant MCPC as MultiServerMCPClient
    participant MS as mcp-server

    U->>M: POST /chat/v1/runs/{thread_id}/stream
    M->>API: 转发到 chat-server

    API->>RM: create_run() + start_run()
    RM->>RM: 创建 RunInstance
    RM->>G: graph.astream(...)

    Note over G,TM: call_model 节点准备可用工具
    G->>TM: get_all_tools(...)
    TM->>TM: 组装本地工具 + MCP 工具
    TM->>MCPC: get_tools()

    MCPC->>M: MCP 请求 /mcp/ (list_tools)
    M->>MS: 转发 /mcp/
    MS-->>MCPC: 返回 MCP tools 列表

    MCPC-->>TM: 返回 tools
    TM-->>G: 返回 all_tools
    G->>G: model.bind_tools(tools)
    G->>G: LLM 决定是直接回答还是发起 tool_calls

    alt 需要调用工具
        G->>TM: 在 call_tools 节点执行 tool.ainvoke(args)
        TM->>MCPC: call_tool(...)
        MCPC->>M: MCP 请求 /mcp/ (call_tool)
        M->>MS: 转发 /mcp/
        MS-->>MCPC: 返回工具执行结果
        MCPC-->>TM: 返回 tool result
        TM-->>G: 生成 ToolMessage
        G-->>RM: 继续下一轮 agent -> tools -> agent
    else 不需要工具
        G-->>RM: 直接得到 AIMessage
    end

    RM->>RM: SSEEventAdapter 处理 messages/custom 流
    RM-->>API: 产出 SSE 事件
    API-->>M: text/event-stream
    M-->>U: 持续推送前端
flowchart TD
    A[用户提问]
    --> B[chat-server<br/>ReAct Agent]

    B --> C{本轮是否启用向量检索<br/>use_vector}

    C -- 否 --> D[VFS 工具]
    D --> D1[ls / read / grep / glob / fetch_next]
    D1 --> D2[mcp-server 虚拟文件系统]
    D2 --> D3[加载或构建 tree.pkl]
    D3 --> D4[按路径读取案件数据]
    D4 --> Z[把内容交给大模型回答]

    C -- 是 --> E[vector_search 工具]
    E --> E1[mcp-server /mcp/tools/v1/vector/search]
    E1 --> E2[加载 tree.pkl]
    E2 --> E3[提取实体 / 校验 Embedding 模型]
    E3 --> E4[问题转 Embedding]
    E4 --> E5[查询 Qdrant]
    E5 --> E6[reranker 重排序]
    E6 --> Z

    B --> F[ges_knowledge_qa 工具]
    F --> F1[GraphRAG]
    F1 --> F2[Qdrant + Neo4j]
    F2 --> Z