初始化

This commit is contained in:
2026-08-14 21:50:48 +08:00
commit d7e382f2e7
114 changed files with 24123 additions and 0 deletions

178
docs/agent_lifecycle.md Normal file
View File

@@ -0,0 +1,178 @@
# AI Agent 生命周期详解
## 概述
本文档详细描述中医AI Agent的完整生命周期以"患者主诉生成病历"和"病历生成处方"两个核心场景为例。
---
## 通用生命周期6个阶段
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌─────────┐
│ ①感知 │───▶│ ②规划 │───▶│ ③检索 │───▶│ ④工具 │───▶│ ⑤反思 │───▶│ ⑥输出 │
│ │ │ │ │ │ │ 调用 │ │ 校验 │ │ │
└─────────┘ └─────────┘ └─────────┘ └──────────┘ └─────────┘ └─────────┘
▲ │
│ │
└────────────────────── 记忆沉淀(长期学习) ◀──────────────────────────────────┘
```
---
## 场景A患者主诉 → 生成病历
### 时序图
```
患者/医生 Go服务(Controller) Agent Runner LLM MaxKB 规则引擎
│ │ │ │ │ │
│ POST /emr/gen │ │ │ │ │
│──────────────────▶│ │ │ │ │
│ │ CreateSession │ │ │ │
│ │─────────────────────▶│ │ │ │
│ │ │ 注入System Prompt │ │ │
│ │ │─────────────────▶│ │ │
│ │ │ │ │ │
│ │ │ 思考:需要检索病历模板 │ │
│ │ │ 调用工具maxkb_retrieve │ │
│ │ │───────────────────────────────────────────────▶│
│ │ │ │ │ RAG检索 │
│ │ │ │ │ 返回规范片段 │
│ │ │◀────────────────────────────────────────────────│
│ │ │ │ │ │
│ │ │ 生成病历初稿 │ │ │
│ │ │─────────────────▶│ │ │
│ │ │ │ │ │
│ │ │ 规则校验 │ │ │
│ │ │──────────────────────────────────────────────────────────▶│
│ │ │ │ │ │
│ │ │ 有issues─────Yes──▶ 反思修正循环 │ │
│ │ │────No──▶ 输出最终结果 │ │
│ │ │ │ │ │
│◀─────────────────│ JSON Response │ │ │ │
│ │ │ │ │ │
```
### 详细步骤
| 阶段 | 动作 | 涉及组件 | 代码位置 |
|------|------|----------|----------|
| ①感知 | 接收HTTP请求解析JSON创建Agent会话 | handler.EMRHandler.Generate | internal/handler/emr_handler.go |
| ②规划 | LLM分析任务需要哪些信息检索什么 | agent.Runner.Run → LLM | internal/agent/runner.go |
| ③检索 | 调用MaxKB获取病历模板、术语规范 | tool.MaxKBRetrieveTool | internal/tool/maxkb.go |
| ④工具 | 可选查HIS获取患者病史 | tool.HISTool | internal/tool/agent_tools.go |
| ⑤反思 | 规则引擎检查完整性,不通过则修正 | rule.EMRQualityChecker | internal/rule/rule_engine.go |
| ⑥输出 | 返回结构化病历JSON + 质控结果 | handler响应封装 | internal/handler/emr_handler.go |
---
## 场景B病历 → 生成处方
### 时序图
```
医生 Go服务 Agent Runner LLM MaxKB 规则引擎
│ │ │ │ │ │
│ POST /rx/gen │ │ │ │ │
│──────────────────▶│ │ │ │ │
│ │ CreateSession │ │ │ │
│ │───────────────────▶│ │ │ │
│ │ │ System Prompt注入 │ │ │
│ │ │─────────────────▶│ │ │
│ │ │ │ │ │
│ │ │ 辨证:太阳伤寒表实证 │ │
│ │ │ 检索经典方剂 │ │ │
│ │ │──────────────────────────────────▶│ │
│ │ │ │ │ 返回麻黄汤 │
│ │ │◀──────────────────────────────────│ │
│ │ │ │ │ │
│ │ │ 生成处方初稿 │ │ │
│ │ │─────────────────▶│ │ │
│ │ │ │ │ │
│ │ │ 配伍禁忌校验 ◀──────────────────────────────────────────────│
│ │ │ 剂量校验 ◀──────────────────────────────────────────────│
│ │ │ 孕妇安全检查 ◀──────────────────────────────────────────────│
│ │ │ │ │ │
│ │ │ Blocked? ──Yes──▶ 重新组方(反思循环) │
│ │ │────No──▶ 输出处方 │ │
│ │ │ │ │ │
│◀─────────────────│ JSON Response │ │ │ │
│ │ │ │ │ │
│ 医生审核确认 │ │ │ │ │
│ POST /rx/:id/approve │ │ │ │
│──────────────────▶│ 写入审计日志 │ │ │ │
│ │──────────────────────────────────────────────────────────────────────▶│
│ │ │ │ │ │
```
### 详细步骤
| 阶段 | 动作 | 涉及组件 | 安全级别 |
|------|------|----------|----------|
| ①感知 | 接收病历+患者信息,创建会话 | handler.PrescriptionHandler | - |
| ②规划 | LLM辨证→确定治则治法→选方思路 | agent.PrescriptionGenerator | - |
| ③检索 | MaxKB检索对应证型的经典方剂 | tool.MaxKBRetrieveTool | 知识来源 |
| ④工具 | 药典查询验证剂量、HIS查过敏史 | tool.PharmacopoeiaTool | 数据支撑 |
| ⑤反思 | **十八反十九畏硬校验** | rule.PrescriptionValidator | 🔴 硬拦截 |
| ⑤反思 | 孕妇/儿童剂量调整 | rule.PrescriptionValidator | 🟡 警告 |
| ⑤反思 | 过敏史冲突检查 | rule.PrescriptionValidator | 🔴 硬拦截 |
| ⑥输出 | 结构化处方 + Human-in-the-Loop | handler响应 + 医生审核 | 🟢 人工兜底 |
---
## 安全设计原则
### 三层防护体系
```
┌─────────────────────────────────────────────────┐
│ Layer 3: 人工审核Human-in-the-Loop │ ← 最后防线
│ 医生审核确认每一张处方 │
├─────────────────────────────────────────────────┤
│ Layer 2: 规则引擎硬约束独立于LLM │ ← 核心防线
│ 十八反十九畏 / 剂量上限 / 孕妇禁忌 / 过敏冲突 │
├─────────────────────────────────────────────────┤
│ Layer 1: MaxKB知识库事实基础 │ ← 知识防线
│ 药典规范 / 经典方剂 / 临床指南 │
└─────────────────────────────────────────────────┘
```
### 关键原则
1. **LLM不做最终决策** —— 只负责语言理解和推理,不负责医疗判断
2. **规则引擎独立运行** —— 不依赖LLM的自检用确定性代码做硬校验
3. **每次修正都有审计日志** —— 谁在什么时间做了什么修改,全链路可追溯
4. **Human-in-the-Loop** —— 处方最终必须经过医生确认才能生效
---
## 配置 MaxKB 知识库
### 快速部署
```bash
# Docker一键启动MaxKB
docker run -d --name maxkb -p 8080:8080 1panel/maxkb
# 访问 http://localhost:8080
# 默认账号: admin / MaxKB@123..
```
### 推荐上传的文档
| 分类 | 文档示例 | 用途 |
|------|----------|------|
| 药典 | 《中国药典》2020版中药部分 | 剂量、性味归经、禁忌 |
| 方剂 | 《方剂学》教材 / 《伤寒论》原文 | 经典方剂组成与主治 |
| 规范 | 《中医病历书写规范》 | 病历模板与质控标准 |
| 指南 | 各病种中医诊疗指南 | 辨证分型与治法 |
| 内部分享 | 本院名老中医经验方 | 院内知识沉淀 |
### 获取API Key
1. 登录MaxKB控制台
2. 进入「应用管理」→ 创建应用
3. 选择知识库 → 配置模型
4. 在「API Key管理」中生成 Key
5. 将 Key 和 AppID 填入 `manifest/config/config.yaml`

223
docs/architecture.md Normal file
View File

@@ -0,0 +1,223 @@
# TCM Agent 架构说明(现状版)
> 更新时间2026-08-11
> 适用版本:加固 + 观测面板落地之后的代码(含 RunLog / 医疗守卫 / DB 配置热加载 / Function Calling 完整协议)
本文档回答四个问题:**现在长什么样(架构)、一次请求怎么走(流程)、哪里还有坑(隐患)、为什么这样设计(优势)**。
---
## 一、架构总览
### 1.1 系统定位
Go Agent 是「PHP 主业务系统」的 **AI 增强侧车**PHP 负责组装 prompt、落库、面向前端Go 负责知识库检索、ReAct 多步推理、多模型调度与降级。PHP 挂了 Go 没意义Go 挂了 PHP 可以降级为直连模型(`ai_agent_via_agent=0`)。
```mermaid
flowchart TB
subgraph php[PHP 主系统 xk-api]
assist[AiMedicalAssistService<br/>组装 system/user messages]
factory[AiAgentFactory<br/>按 ai_agent_via_agent 决定直连或走 Go]
stepTable[(xk_ai_generation_step<br/>历史步骤审计表)]
end
subgraph go[Go Agent nl-tcm-agent]
auth[middleware.Auth<br/>JWT + SharedSecret 双轨]
sem[并发信号量 16<br/>超限 503]
enhancer[EnhancerService.Enhance<br/>包装层:统一埋点]
guard[medical_guard<br/>医疗相关性守卫]
react[ReactLoop<br/>Plan → Think-Act-Observe → Reflection]
resolve[resolveClient<br/>DB 优先解析生效模型]
runlog[RunLog 环形缓冲<br/>内存 200 条]
panel[/agent/view 观测面板/]
end
subgraph db[MySQL z_xk]
syscfg[(xk_system_config<br/>生效模型/开关)]
aikey[(xk_ai_model / xk_ai_api_key<br/>模型池与密钥 AES 加密)]
kbtbl[(xk_kb_* 本地知识库)]
end
subgraph llmv[模型供应商]
spark[讯飞 Spark]
dsk[DeepSeek]
oai[OpenAI 兼容]
end
assist --> factory -->|POST /api/v1/agent/enhance| auth --> sem --> enhancer
enhancer --> guard --> react
enhancer --> resolve --> syscfg
resolve --> aikey
react --> spark & dsk & oai
react -->|kb_enabled| kbtbl
enhancer -->|每次运行埋点| runlog --> panel
enhancer -->|steps 随响应返回| assist --> stepTable
```
### 1.2 目录与模块职责
| 目录 | 职责 | 关键点 |
|---|---|---|
| `main.go` | 启动入口 | config → dao → llm.InitLLM → agent.InitRunner → router.Setup`http.Server` 带四组超时;优雅停机 |
| `internal/config` | YAML/env 静态配置 | 只做启动期兜底;运行期以 DB 为准 |
| `internal/dao` | 数据库访问 | `LoadActiveLLMConfig`60s TTL 缓存)从 `xk_system_config` 实时解析生效模型AES 解密密钥 |
| `internal/agentcfg` | Agent 行为配置 | 从 `xk_system_config` 读 ReAct 开关、医疗守卫开关、debug 日志开关等 |
| `internal/llm` | 模型接入层 | `ProviderFactory` 创建客户端;`ModelRouter` 按场景路由 + `GetByConfig` 按配置指纹缓存/重建;`FallbackChain` 降级链 |
| `internal/service` | 业务核心 | `enhancer.go` 主流程、`reactloop.go` ReAct 循环、`medical_guard.go` 医疗守卫、`runlog.go` 运行轨迹缓冲 |
| `internal/agent` | 会话式 Runner | `/agent/chat` 用的多轮会话引擎 + TokenBudget与 EnhancerService 相互独立) |
| `internal/kb` | 本地知识库 | V1 关键词检索NoopEmbedder读写 `xk_kb_*`V2 预留向量化接口 |
| `internal/tool` | Function Calling 工具集 | MaxKB 检索等,注入 ReactLoop 供模型调用 |
| `internal/middleware` | 全局中间件 | 日志 / CORS / 双轨鉴权JWT + SharedSecret |
| `internal/router` | 路由注册 | 业务 API + 运维 API + 两个静态面板(`/kb/view``/agent/view` |
| `internal/security/xkaes` | AES 加解密 | 与 PHP `EncryptorService` 同算法,解密 DB 里的 API Key |
| `view/` | 前端单页 | `index.html`(知识库后台)、`agent.html`运行观测面板Vue3 + Element Plus CDN |
### 1.3 配置体系三层DB 为王)
```
优先级xk_system_configDB运行期实时 > 环境变量 > manifest/config/config.yaml启动期兜底
```
- **生效模型**`ai_active_provider` / `ai_active_model` / `ai_active_api_key_id` 三个键决定当前用哪个模型。Go 端 `dao.LoadActiveLLMConfig` 带 60s TTL 缓存,后台切换最多 1 分钟内全集群生效PHP 后台保存后还可调 `POST /api/v1/models/invalidate-cache` 立即生效。
- **走不走 Go Agent**:独立开关 `ai_agent_via_agent`PHP `AiAgentFactory` 读取),与「用哪个模型」彻底解耦——这是踩过坑后的关键设计:早期把 `ai_active_provider=agent` 当路由标记,导致模型配置和链路选择互相污染。
- **Agent 行为**`ai_agent_medical_guard`(守卫开关)、`ai_agent_debug_log`(请求体日志开关,含 PHI 默认关、ReAct 迭代数等,由 `agentcfg` 统一加载。
- **客户端缓存与指纹**`ModelRouter.GetByConfig` 对「model+url+key+provider」做指纹配置变了自动重建 LLM 客户端,没变就复用连接池。
---
## 二、一次 enhance 请求的完整流程
以 PHP 发起「开方建议」为例:
```mermaid
sequenceDiagram
participant PHP as PHP TcmAgentClient
participant MW as Auth + 信号量(16)
participant EN as EnhancerService.Enhance
participant GD as 医疗守卫
participant RC as resolveClient
participant RL as ReactLoop
participant LLM as LLM 供应商
participant LOG as RunLog 环形缓冲
PHP->>MW: POST /agent/enhance {scene, messages, kb_enabled}
MW->>MW: Bearer == SharedSecret并发 <16
MW->>EN: 通过(否则 401 / 503 快速失败)
EN->>GD: 白名单/黑名单关键词校验
alt 非医疗内容
GD-->>EN: 拦截
EN->>LOG: 记录 status=3守卫拦截
EN-->>PHP: 500 + medical_guard step
end
EN->>RC: 解析生效模型DB 60s 缓存 → yaml 兜底)
RC-->>EN: client + provider + api_key_id
EN->>RL: 进入 ReAct 循环
RL->>LLM: [1] Planning输出计划计划跑题则丢弃
loop Think-Act-Observe预算内多轮
RL->>LLM: [2] 带 tools 调用Lite 自动降级纯文本)
alt 触发 Function Calling
RL->>RL: 执行工具tool 结果带 tool_call_id 入栈
end
end
RL->>LLM: [3] Reflection 低温自检
RL->>LLM: [4] JSON 不合法时自动修复
RL-->>EN: content + steps[](每步耗时/token/detail
EN->>LOG: 记录 status=1/2 + 完整 steps
EN-->>PHP: {content, provider, model, steps, total_ms}
PHP->>PHP: steps 落 xk_ai_generation_step
```
关键细节:
1. **守卫在最前面**:不花任何 token 就能拒掉「PHP 组装出错 / 接口被滥用」的非医疗请求,同时留下 `medical_guard` 步骤便于定位是谁的问题。
2. **每次调用都实时解析模型**:不是启动时定死,后台切模型不用重启 Go。
3. **消息序列保证以 user/tool 结尾**Planning 注入后补 user 帧,兼容 Spark Lite 等严格遵循 OpenAI 协议的模型(曾因 assistant 结尾触发 10003
4. **网络错误自动重试 1 次**(间隔 1s只重试连接/超时类错误「API 返回 xxx」业务错误不重试避免双倍烧 token。
5. **无论成败都写 RunLog**包装层统一埋点守卫拦截、配置解析失败这类「PHP 侧看不到」的失败也有记录。
### 观测面板(/agent/view
- **数据链路**RunLog内存 200 条)→ `GET /api/v1/agent/runs`(列表摘要)/ `runs/:id`(完整时间线)/ `stats`成功率、平均耗时、token、按场景分布
- **三个 Tab**:运行记录(点行弹时间线抽屉)、统计概览、生效配置(复用 `/models/active-config` + `/health`)。
- **localStorage 记忆**:当前 Tab`agent_view_tab`)、鉴权 token`agent_view_token`)、自动刷新开关(`agent_view_refresh`5s 轮询)、主题与 KB 页共用 `kb_theme`
- 页面本身放行不鉴权(纯静态无数据),数据 API 走全局 Auth。
---
## 三、隐患清单(按风险排序)
### 高:模型能力短板不是代码能完全兜住的
- **Lite 模型「计划腔」污染输出**:即使内容是医疗的,输出仍可能带「执行阶段/步骤一」的项目管理腔调(守卫只拦非医疗关键词,拦不住文风跑偏)。实测已复现。
- **Reflection 审核不通过时内容照样返回**:目前 reflection 结果只作为附注拼进输出,没有「不合格 → 重试或换模型」的闭环。临床场景下这是最值得补的一环。
- 缓解方向:审核不通过时触发一次重生成或 fallback 到更强模型;或在 PHP 侧按 reflection 结果决定是否展示。
### 高:医疗守卫是关键词规则引擎
- 白名单/黑名单靠人工维护,存在**误杀**(新病种词不在白名单)与**漏放**(换个说法绕过黑名单)两种风险,且没有命中率统计来指导调词。
- 缓解方向:面板已能看到拦截记录,可定期复盘;长期可换成小模型二分类。
### 中:观测数据是单实例内存态
- RunLog 重启即清空、多实例部署时各自为政(面板只能看到所连实例的数据)、只保留最近 200 条。当前单实例部署下可接受,**水平扩容前必须重新设计**(落 Redis/DB 或接 Prometheus
- 同理,**并发限流的 16 是进程内信号量**:多实例时总并发 = 16 × N且 16 写死在 `router.go`,不可配置。
### 中:鉴权与暴露面
- SharedSecret 是**明文字符串比对**且和 KB 管理口令一起写在 `config.yaml``qiqi991012`),中间件里还硬编码了开发默认 JWT 密钥。生产必须换强随机值并走环境变量。
- `GET /models/active-config` **无鉴权**key 已脱敏只留尾号,但 provider/model/URL 对外可见)。
- CORS 是 `Allow-Origin: *`panic recovery 会把 recovered 内容原样返回给客户端(可能泄露内部路径/变量名)。
### 中:超时与重试的边界情况
- `WriteTimeout=300s` 是兜底,但 ReactLoop 本身**没有整体 deadline**:多轮迭代 + 每轮重试 1 次 + fallback 链,极端情况下可能逼近甚至顶到 300s届时连接被强制掐断、PHP 只收到断连而非结构化错误。
- `isRetryableNetworkError` 靠**错误文案子串匹配**"timeout"、"connection reset"…Go 版本升级或客户端封装改文案就可能失效——这是无类型错误链下的妥协,脆弱但可用。
### 低:其他已知瑕疵
- `tool_calls[].id` 缺失时用 `time.Now().UnixNano()` 生成,理论上高并发同纳秒会撞(概率极低)。
- KB V1 是 NoopEmbedder 纯关键词检索,召回质量有限,`ai_kb_source=local` 下检索增强效果打折V2 接 BGE-M3 前这是已知取舍。
- 60s 配置缓存意味着**不调 invalidate-cache 时切换最多延迟 1 分钟**——依赖 PHP 后台保存后主动调那个接口。
- 无 Prometheus/metrics 端点,告警只能靠日志和面板人工看。
---
## 四、优势(为什么这样设计)
### 1. 配置热加载 + 指纹缓存:切模型不重启
后台改 `xk_system_config` → Go 下一次请求自动用新配置60s 内,或调 invalidate 立即),客户端按指纹复用/重建。对比「启动时读一次配置」的方案,运维成本降了一个量级,这也是本项目踩坑(模型不一致)后重构出来的核心能力。
### 2. 多层防跑题:守卫 → 锚定 → 计划校验 → 反思
入口医疗守卫0 token 拦非医疗、Planning prompt 医疗锚定、计划输出不含医疗词即丢弃、Reflection 自检——四层针对「轻量模型易被通用指令带偏」的现实问题层层设防。单层都可能漏,叠加后 Lite 这类模型也能稳定输出医疗内容。
### 3. 故障半径控制:每一层都有降级路径
- 请求层:信号量满 → 503 快速失败 → PHP 走直连降级,不排队堆积;
- 网络层:瞬时抖动自动重试 1 次;
- 模型层:主模型挂 → FallbackChain 自动切备用;
- 配置层DB 抖动 → 回落 yaml/env 兜底配置;
- 进程层panic recovery + 优雅停机 + 四组 HTTP 超时防慢连接。
### 4. 全链路可观测,且观测不依赖被观测对象
每一步(守卫/检索/plan/llm_call/tool/reflection都有独立的耗时、token、detail实时看 Go 内存 RunLog + `/agent/view` 面板DB 挂了面板照常工作),历史审计查 PHP `xk_ai_generation_step` 表。排障时能精确回答「哪一步、花了多久、模型说了什么」。
### 5. 协议完整性:换更强模型零改动
Function Calling 按 OpenAI 完整协议实现(`tool_calls[].id``tool_call_id` 回传、`arguments` JSON 字符串),消息序列符合严格校验;今天用 Lite自动降级纯文本明天切 Pro/Max/DeepSeek 直接享受工具调用,不用再动协议层。
### 6. 安全默认值
API Key AES 加密入库(与 PHP 同算法)、日志默认不打请求体(`ai_agent_debug_log` 显式开启才打,防 PHI 泄漏、active-config 接口 key 脱敏只留尾号。
---
## 五、后续建议(按投入产出比排序)
1. **Reflection 闭环**:审核不合格时自动重试一次或 fallback 到更强模型(隐患一的直接解法)。
2. **ReactLoop 整体 deadline**:用 `context.WithTimeout`(如 240s包住整个循环保证永远先于 WriteTimeout 结构化返回。
3. **限流阈值进配置**:把 16 挪到 `xk_system_config` 或 config.yaml。
4. 生产部署前:换强随机 SharedSecret、收紧 CORS、active-config 加鉴权、panic 响应脱敏。
5. 多实例化时RunLog 与限流改集中式Redis或直接接 Prometheus + Grafana 替代自建面板。

View File

@@ -0,0 +1,137 @@
# 第 1 步:部署 MaxKB 平台
> 目标:在服务器上跑起来一个 MaxKB能在浏览器打开它的管理后台。
---
## 方式 A用 1Panel 应用商店装(推荐,最简单)
你的环境已经是 1Panel这是最省事的。
### 步骤
1. **登录 1Panel 后台**:浏览器打开你的 1Panel一般 `http://服务器IP:1Panel端口`
2. **进入应用商店**:左侧菜单 → **应用商店**
3. **搜索 MaxKB**:在搜索框输入 `maxkb`
4. **点击安装**
- 名称:保持默认 `maxkb`
- 端口:**改成 `8081`**(重要!不要用默认 8080会和 Go Agent 冲突)
- 数据库:选"外部数据库"或让 MaxKB 自带的(小白选自带即可)
- 点"确认"等 1-2 分钟
5. **等待状态变成"运行中"**
---
## 方式 B用项目里的 docker-compose 装
如果 1Panel 商店里没有 MaxKB用项目自带的脚本。
### 步骤
1. **SSH 登录服务器**
2. **进入项目目录**
```bash
cd "/path/to/nl-tcm-agent/manifest/docker"
```
3. **⚠️ 先改端口**(项目默认配的是 8080会和 Go Agent 冲突)
用 `vim docker-compose.yml` 或 `nano docker-compose.yml`,找到 maxkb 部分:
```yaml
maxkb:
image: 1panel/maxkb:latest
ports:
- "8080:8080" # ❌ 改这里
```
改成:
```yaml
maxkb:
image: 1panel/maxkb:latest
ports:
- "8081:8080" # ✅ 改成 8081
```
4. **启动 MaxKB**
```bash
docker-compose up -d maxkb
```
5. **等 1-2 分钟,看启动日志**
```bash
docker logs -f maxkb
```
看到类似 `Application started on port 8080` 就说明成功了,按 `Ctrl+C` 退出日志。
---
## 方式 C直接 docker run最朴素
如果上面都不方便,一条命令搞定:
```bash
docker run -d \
--name maxkb \
-p 8081:8080 \
-v maxkb_data:/app/data \
--restart unless-stopped \
1panel/maxkb:latest
```
> `-p 8081:8080` 表示把容器内 8080 映射到服务器 8081。**记住你映射的端口,后面要用。**
---
## 验证部署是否成功
### 1. 浏览器访问管理后台
打开:`http://你的服务器IP:8081`
> 把 `你的服务器IP` 换成实际 IP。本地开发就是 `http://127.0.0.1:8081`。
### 2. 看到登录页 → 成功
第一次会让你设置管理员账号密码,或者用默认账号:
- 用户名:`admin`
- 密码:`MaxKB@123..`
### 3. 登录成功,看到控制台
![MaxKB 登录后会看到欢迎页]
到这里 MaxKB 部署完成。下一步去上传资料。
---
## 常见问题
### Q1访问不了 8081 端口
**原因**:服务器防火墙没放端口。
**解决**
- 云服务器:去云厂商控制台 → 安全组 → 入方向规则 → 加一条"TCP 8081 允许"
- 自建机:`sudo ufw allow 8081/tcp`Ubuntu或 `sudo firewall-cmd --add-port=8081/tcp --permanent && sudo firewall-cmd --reload`CentOS
### Q2MaxKB 启动后立刻挂掉
**原因**内存不够MaxKB 至少要 2G 空闲内存)。
**解决**
- `free -h` 看内存
- 不够就加内存,或者关掉其他占内存的容器
### Q31Panel 装的 MaxKB 在哪个端口?
1Panel 应用商店装完后,在 1Panel → 容器 → 找到 maxkb → 看端口映射。也可以在 1Panel → 应用商店 → 已安装 → maxkb → 详情里看到。
---
下一步:[02_上传知识库.md](./02_上传知识库.md)

View File

@@ -0,0 +1,143 @@
# 第 2 步:上传知识库资料
> 目标:把项目里准备好的 7 个中医资料文件,上传到 MaxKB让它能"翻书"。
---
## 资料文件在哪?
项目里已经为你准备好了 7 个 Markdown 文件,路径:
```
xk-api/storage/maxkb_seed/
├── 01_decoction_methods.md 煎法(先煎/后下/包煎…)
├── 02_entrusted_process.md 委托调剂规则
├── 03_tcm_dict.md 中医证候 / 治法 / 疾病字典
├── 04_icd_diagnosis.md ICD-10 诊断编码
├── 05_drug_catalog.md 药品库
├── 06_tcm_conflict.md 中医配伍禁忌(十八反十九畏)
└── 07_mr_field_spec.md 病历字段规范
```
> 这些就是 AI 在生成病历/处方时需要"翻书"查的固定资料。
---
## 在 MaxKB 创建知识库
### 1. 登录 MaxKB 后台
浏览器打开 `http://你的服务器IP:8081`,登录。
### 2. 进入"知识库"菜单
- **新版4.x**:首页就有 **"创建知识库"** 大按钮,直接点
- **老版1.x/2.x**:左侧菜单 → 点 **知识库**(或 **Knowledge Base**)→ 右上角"创建知识库"
### 3. 点击"创建知识库"
首页或知识库列表页有 **创建知识库** 按钮,点击。
### 4. 填写基本信息
| 字段 | 填什么 |
|---|---|
| 知识库名称 | `中医医疗知识库`(随便起,自己认得就行)|
| 知识库描述 | `中医诊疗参考资料煎法、证候、ICD-10、药品库等` |
| 知识库类型 | 选 **通用型** / **文档型**(默认即可) |
**下一步**
### 5. 选择向量模型
MaxKB 会让你选"文本向量模型",用来把文字转成数字。
**推荐选择**
- 如果 MaxKB 自带本地 embedding 模型(如 `m3e-base`):选它,免费
- 如果没有:用 MaxKB 默认的,或后续配置 OpenAI embedding要 API Key
> 小白先用默认,跑通再说。
**创建**
---
## 上传资料文件
### 1. 进入新建好的知识库
列表里点开 **中医医疗知识库**
### 2. 切到"文档"标签
知识库里有几个 Tab文档 / 设置。点 **文档**
### 3. 点击"上传文档"
按钮一般叫 **添加文档****上传文档**
### 4. 选择本地文件
`xk-api/storage/maxkb_seed/` 下的 **7 个 md 文件全部选中**,一次性传上去。
> 也支持 zip / pdf / docx / txt但我们准备的 md 最准。
### 5. 等待向量化完成
上传后每条文档会有"状态"列:
-**向量化中**:正在处理(每条 1-2 分钟)
-**已就绪 / 成功**:可以用了
-**失败**:点开看错误原因
**全部变成 ✅ 才能进下一步**
> 如果卡很久没动,可能是 embedding 模型没配好,去看 MaxKB 设置 → 模型管理。
---
## 验证知识库能搜
### 1. 在知识库页面找"命中测试"或"搜索测试"
每个知识库都自带一个测试入口,能让你试搜。
### 2. 输入测试词
输入:`痰湿中阻 煎法`
### 3. 看结果
应该返回 `01_decoction_methods.md``03_tcm_dict.md` 的相关片段。
> 如果搜不到任何东西,说明:
> - 文档还没向量化完成 → 等等再试
> - 或者文件是空的 → 检查 md 文件是否有内容
---
## 常见问题
### Q1上传报错"不支持的文件类型"
.md 文件 MaxKB 是支持的。如果报错,把文件后缀改成 `.txt``.markdown` 再传。
### Q2向量化一直失败
**原因**embedding 模型没配好。
**解决**
- MaxKB 后台 → 设置 → 模型管理 → 看是否有可用的 embedding 模型
- 没有就加一个:本地 m3e-base免费或 OpenAI text-embedding-3-small要 Key
### Q3上传成功但搜不到内容
**原因**可能文档没分段成功或文字内容是图片OCR 失败)。
**解决**
- 进文档详情看"分段"列表
- 我们准备的 md 都是纯文字,应该没问题
- 如果还不行,单独上传一个文件试试
---
下一步:[03_创建应用拿密钥.md](./03_创建应用拿密钥.md)

View File

@@ -0,0 +1,281 @@
# 第 3 步:创建智能体并拿 API 密钥
> 目标:在 MaxKB 里建一个"智能体"(老版本叫"应用"),把它和一个知识库关联起来,再拿到 3 个连接用的钥匙。
>
> Go Agent 不会直接调"知识库",而是调"智能体",由"智能体"去访问关联的知识库。
---
## ⚠️ 先看MaxKB 老版 vs 新版术语对照
如果你看到的界面里没有"应用"两个字,但有"**智能体**",说明你装的是 **MaxKB 4.x 新版**。两者底层 API 完全一样,只是改了名字:
| 老版MaxKB 1.x / 2.x | 新版MaxKB 4.x | 你的代码注释里写的 |
|---|---|---|
| **应用** | **智能体** | "应用"(代码先写的)|
| 创建应用 | 创建智能体 | — |
| 简单应用 / 工作流 | 空白创建 / 从模板创建 | — |
| 应用信息 | 智能体概览 | — |
| API 文档 | API 文档(位置变了,见下文)| — |
**API 接口路径完全没变**`/api/application/<AppID>/...` 在新旧版本都能用Go Agent 代码不用动。
---
## 你看到的 4 个按钮都是干嘛的?
你截图里的 4 个入口:
| 按钮 | 是不是这一步要点的? | 解释 |
|---|---|---|
| **创建智能体** | ✅ **就是这个!** | 相当于老版的"创建应用" |
| **创建知识库** | ❌ 上一步已做 | 上传中医资料用的,已经做过了 |
| **添加工具** | ❌ 不需要 | MaxKB 自己的工具系统(脚本/工作流),我们用不上 |
| **添加模型** | ⚠️ 可能要先做 | 给 MaxKB 配大语言模型/向量模型。**如果创建智能体时报错"未配置模型",先回来做这个** |
---
## 1. (前置)确认模型已配置
**新版 MaxKB 强制要求先配模型才能建智能体**。如果你之前没配过,先做这一步:
### 操作
1. 首页 → **添加模型** (或左下角"模型管理"
2. 至少配 2 类:
- **大语言模型**:选 DeepSeek你自己有 API Key 最便宜)/ 或 OpenAI GPT-4o
- **向量模型**:选 `m3e-base`(本地免费)或 `text-embedding-3-small`OpenAI
3. 填写:
- 模型类型:选 DeepSeek / OpenAI 等
- API Key你的 Key
- Base URL模型厂商地址
> 💡 **这个模型是 MaxKB 自己生成对话时用的**。我们 Go Agent 主要走"检索接口"search不太会真用它但 MaxKB 要求智能体必须配模型。
> 💡 **如果跳过这步,下一步"创建智能体"会一直报错**。
---
## 2. 创建智能体
### 操作
1. 首页 → **创建智能体**
2.**空白创建**(不要选"从模板",模板是人家做好的,不适合我们)
3. 填写信息:
| 字段 | 填什么 |
|---|---|
| 名称 | `中医诊疗助手`(随便起)|
| 描述 | `给 Go Agent 用的知识检索入口` |
| 类型 | 保持默认(**简单工作流 / 基础问答**,不要选复杂工作流)|
4. 进入智能体后,**关键一步:关联知识库**
- 在智能体的 **"知识库"** 或 **"资源"** 标签
- 点击 **"添加知识库"**
- **勾选第 2 步创建的"中医医疗知识库"**
- 保存
5. (可选)在 **"模型"** 或 **"设置"** 标签:
- 选一个大模型DeepSeek 即可)
6. **点右上角"保存"或"发布"**
- 新版 MaxKB 智能体必须**手动发布**才能用
- 状态显示"已发布"才算成功
---
## 3. 拿到三个钥匙
### 进入智能体"概览"页面
在智能体列表里点开你刚建的"中医诊疗助手",看到 **概览** Tab。
### 🔑 钥匙 1Base URL
在概览页面找 **"API 文档地址"** 或 **"API 访问"** 区域:
```
http://192.168.1.100:8081
```
**这就是 Base URL**,注意:
- 端口要写你部署时映射的端口(比如 8081
- 不要带末尾 `/`
- 要写 Go Agent 能访问到的地址(同机 `127.0.0.1`,跨机用 IP
### 🔑 钥匙 2API Key
在概览页面点 **"API Key"** 或 **"API 密钥"** 按钮:
1.**"创建 API Key"**
2. 给它一个名字(如 "go-agent"
3. 复制出来:
```
application-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
或新版可能是:
```
agent-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
> **格式可能以 `application-` 或 `agent-` 开头**,两种都对,**完整复制**即可。
>
> ⚠️ **关闭窗口后可能再也看不到完整 Key**,所以**当场复制存好**
### 🔑 钥匙 3App ID
App ID 一般显示在:
- **概览页面的"API 文档地址"里**,形如:`http://.../api/application/<这串UUID>/chat/completions`
-**API Key 列表的"应用 ID"列**
-**浏览器地址栏** URL 里的 UUID
格式:
```
a1b2c3d4-e5f6-7890-abcd-ef1234567890
```
UUID 格式8-4-4-4-12
> 💡 **快捷找法**:在概览页面点"API 文档"或"Swagger",浏览器打开的 URL 里就包含 App ID。
---
## 4. 验证 API 能调通(强烈建议)
打开命令行PowerShell / Terminal / cmd 都行),用 curl 测试。
### 测试 1对话接口OpenAI 兼容格式)
```bash
curl -X POST "http://你的服务器IP:8081/api/application/你的AppID/chat/completions" ^
-H "Authorization: Bearer 你的APIKey" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"deepseek-chat\",\"messages\":[{\"role\":\"user\",\"content\":\"痰湿中阻的煎法\"}]}"
```
> Windows 用 `^` 换行Linux/Mac 用 `\`。
>
> 不熟悉 curl 可以直接写一行。
### 期望返回
```json
{
"id": "xxx",
"choices": [
{
"message": {
"role": "assistant",
"content": "痰湿中阻的方剂常用煎法..."
}
}
]
}
```
### 测试 2检索接口Go Agent 用的就是这个)
```bash
curl -X POST "http://你的服务器IP:8081/api/application/你的AppID/search" ^
-H "Authorization: Bearer 你的APIKey" ^
-H "Content-Type: application/json" ^
-d "{\"query\":\"痰湿中阻 煎法\",\"top_k\":3}"
```
期望返回:
```json
{
"code": 200,
"data": {
"documents": [
{"content": "煎法说明……痰湿中阻宜……", "score": 0.85},
{"content": "中医证候:痰湿中阻……", "score": 0.78}
]
}
}
```
> ⚠️ **新版 MaxKB 的 search 接口可能改名了**。如果返回 404去看"API 文档"Swagger找 `/hit-test` 或 `/search` 或 `/embedding/search` 路径。Go Agent 代码 [internal/tool/maxkb.go](../../../../internal/tool/maxkb.go) 第 125 行调的就是 `/api/application/<AppID>/search`,新版可能需要适配(见下文排错)。
---
## 5. 常见错误
### 返回 401 Unauthorized
**原因**API Key 错或没传 Authorization 头。
**解决**
- 重新去智能体 → 概览 → API Key 复制
- 确认 Header 是 `Authorization: Bearer application-xxx`(注意 `Bearer ` 后有个空格)
### 返回 404 Not Found
**原因**URL 不对,特别是 App ID 拼错,或者新版接口路径变了。
**解决**
- 打开智能体 → 概览 → **API 文档**Swagger
- 在 Swagger 里找"对话接口"或"检索接口"
- 复制完整 URL 替换
### 返回 500 / "应用未发布"
**原因**:智能体没发布。
**解决**
- 回到智能体详情
- 右上角找 **"发布"** 按钮
- 状态变成"已发布"
### 返回空 documents 数组
**原因**:知识库里没相关内容,或文档没向量化完。
**解决**:回第 2 步检查知识库。
### 创建 API Key 按钮置灰
**原因**:智能体未发布。
**解决**:先发布智能体,再创建 API Key。
---
## 6. 钥匙清单(待会儿要填)
把下面这张表填好,下一步要用:
| 钥匙 | 你的值 |
|---|---|
| Base URL | `http://____________________:8081` |
| API Key | `application-____________________`(或 `agent-____________________`|
| App ID | `________________________________` |
---
## 7. ⚠️ 如果新版 search 接口路径不一样怎么办?
**先做完上面所有步骤**,然后到 [06_修复检索词Bug.md](./06_修复检索词Bug.md) 改完后,**第 7 步验证时如果 kb_retrieval step 一直 status=2**,回来看这里:
1. 打开智能体概览 → API 文档Swagger
2. 在 Swagger 里搜索 `/search``/hit``/embedding`
3. 找到对应的检索接口路径
4. 改 Go Agent 代码:`nl-tcm-agent/internal/tool/maxkb.go` 第 125 行:
```go
// 原代码:
url := fmt.Sprintf("%s/api/application/%s/search", c.BaseURL, c.AppID)
// 改成新版路径(示例,根据 Swagger 实际路径):
url := fmt.Sprintf("%s/api/application/%s/hit-test", c.BaseURL, c.AppID)
```
5. 重启 Go Agent
> 也可以告诉我你的 MaxKB 版本和 Swagger 里看到的检索接口路径,我帮你改。
---
下一步:[04_配置GoAgent.md](./04_配置GoAgent.md)

View File

@@ -0,0 +1,146 @@
# 第 4 步:配置 Go Agent 连接 MaxKB
> 目标:改 Go Agent 的配置文件,让它知道 MaxKB 在哪、用什么钥匙访问。
---
## 1. 找到配置文件
文件位置:
```
nl-tcm-agent/manifest/config/config.yaml
```
---
## 2. 改 maxkb 节点
打开 config.yaml找到开头那一段第 13-17 行附近):
```yaml
# ========== MaxKB 知识库配置 ==========
maxkb:
base_url: "http://127.0.0.1:8080" # ❌ 这是占位符
api_key: "application-xxxxxxxx" # ❌ 这是占位符
app_id: "xxxxxxxx-xxxx-xxxx" # ❌ 这是占位符
```
### 改成你在 [第 3 步](./03_创建应用拿密钥.md) 拿到的真实值
```yaml
# ========== MaxKB 知识库配置 ==========
maxkb:
base_url: "http://192.168.x.x:8081" # ✅ MaxKB 实际地址(注意端口!)
api_key: "application-真实APIKey" # ✅ 第 3 步拿到的 API Key
app_id: "真实AppID-UUID格式" # ✅ 第 3 步拿到的 App ID
```
---
## 3. 三个常见坑
### ⚠️ 坑 1地址要写 Go Agent 能访问到的
| Go Agent 部署位置 | base_url 怎么写 |
|---|---|
| 同一台服务器(本机) | `http://127.0.0.1:8081` |
| Docker 容器内(同一 compose | `http://maxkb:8080`(用服务名)|
| 另一台服务器 | `http://其他服务器IP:8081` |
**不要写 `localhost`**,因为 Docker 容器里的 localhost 不是宿主机。
### ⚠️ 坑 2端口别填错
- MaxKB 容器内一直是 8080
- 但你宿主机映射的是 8081
- 所以**外部访问写 8081容器间互访写 8080**
### ⚠️ 坑 3YAML 格式
- key 后面必须有**一个空格**再写值
- 字符串**必须用双引号**(单引号也行,但别一个单一个双)
- 缩进必须用空格,不能用 Tab
```yaml
# ✅ 对
maxkb:
base_url: "http://127.0.0.1:8081"
# ❌ 错(冒号后没空格)
maxkb:
base_url:"http://127.0.0.1:8081"
# ❌ 错(用了 Tab
maxkb:
base_url: "http://127.0.0.1:8081"
```
---
## 4. 重启 Go Agent
配置改完要重启服务才生效。
### 本地开发
```bash
cd "/path/to/nl-tcm-agent"
# 停掉旧的Ctrl+C
# 重新启动
go run main.go
```
### Docker 部署
```bash
cd "/path/to/nl-tcm-agent/manifest/docker"
docker-compose restart agent
```
### 1Panel 部署
1Panel → 容器 → 找到 tcm-agent → 重启。
---
## 5. 看启动日志确认
启动后看日志,应该看到:
```
[启动] ✅ 中医 AI Agent 服务已启动,监听端口 :8080
```
如果 MaxKB 配置错启动时一般不会报错MaxKB 是按需调用的,不启动就调)。所以这一步**不能完全验证配置对**,要进第 7 步验证。
---
## 6. 完整配置示例(参考)
这是一个完整的 maxkb 节点示例(**值是假的,替换成你自己的**
```yaml
# ========== MaxKB 知识库配置 ==========
maxkb:
base_url: "http://192.168.1.100:8081"
api_key: "application-abcd1234efgh5678ijkl9012mnop3456"
app_id: "f4b8a2c1-3d5e-4f6a-9b7c-8d2e1f0a3b5c"
```
---
## 常见问题
### Q1YAML 改完启动报错 `yaml: unmarshal errors`
**原因**YAML 格式错了(缩进/引号/冒号空格)。
**解决**:找个在线 YAML 校验器(如 yamllint.com粘进去检查。
### Q2怎么知道 Go Agent 真的连上了 MaxKB
启动时连不上也不会报错。要等真正发起请求时才知道。**先继续下一步配置开关,最后用第 7 步的 curl 测试**。
---
下一步:[05_打开PHP开关.md](./05_打开PHP开关.md)

View File

@@ -0,0 +1,135 @@
# 第 5 步:打开 PHP 端的"启用知识库"开关
> 目标:在数据库里把"病历场景"和"处方场景"是否走 MaxKB 检索的开关打开。
>
> PHP 端有 4 个开关,对应 4 个场景。默认都是关的0需要手动改成开1
---
## 1. 这 4 个开关在哪
它们存在数据库 `z_xk.xk_system_config` 表里:
| config_key | 控制 |
|---|---|
| `ai_agent_kb_enabled_medical_record` | 病历生成是否查 MaxKB |
| `ai_agent_kb_enabled_prescription` | 处方生成是否查 MaxKB |
| `ai_agent_kb_enabled_prescription_validate` | 处方校验是否查 MaxKB |
| `ai_agent_kb_enabled_knowledge_qa` | 知识问答是否查 MaxKB |
> 这 4 个开关本来就应该在 [20260809/ai_generation_extend_for_agent.sql](../../../../../gs/sql/萧康云医/20260809/ai_generation_extend_for_agent.sql) 执行后就已经插入数据库了。
---
## 2. 检查开关是否已存在
用数据库工具Navicat / DBeaver / 1Panel 自带的 phpMyAdmin`z_xk` 库,执行:
```sql
SELECT config_key, config_value
FROM xk_system_config
WHERE config_key LIKE 'ai_agent_kb_enabled_%';
```
### 情况 A能查到 4 条记录
太好了,直接进**第 3 步**改值。
### 情况 B查不到
说明 [ai_generation_extend_for_agent.sql](../../../../../gs/sql/萧康云医/20260809/ai_generation_extend_for_agent.sql) 还没执行。先执行这个 SQL 文件,再回来查。
---
## 3. 打开开关(改成 1
执行 UPDATE 把对应场景的开关改成 `1`
```sql
-- 病历生成走 MaxKB
UPDATE xk_system_config
SET config_value = '1', updated_at = UNIX_TIMESTAMP()
WHERE config_key = 'ai_agent_kb_enabled_medical_record';
-- 处方生成走 MaxKB
UPDATE xk_system_config
SET config_value = '1', updated_at = UNIX_TIMESTAMP()
WHERE config_key = 'ai_agent_kb_enabled_prescription';
```
> 如果你暂时只想测试一个场景,只改一条也行。
### 验证
再查一次:
```sql
SELECT config_key, config_value FROM xk_system_config WHERE config_key LIKE 'ai_agent_kb_enabled_%';
```
应该看到:
```
ai_agent_kb_enabled_medical_record 1
ai_agent_kb_enabled_prescription 1
```
---
## 4. ⚠️ 清缓存(重要)
PHP 端 `SystemConfigService` 有 1 小时内存缓存。如果你直接改数据库不开PHP 可能还读旧值。
### 方法 A重启 PHP-FPM最稳
1Panel → 网站 → 找到你的 PHP 站点 → 重启 PHP-FPM。
或者 SSH
```bash
systemctl restart php-fpm
# 或
service php8.1-fpm restart
```
### 方法 B等 1 小时
如果你不急,等 1 小时缓存自动过期。
### 方法 C从后台改推荐
如果你已经在 [system-config 页面](../../../../../gs/xk-admin/apps/web-antd/src/views/system/system-config/index.vue) 加了这 4 个 key 的可视化(**注意:当前前端只加了 `ai_react_*` 等 13 个,没有这 4 个 `ai_agent_kb_*`**),可以从后台改并自动清缓存。
---
## 5. 开关与场景对应关系(理解记忆)
| PHP 调用时的 `scene` | 对应的开关 key | 现在的值 |
|---|---|---|
| `medical_record`(病历)| `ai_agent_kb_enabled_medical_record` | 0 → 改成 1 |
| `prescription`(处方)| `ai_agent_kb_enabled_prescription` | 0 → 改成 1 |
PHP 调用 Go Agent 时会带 `scene=medical_record`Go Agent **不查开关**,开关完全在 PHP 端决定是否发请求时带 `kb_enabled=true`
---
## 常见问题
### Q1改完没生效
99% 是 PHP 缓存。重启 PHP-FPM。
### Q2临时关掉怎么办
`config_value` 改回 `0`,再重启 PHP-FPM。
---
## 🚨 重要提醒:做完这一步还不够!
到这里你以为开关闭了,**但实际还是不会触发检索**
为什么?因为 PHP 调用 Go Agent 时**没有传"检索关键词"字段**Go Agent 看到关键词为空,会自动跳过检索。
下一步**必须**做:[06_修复检索词Bug.md](./06_修复检索词Bug.md)
---
下一步:[06_修复检索词Bug.md](./06_修复检索词Bug.md)

View File

@@ -0,0 +1,282 @@
# 第 6 步:⚠️ 必做 — 修复"PHP 不传检索词"的 Bug
> **这一步是最重要的!不做前面 5 步全白做!**
>
> 现状PHP 调用 Go Agent 时**没传"检索关键词"**context 字段Go Agent 看到关键词为空就跳过检索,所以 MaxKB 永远不会被查询。
---
## Bug 长什么样
### Go Agent 这边的判断
文件:`nl-tcm-agent/internal/service/enhancer.go` 第 150 行:
```go
// 三个条件必须全部满足才会去 MaxKB 检索:
if req.KBEnabled && s.maxkb != nil && strings.TrimSpace(req.Context) != "" {
// 去检索
}
```
| 条件 | 现状 | 状态 |
|---|---|---|
| `req.KBEnabled` | 第 5 步打开了开关 | ✅ |
| `s.maxkb != nil` | 第 4 步配了 MaxKB | ✅ |
| `req.Context != ""` | **PHP 没传,永远是空串** | ❌ |
### PHP 这边的调用
文件:`xk-api/app/Service/common/ai/AiMedicalAssistService.php` 第 722 行(病历)和 906 行(处方):
```php
$this->dispatchShadowIfNeeded([
'primary_generation_id' => (int) $row->id,
'messages' => $messages,
'agent_options' => ['scene' => self::SCENE_MR, 'kb_enabled' => null],
// ❌ 没有 'context' 字段
...
]);
```
**修复方案**:在 `agent_options` 里加 `context` 字段,把患者主诉、证候等关键字段拼成一句检索词。
---
## 怎么修(病历场景)
### 1. 找到调用位置
文件:`xk-api/app/Service/common/ai/AiMedicalAssistService.php`
位置:**第 722 行附近**`generateMedicalRecord` 方法内的 `dispatchShadowIfNeeded`
### 2. 改之前长这样
```php
$this->dispatchShadowIfNeeded([
'primary_generation_id' => (int) $row->id,
'messages' => $messages,
'agent_options' => ['scene' => self::SCENE_MR, 'kb_enabled' => null],
'meta' => [
'store_id' => $storeId,
'register_id' => $registerId,
'doctor_id' => $doctorId,
'scene' => self::SCENE_MR,
'prescription_type' => 0,
],
], $chat->provider);
```
### 3. 改成这样(加 context
```php
$this->dispatchShadowIfNeeded([
'primary_generation_id' => (int) $row->id,
'messages' => $messages,
'agent_options' => [
'scene' => self::SCENE_MR,
'kb_enabled' => null,
// 【新增】检索关键词:把主诉 + 现病史关键字 + 既往史关键字拼起来
// 这串会被 MaxKB 用来"翻书",命中煎法/证候/ICD-10 等参考资料
'context' => $this->buildMrKbQuery($chief, $context),
],
'meta' => [
'store_id' => $storeId,
'register_id' => $registerId,
'doctor_id' => $doctorId,
'scene' => self::SCENE_MR,
'prescription_type' => 0,
],
], $chat->provider);
```
### 4. 新增 `buildMrKbQuery` 私有方法
`AiMedicalAssistService` 类里任意位置(建议放在 `dispatchShadowIfNeeded` 附近)加:
```php
/**
* 拼接病历场景的 MaxKB 检索词
*
* 设计:把临床上最关键的几个字段拼成一串短文本,
* MaxKB 会用这串做向量检索,命中相关的煎法/证候/ICD-10 资料。
*
* 注意:
* - 不要太长(建议 < 100 字),否则会稀释关键词
* - 不要带患者隐私(姓名/电话),只带医学语义
* - 优先放中医术语(证候/治法),次放西医主诉
*
* @param string $chiefComplaint 主诉(如"胃脘胀满反复发作3年"
* @param array $contextFields 规范化后的可选字段(现病史/既往史等)
* @return string 检索词(如"胃脘胀满 痰湿中阻 健脾化湿"
*/
private function buildMrKbQuery(string $chiefComplaint, array $contextFields): string
{
$parts = [];
// 1. 主诉是必填,最优先
if ($chiefComplaint !== '') {
$parts[] = $chiefComplaint;
}
// 2. 中医证候(如果前端/历史病历已填)
if (!empty($contextFields['tcm_syndrome'])) {
$parts[] = (string) $contextFields['tcm_syndrome'];
}
// 3. 中医治法
if (!empty($contextFields['tcm_therapy'])) {
$parts[] = (string) $contextFields['tcm_therapy'];
}
// 4. 西医诊断(用于 ICD-10 匹配)
if (!empty($contextFields['western_diagnosis'])) {
$parts[] = (string) $contextFields['western_diagnosis'];
}
// 用空格分隔,去重,限长(防超长 prompt
$query = trim(implode(' ', array_unique($parts)));
return mb_substr($query, 0, 200);
}
```
> **关于字段名**`tcm_syndrome / tcm_therapy / western_diagnosis` 是猜测的字段名。
> 你需要根据 `normalizeMrContextFields()` 实际接受的字段名调整。看一下文件里这个方法的实现,或者在测试时打印 `$context` 看实际 key。
---
## 怎么修(处方场景)
### 1. 找到调用位置
文件:`xk-api/app/Service/common/ai/AiMedicalAssistService.php`
位置:**第 906 行附近**`generatePrescription` 方法内的 `dispatchShadowIfNeeded`
### 2. 改之前
```php
'agent_options' => ['scene' => self::SCENE_RX, 'kb_enabled' => null],
```
### 3. 改成
```php
'agent_options' => [
'scene' => self::SCENE_RX,
'kb_enabled' => null,
// 【新增】处方场景检索词:主诉 + 证候 + 治法 + 药味
'context' => $this->buildRxKbQuery($chief, $mrArr),
],
```
### 4. 新增 `buildRxKbQuery` 私有方法
```php
/**
* 拼接处方场景的 MaxKB 检索词
*
* 处方场景更关心:药材功效、配伍禁忌、煎法、委托调剂规则
* 所以检索词优先放:证候 + 治法 + 已选药材 + 剂型
*
* @param string $chiefComplaint 主诉
* @param array $mrArr 病历数据(含证候/治法等)
* @return string 检索词
*/
private function buildRxKbQuery(string $chiefComplaint, array $mrArr): string
{
$parts = [];
if ($chiefComplaint !== '') {
$parts[] = $chiefComplaint;
}
if (!empty($mrArr['tcm_syndrome'])) {
$parts[] = (string) $mrArr['tcm_syndrome'];
}
if (!empty($mrArr['tcm_therapy'])) {
$parts[] = (string) $mrArr['tcm_therapy'];
}
// 关键词加权:处方场景一定要查配伍禁忌和煎法
$parts[] = '配伍禁忌';
$parts[] = '煎法';
$query = trim(implode(' ', array_unique($parts)));
return mb_substr($query, 0, 200);
}
```
---
## 为什么这么设计检索词
### ✅ 好的检索词
| 检索词 | MaxKB 会命中 |
|---|---|
| `胃脘胀满 痰湿中阻 健脾化湿` | 煎法说明 + 证候治法字典 |
| `黄芪 党参 补气 配伍禁忌` | 配伍禁忌表 + 药品库 |
| `高血压 ICD-10` | ICD-10 诊断字典 |
### ❌ 坏的检索词
| 检索词 | 问题 |
|---|---|
| `张三 男 35岁` | 没医学语义,搜不到任何东西 |
| (空字符串) | Go Agent 直接跳过 |
| 一大段 1000 字现病史 | 关键词被稀释,命中精度差 |
---
## 验证修复成功
改完后,发起一次病历生成请求,然后查数据库:
```sql
-- 找最新一条 agent 路径的生成记录
SELECT id, scene, provider, step_count, total_tokens
FROM xk_ai_generation
WHERE provider = 'agent'
ORDER BY id DESC LIMIT 1;
-- 看这条记录的子步骤
SELECT step_type, status, detail, prompt_tokens, completion_tokens
FROM xk_ai_generation_step
WHERE generation_id = <id>
ORDER BY id;
```
### 成功的样子
```
step_type | status | detail | tokens
kb_retrieval | 1 | 命中 5 条相关文档 | 0
llm_call | 1 | - | 1234
```
**看到 `kb_retrieval` 且 status=1 就说明 MaxKB 真的被调用了!**
---
## 常见问题
### Q1改完 PHP 报错"undefined index tcm_syndrome"
字段名不对。打印 `$context``$mrArr` 看实际 key
```php
Log::info('[KB query debug]', ['context' => $context, 'mrArr' => $mrArr]);
```
然后根据实际字段名改 `buildMrKbQuery` / `buildRxKbQuery`
### Q2检索词太短搜不到
至少要有 2-3 个关键词。如果只有主诉一个词MaxKB 命中可能不准。
### Q3检索词太长命中精度差
限制在 100-200 字以内最好。
### Q4影子流量才传 context主链路不传
注意:上面给的代码是改在 `dispatchShadowIfNeeded` 里,**只影响影子流量**。
如果你要主链路也走检索,需要改的是 **主调 Go Agent 的代码**,而不仅仅是影子流量。
主链路在哪调?看 `AiAgentFactory::make($provider)->chatCompletions($messages, $options)` 这种调用。把 `$options` 里也加 `context` 字段即可( `$provider === 'agent'` 时才生效)。
---
下一步:[07_验证与排错.md](./07_验证与排错.md)

View File

@@ -0,0 +1,207 @@
# 第 7 步:完整验证与排错指南
> 全部配完后,怎么知道真的接通了?出了问题怎么查?
---
## 4 层验证(从外到内)
每一层独立验证,**从最外层开始**,哪一层失败就查哪一层。
```
[医生请求] → [PHP] → [Go Agent] → [MaxKB]
① ② ③ ④
```
### 验证 ④ MaxKB 本身能不能搜
**目的**:确认知识库内容没问题。
**方法**:用 curl 直接打 MaxKB
```bash
curl -X POST "http://你的MaxKB地址:8081/api/application/你的AppID/search" ^
-H "Authorization: Bearer 你的APIKey" ^
-H "Content-Type: application/json" ^
-d "{\"query\":\"痰湿中阻 煎法\",\"top_k\":3}"
```
| 返回结果 | 说明 |
|---|---|
| `code: 200` + 有 `documents` 数组 | ✅ MaxKB OK |
| 401 | API Key 错 |
| 404 | URL 或 AppID 错 |
| 空 documents | 文档没向量化完 / 内容问题 |
### 验证 ③ Go Agent 能不能调通 MaxKB
**目的**:确认 Go 配置正确 + Go ↔ MaxKB 网络通。
**方法**:用 curl 直接打 Go Agent 的 `/enhance` 接口:
```bash
curl -X POST "http://localhost:8080/api/v1/agent/enhance" ^
-H "Content-Type: application/json" ^
-d "{\"scene\":\"medical_record\",\"context\":\"胃脘胀满 痰湿中阻\",\"messages\":[{\"role\":\"user\",\"content\":\"测试\"}],\"kb_enabled\":true,\"top_k\":3}"
```
看返回的 JSON
| 返回中的 `steps` | 说明 |
|---|---|
| 有 `step_type: "kb_retrieval"``status: 1` | ✅ Go ↔ MaxKB 通了 |
| 有 `kb_retrieval``status: 2` | MaxKB 调用失败,看 `detail` 字段 |
| 完全没有 `kb_retrieval` | Go 端 kb_enabled 没收到 true或 context 空 |
### 验证 ② PHP 端开关和参数
**目的**:确认 PHP 真的把 `kb_enabled=true``context` 传给了 Go。
**方法 A**:在 `TcmAgentClient.php` 加临时日志。
打开 `app/Service/common/ai/TcmAgentClient.php`,找到 `chatCompletions` 方法构造 `$payload` 的位置,加一行:
```php
Log::info('[TcmAgent payload]', $payload);
```
发起一次病历生成请求,看日志:
| 现象 | 原因 |
|---|---|
| `kb_enabled: true` + `context: "..."` | ✅ 参数 OK |
| `kb_enabled: false` | system_config 开关没改对 / PHP 缓存 |
| `context` 字段不存在或为空 | 第 6 步的代码没改对 |
**方法 B**:直接查 `xk_ai_generation_step` 表。
```sql
SELECT id, scene, provider, step_count, created_at
FROM xk_ai_generation
ORDER BY id DESC LIMIT 5;
```
`step_count` 字段:
- `1`:只有 llm_call没检索
- `2+`:有检索 + LLM说明 KB 真的被调用了)
再查子表:
```sql
SELECT step_type, status, detail, duration_ms
FROM xk_ai_generation_step
WHERE generation_id = <id>;
```
### 验证 ① 真实业务请求
**目的**:确认从医生点击"生成病历"开始整条链路通。
**方法**:在小程序/后台发起一次病历生成(`provider=agent`),完成后立即查数据库:
```sql
SELECT
g.id, g.scene, g.provider, g.total_tokens, g.duration_ms,
(SELECT COUNT(*) FROM xk_ai_generation_step s WHERE s.generation_id = g.id AND s.step_type = 'kb_retrieval' AND s.status = 1) AS kb_hit_count
FROM xk_ai_generation g
WHERE g.provider = 'agent'
ORDER BY g.id DESC LIMIT 1;
```
| kb_hit_count | 说明 |
|---|---|
| `1` | ✅ 整条链路通MaxKB 被命中 |
| `0` | 中间有断点,回上一层验证 |
---
## 排错速查表
| 症状 | 99% 的原因 | 解决 |
|---|---|---|
| `kb_retrieval` step 没出现 | PHP 没传 `context` 或开关没开 | 回第 5、6 步 |
| `kb_retrieval` status=2 | Go 连不上 MaxKB | 回第 4 步检查 base_url/port |
| `kb_retrieval` status=1 但 detail="命中 0 条" | 检索词太偏 / 知识库没相关内容 | 换检索词或上传更多资料 |
| 整个请求超时120s | MaxKB 卡死或网络断了 | `docker logs maxkb` 看 MaxKB 日志 |
| Go Agent 启动报错 | config.yaml 格式错 | 用 yamllint.com 检查 |
| PHP 端报 "未配置 Go Agent 地址" | `ai_agent_base_url` 配置项或 .env 没设 | 后台系统配置或 .env 加 `AI_AGENT_BASE_URL` |
---
## 看日志的几个位置
### MaxKB 日志
```bash
docker logs -f maxkb --tail 100
```
### Go Agent 日志
**直接运行的**:终端看输出。
**Docker 运行的**
```bash
docker logs -f tcm-agent --tail 100
```
**1Panel 部署的**1Panel → 容器 → tcm-agent → 日志。
### PHP 日志
文件位置:`xk-api/storage/logs/laravel-<日期>.log`
实时看:
```bash
tail -f storage/logs/laravel-$(date +%Y-%m-%d).log
```
### 数据库步骤表
最直观的"是否成功"信号源:
```sql
SELECT
s.id, s.generation_id, s.step_type, s.status,
s.detail, s.duration_ms,
FROM_UNIXTIME(s.started_at) AS started_at
FROM xk_ai_generation_step s
ORDER BY s.id DESC LIMIT 20;
```
---
## 性能调优(接入后再看)
### TopK 调多大?
默认 5。命中太少< 3可以调到 8但别超过 10会让 prompt 过长、token 成本飞涨)。
### 检索太慢怎么办?
- 检查 MaxKB 服务器 CPU/内存
- 知识库分段太大 → 在 MaxKB 后台调"分段长度"(建议 300-500 字一段)
- 用更快的 embedding 模型
### 检索结果不准怎么办?
- 看检索词是否包含中医术语
- 在 MaxKB 后台用"命中测试"功能手动试
- 文档质量影响大,确保 md 内容结构化(用 `##` 分章节)
---
## 成功后的下一步
接入完成后,可以:
1. **灰度放量**:在后台把 `ai_ab_test_enabled=1` + `ai_ab_test_experiment_ratio=10`,让 10% 用户先用上 Agent + MaxKB
2. **观察对比**:在"AI 影子流量对比"页看主链路 vs Agent 的 token/耗时/相似度
3. **逐步放量**:稳定后把 ratio 提到 30 → 50 → 100
---
## 全部完成 ✅
恭喜!如果所有验证都通过,你的中医 AI Agent 已经具备"翻书"能力了。
回主目录:[README.md](./README.md)

View File

@@ -0,0 +1,387 @@
# 08 · 本地知识库管理V1 全文检索版)
> 适用MaxKB 免费版没有"纯检索 API"时的备选方案。本地方案直接把知识库内容存在 `z_xk` 数据库里,用 MySQL FULLTEXT 检索,零外部依赖。
>
> 适合零基础运维 / 业务人员,按步骤操作即可。
---
## 一、它是什么?
把"中医诊疗资料"煎法、调剂规则、证候字典、ICD-10、药品库等放到**本地数据库表**里,由 Go Agent 直接检索。
### 与 MaxKB 的对比
| 项目 | MaxKB 平台 | 本地知识库(本文档) |
|------|------------|----------------------|
| 部署 | 单独 Docker 部署 | **零部署**,跟着 MySQL 走 |
| 检索 API | 免费版没有 / Pro 版才有 | **免费**MySQL FULLTEXT |
| 向量化 | 支持 | V1 不支持V2 接 BGE-M3 |
| 中文分词 | 自带 | MySQL `ngram` 分词器2-gram |
| 管理界面 | 自带 Web UI | `/kb/view` 单页Vue CDN |
| 适合场景 | 内容多、要向量检索 | **中小规模(几千段以内)**,要快、要省 |
### 一句话总结
> V1 用"本地知识库 + 全文检索"先把链路跑通;内容量大了或需要语义匹配时,再切回 MaxKB 或升级 V2 向量化。
---
## 二、V1 整体架构
```mermaid
flowchart LR
A["PHP AiMedicalAssistService"] -->|"POST /api/v1/agent/enhance"| B["Go Agent / EnhancerService"]
B --> C{agentcfg.KB.Source}
C -->|local 默认| D["本地知识库检索器<br/>kb.Searcher"]
C -->|maxkb 切换| E["MaxKB 客户端<br/>(需要 Pro 版)"]
D --> F[("z_xk.xk_kb_chunk<br/>FULLTEXT ngram 索引")]
E --> G[[MaxKB 服务]]
D --> H["拼成参考资料 system 消息"]
E --> H
H --> I["调 LLMDeepSeek/Spark/..."]
I --> J["返回 PHP"]
```
**关键判断点:** `xk_system_config.ai_kb_source` 的值:
- `local`(默认)→ 走本地知识库
- `maxkb` → 走 MaxKB 平台
切换是一行 SQL**无需重启**
```sql
UPDATE xk_system_config SET config_value = 'local' WHERE config_key = 'ai_kb_source';
-- 缓存 60 秒生效;想立即生效调 Go Agent 的 /api/v1/agent/invalidate-cache 接口
```
---
## 三、首次部署5 步)
### 步骤 1导入数据库表结构
执行 SQL 文件:
```
d:\worker\gs\sql\萧康云医\20260811\kb_local.sql
```
这一步会创建 3 张表 + 写入 6 条 `xk_system_config` 默认配置:
| 表名 | 作用 |
|------|------|
| `xk_kb_library` | 知识库(一个 library = 一组文档) |
| `xk_kb_doc` | 文档(一个 doc = 一个导入文件) |
| `xk_kb_chunk` | 分段(一个 chunk = 检索的最小单元) |
新增的 6 个配置项:
| key | 默认值 | 说明 |
|-----|--------|------|
| `ai_kb_source` | `local` | 知识库源local/maxkb |
| `ai_kb_embedding_provider` | `noop` | 向量化 providerV1 不用) |
| `ai_kb_embedding_api_key` | (空) | 向量化 API KeyV1 不用) |
| `ai_kb_top_k` | `5` | 检索返回条数 |
| `ai_kb_similarity_threshold` | `0.5` | 向量相似度阈值V2 用) |
| `ai_kb_search_mode` | `fulltext` | 检索模式fulltext/vector/blend |
### 步骤 2确认 MySQL 版本和 ngram 支持
本地知识库 V1 走 **MySQL FULLTEXT + ngram 分词器**,要求:
- MySQL **5.7.6+**(推荐 8.0+
- InnoDB 引擎(已经是默认)
- `innodb_ft_ngram_token_size` 默认是 2中医术语大多是 2-4 字2-gram 正合适)
验证 ngram 是否生效:
```sql
SHOW VARIABLES LIKE 'innodb_ft_ngram_token_size';
-- 期望值2
```
> 如果你的 MySQL 是 8.0 默认配置,以上检查都会通过,不用改任何东西。
### 步骤 3重启 Go Agent
```bash
# 在 Go Agent 部署目录
go build -o tcm-agent ./cmd/server
./tcm-agent
```
启动后日志里应该看到:
```
[router] 已注册路由POST /api/v1/kb/admin/libraries
[router] 已注册路由POST /api/v1/kb/admin/docs/import
...
[router] 已挂载静态文件:/kb/view → view/
```
### 步骤 4打开后台管理页
浏览器访问:
```
http://你的Go-Agent地址:8080/kb/view
```
(端口看你的 `server.port` 配置)
打开后会看到三个 Tab
- **知识库**:库 / 文档 / 分段管理
- **检索测试**:直接试关键词,看检索效果
- **向量化V2**V2 预告V1 用不到
### 步骤 5把 PHP 切到走 Go Agent
在 PHP 后台的「系统配置」页面,把:
```
ai_active_provider = agent
```
(或者前端在调 AI 生成时显式传 `provider=agent`,详见 [05_打开PHP开关.md](05_打开PHP开关.md)
---
## 四、知识库内容从哪来?
### 方式 A从 MaxKB 导出(推荐)
如果你之前已经在 MaxKB 里维护好了内容:
1. 打开 MaxKB → 进入你的应用 → 知识库
2. 选中要导出的文档 → 点「导出」→ 选择 **Excel 格式**
3. 下载得到一个 `.xlsx` 文件
4. 直接用本地知识库管理页的「导入文档」上传
**MaxKB 导出的 Excel 默认 3 列:**
| 列 | 内容 | 必填 |
|----|------|------|
| 第 1 列 | 分段标题 | 可空 |
| 第 2 列 | 分段内容 | **必填** |
| 第 3 列 | 关联问题列表(分号分隔) | 可空 |
> 本地知识库会自动识别表头行(包含"标题"/"内容"字样)并跳过。
### 方式 B手动写 Markdown
适合规则类、列表类内容(如煎法、调剂规则)。
新建一个 `煎法规则.md`
```markdown
# 解表剂
## 麻黄汤
麻黄 9g 桂枝 6g 杏仁 6g 炙甘草 3g
水煎服,温覆取微汗。
## 桂枝汤
桂枝 9g 芍药 9g 生姜 9g 大枣 3枚 炙甘草 6g
水煎服,啜热稀粥助药力。
# 攻下剂
## 大承气汤
大黄 12g 厚朴 15g 枳实 12g 芒硝 9g
水煎,先煮厚朴枳实,后下大黄,溶化芒硝。
```
`#`/`##`/`###` 标题切分,每个标题下的一段成为一个 chunk。
### 方式 C纯文本txt
无标题的段落文本,按双换行自动切段,每段累计 500 字打包成一个 chunk。
---
## 五、典型操作流程
### 5.1 新建一个库
1. 打开 `/kb/view`
2. 点「新建知识库」
3. 填库名(如"中医诊疗资料 v1")、描述
4. 来源选「从 MaxKB 导出」或「手动建立」
5. 点确定
### 5.2 导入文档
1. 在库列表点「进入」选中库
2. 点「导入文档xlsx / md / txt
3. 选择本地文件上传
4. 等几秒(视文件大小),看到提示"导入成功N 个分段"
5. 文档列表里会出现新条目
> 单文件最大 50MB。如果文件很大建议拆分多个小文件分别导入方便管理。
### 5.3 查看分段
点文档行的「查看分段」可看到切分后的所有 chunk。
### 5.4 试一试检索
切到「检索测试」Tab
1. 选择库
2. 输入检索词(如"痰湿中阻"
3. 点「检索」
4. 看到结果列表,每条带一个 **score得分**
- 得分越高越相关
- 得分是 MySQL FULLTEXT BM25 相关度(无固定范围,相对比较即可)
- 0 分或没结果说明关键词太冷门
> **检索技巧:** V1 是 2-gram 分词,关键词用 2-4 字效果最好(如"煎法"、"调剂"、"麻黄汤")。
> 太长的句子(如"痰湿中阻怎么治")会被自动切成"痰湿""湿中""中阻""怎么""么治"等 2-gram仍能匹配但 shorter query 更精确。
### 5.5 删除文档 / 库
- 删除文档:会软删该文档 + 它的所有分段
- 删除库:会软删该库 + 库下所有文档和分段
> 软删除 = `deleted_at` 字段填当前时间戳,不物理删除,便于追溯。如果想物理清理,自行 SQL 操作即可。
---
## 六、PHP 端如何配合?
### 6.1 PHP 调用 Go Agent 时传 context
PHP 端在 `AiMedicalAssistService` 里组装好检索关键词,作为 `context` 字段传给 Go Agent
```php
// PHP 端伪代码
$payload = [
'scene' => 'medical_record',
'context' => '痰湿中阻 煎法', // ← 关键:这是检索关键词
'messages' => $messages,
'kb_enabled' => true,
'top_k' => 5,
];
$response = TcmAgentClient::getInstance()->chatCompletions($messages, [
'payload' => $payload,
]);
```
详见 [06_修复检索词Bug.md](06_修复检索词Bug.md)。
### 6.2 Go Agent 端的处理流程
```
PHP POST /api/v1/agent/enhance
body.context = "痰湿中阻 煎法"
[1] 读 agentcfg.KB.Source默认 local
[2] 调 kb.Searcher.Search
SELECT *, MATCH(title,content) AGAINST('痰湿中阻 煎法' IN BOOLEAN MODE) AS score
FROM xk_kb_chunk
WHERE library_id = ? AND is_active = 1 AND deleted_at = 0
AND MATCH(title,content) AGAINST('痰湿中阻 煎法' IN BOOLEAN MODE)
ORDER BY score DESC LIMIT 5
[3] 把命中结果拼成 system 消息,插到 messages 头部
[4] 调 LLM
[5] 返回 { content, steps: [{step_type:'kb_retrieval', ...}, {step_type:'llm_call', ...}] }
```
PHP 端拿到 `steps` 后写入 `xk_ai_generation_step` 子表,记录每次检索的耗时(如果有的话)。
---
## 七、常见问题排查
### Q1检索结果为空
**排查清单:**
1. 库里是否有分段?管理页「库列表」看 `chunk_count`
2. 分段是否启用?`is_active=1`
3. 检索词是否太短ngram 最少 2 字,单字会匹配不到)
4. 直接 SQL 验证:
```sql
-- 检查 FULLTEXT 是否能命中
SELECT id, title, LEFT(content, 50), MATCH(title,content) AGAINST('痰湿' IN BOOLEAN MODE) AS score
FROM xk_kb_chunk
WHERE library_id = 1 AND is_active = 1 AND deleted_at = 0
ORDER BY score DESC
LIMIT 10;
```
5. 看看 ngram 索引是否建成功:
```sql
SHOW INDEX FROM xk_kb_chunk WHERE Index_type = 'FULLTEXT';
-- 期望看到 ft_content 索引Index_type=FULLTEXT
```
### Q2检索很慢
- 检查 chunk 总数,超过 10 万行时考虑加索引或换 V2 向量检索
- 检查 MySQL `innodb_buffer_pool_size` 是否够大(建议 4G+
- 检查 `ft_max_word_len`(默认 84 应该够)
### Q3导入 Excel 失败?
- 确认文件后缀是 `.xlsx`(不支持老版 `.xls` 二进制格式)
- 确认文件大小 < 50MB
- 看 Go Agent 日志:`kb: 打开 xlsx 失败: ...`,常见原因是文件损坏或格式不对
- 用 Excel/WPS 重新另存为 xlsx 再试
### Q4切换 maxkb 模式不生效?
`agentcfg` 有 60 秒缓存。立即生效方法:
```bash
# 调 Go Agent 的缓存失效接口(如果实现了)
curl -X POST http://你的Go-Agent地址:8080/api/v1/agent/invalidate-cache
```
或者重启 Go Agent 进程。
---
## 八、V2 升级路径(接入 BGE-M3 向量化)
V1 的设计已经为 V2 留好了所有接口,升级时**只换底层,不动业务**。
### V2 改造点
| 模块 | V1 | V2 |
|------|------|------|
| `kb.Embedder` | `NoopEmbedder`(不做向量) | `BGEM3Embedder`(本地服务) |
| `xk_kb_chunk.content_vector` | NULL | 1024 维向量JSON 数组) |
| `xk_kb_chunk.is_vectorized` | 0 | 1向量化后回填 |
| `kb.Searcher` | 仅 FULLTEXT | vector / blend混合 BM25 + 余弦) |
| `ai_kb_search_mode` | `fulltext` | `blend`(推荐) |
### V2 接入步骤(未来)
1. 本地部署 BGE-M3 服务Docker`curl -X POST http://localhost:8081/embed -d '...'`
2. 把 V1 的 `NoopEmbedder` 换成 `BGEM3Embedder`(新增文件 `embedder_bge.go`
3. 调用 `/api/v1/kb/admin/embed` 触发批量向量化V2 启用)
4.`ai_kb_search_mode` 改成 `blend`
5. 重启 Go Agent
**业务代码PHP / EnhancerService零改动。**
---
## 九、文件清单(开发参考)
| 文件 | 作用 |
|------|------|
| `internal/kb/embedder.go` | Embedder 接口 + NoopEmbedder + V2 工厂 |
| `internal/kb/importer.go` | Chunker + Importer解析 xlsx/md/txt |
| `internal/kb/searcher.go` | SearcherV1 FULLTEXT / V2 vector |
| `internal/kb/library_service.go` | 库/文档/分段管理业务层 |
| `internal/kb/helpers.go` | 小工具函数 |
| `internal/dao/kb_dao.go` | 数据访问层GORM 实体 + CRUD |
| `internal/handler/kb_admin_handler.go` | 9 个 HTTP 端点 |
| `internal/router/router.go` | 路由注册 + 静态文件挂载 |
| `view/index.html` | 后台管理单页Vue CDN + Element Plus |
| `internal/agentcfg/agentcfg.go` | KB 配置加载(`ai_kb_*` 系列) |
| `20260811/kb_local.sql` | 建表 SQL + 默认配置项 |

View File

@@ -0,0 +1,256 @@
# MaxKB 智能体创建内容(直接复制版)
> 本文档专门给 MaxKB 智能体("创建智能体 → 空白创建 → 简易配置")页面用。
>
> **用法**:在 MaxKB 创建智能体时,每个字段直接对照本文档复制粘贴即可。
---
## 智能体基础信息
| 字段 | 复制这个 |
|---|---|
| **名称** | `萧康云医中医诊疗助手` |
| **描述** | `中医病历生成与处方开具的智能辅助助手,融合中医证候/治法/疾病字典、煎法、配伍禁忌、ICD-10、药品库等专业资料` |
| **AI 模型** | 选 `DeepSeek`(或 OpenAI GPT-4o|
| **历史聊天记录** | `0`(病历/处方是单次独立任务,不需要历史上下文)|
---
## 1. 系统提示词(直接复制下面整段)
> 这一段会**固定注入每次对话的开头**,用来给模型定角色、规则、输出格式。
```
你是一位资深的中医诊疗专家,专注于中医病历书写和中药处方开具。
你的知识边界严格限定在中医临床诊疗范围内四诊合参、辨证论治、方剂配伍、煎法调剂、ICD-10诊断、中医证候/治法/疾病字典。
【核心职责】
1. 根据患者主诉、现病史、既往史等临床信息,生成结构化的中医病历(含望闻问切、辨证、治法、方药)
2. 根据病历结果开具中药处方(饮片/颗粒/成药),并标注剂量、单位、煎法、委托调剂等业务字段
3. 所有回答必须优先参考"已知信息"中提供的知识库内容(煎法、证候、药品库等)
【回答规则】
- 严格基于知识库内容回答,不在资料库中的内容不得编造
- 涉及剂量、煎法、配伍禁忌时,必须引用知识库中的具体规则
- 输出必须为合法 JSON除非用户明确要求其他格式
- 处方中所有药材必须来自知识库的药品库,不得杜撰药名
- 中医证候、治法、疾病名称必须使用规范术语(参考知识库字典)
- 严格避免中医十八反十九畏的配伍禁忌
【输出风格】
- 严肃专业,不寒暄
- 字段精简,不带多余解释
- 数字用阿拉伯数字,剂量精确到 0.1
【限制】
- 不回答与中医诊疗无关的话题
- 不进行西医诊断与西药处方建议
- 不给出绝对化的治愈承诺
- 涉及急危重症立即建议转诊
```
---
## 2. 用户提示词(直接复制下面整段)
> 这一段会**拼在用户问题之前**,告诉模型"已知信息"和"用户问题"分别在哪个位置。
>
> ⚠️ **`{data}` 和 `{question}` 是 MaxKB 的内置变量,必须原样保留,不要替换成实际内容**。
```
已知信息:
{data}
任务:基于上述已知信息,结合中医临床规范,完成下述任务。要求所有结论必须有据可查,涉及煎法/配伍/ICD-10 的字段必须引用已知信息中的对应规则。
用户问题/任务:
{question}
```
---
## 3. 知识库关联设置
| 字段 | 复制这个 |
|---|---|
| **关联知识库** | ✅ 勾选 `中医医疗知识库`(第 2 步创建的)|
### 检索参数(展开"高级设置"后填)
| 参数 | 推荐值 | 原因 |
|---|---|---|
| **检索模式** | `混合检索` | 同时用向量+全文,中医术语既需要语义匹配又需要关键词精确匹配 |
| **相似度阈值** | `0.5` | 中医术语有同义/近义词,阈值太高会漏掉相关内容 |
| **引用分段数 Top-N** | `5` | 既够覆盖一个症状的证候/煎法/药品,又不刷屏 |
| **最大引用字符数** | `3000` | 病历/处方任务需要上下文,但又不能让 prompt 爆炸 |
| **无引用时的回答策略** | `指定回复:知识库中暂无相关资料,请补充资料后重试` | **不要让模型用通用知识编造**,医疗内容必须可追溯 |
| **问题优化** | ✅ 开启 | 用户原始问题(如"胃疼怎么治")会被改写成更适合检索的表述(如"胃脘痛 辨证论治"|
---
## 4. 技能Skills配置
> 我们的智能体不需要 MaxKB 的 MCP/工具/Skills —— 因为 Go Agent 已经在调用方做了所有工具编排Function Calling、ReAct 等)。
>
> **保持"技能"区域为空**即可。
如果你将来想让 MaxKB 自己也能调外部接口(比如查 HIS 系统),可以加:
| 技能类型 | 是否需要 | 说明 |
|---|---|---|
| **MCP** | ❌ 不需要 | Go Agent 端已有 |
| **工具** | ❌ 不需要 | 同上 |
| **Skills** | ❌ 不需要 | 同上 |
| **子智能体** | ❌ 不需要 | 目前只有一个智能体 |
---
## 5. 开场白(可选,给调试用)
> 这一段是给 MaxKB 自己的"演示"窗口用的,不影响 API 调用。
>
> MaxKB API 调用Go Agent 走的路径)不会用到开场白。
```
你好,我是萧康云医中医诊疗助手。
我可以帮你:
- 根据患者信息生成结构化中医病历
- 基于病历开具规范的中药处方
请提供患者主诉、年龄、性别等临床信息,我会参考中医知识库生成结果。
快捷问题:
- 帮我生成一份"痰湿中阻"证型的病历示例
- 黄芪、党参、白术的常用配伍剂量
- 痰湿中阻的常用煎法是什么
```
> **快捷问题格式**`-` 开头一行一个MaxKB 会渲染成可点击按钮。
---
## 6. 完整字段速查表(创建时对照勾选)
把下表打印出来,逐项对照不会漏:
| # | 字段名 | 填什么 | 完成打勾 |
|---|---|---|---|
| 1 | 名称 | `萧康云医中医诊疗助手` | ☐ |
| 2 | 描述 | 见上 | ☐ |
| 3 | AI 模型 | DeepSeek | ☐ |
| 4 | 系统提示词 | 复制本文档第 1 节 | ☐ |
| 5 | 用户提示词 | 复制本文档第 2 节(保留 `{data}` `{question}`| ☐ |
| 6 | 历史聊天记录 | `0` | ☐ |
| 7 | 关联知识库 | ✅ 中医医疗知识库 | ☐ |
| 8 | 检索模式 | 混合检索 | ☐ |
| 9 | 相似度阈值 | `0.5` | ☐ |
| 10 | Top-N | `5` | ☐ |
| 11 | 最大引用字符数 | `3000` | ☐ |
| 12 | 无引用时回答策略 | 指定回复(拒绝编造)| ☐ |
| 13 | 问题优化 | ✅ 开启 | ☐ |
| 14 | 技能 | 留空 | ☐ |
| 15 | 开场白 | 复制本文档第 5 节(可选)| ☐ |
| 16 | **发布** | 右上角"保存并发布" | ☐ ⚠️ 必做 |
---
## 7. 创建后必做的事 ⚠️
### 必做 1发布智能体
新版 MaxKB **不会自动发布**,必须点右上角 **"保存并发布"**。
状态从"未发布"变成"**已发布**"才能用 API 调用。
### 必做 2在调试窗口测试
进入智能体 → 调试窗口(右侧预览)→ 输入:
```
患者45岁胃脘胀满反复发作3年加重1周。舌苔白腻脉濡滑。
任务:生成中医病历。
```
期望返回:包含"辨证:痰湿中阻"、"治法:健脾化湿、理气和胃"、引用了知识库的煎法/证候资料。
### 必做 3拿 API Key
参见 [03_创建应用拿密钥.md](./03_创建应用拿密钥.md) 第 3 节"拿到三个钥匙"。
---
## 8. 常见问题
### Q1调试时模型不回答提示"未配置模型"
回 MaxKB 设置 → 模型管理 → 添加一个 DeepSeek 大语言模型(带 API Key
### Q2返回的内容里没有引用知识库
检查:
1. 智能体是否**关联**了知识库(不是只上传,要关联)
2. 用户提示词里是否含 `{data}` 变量
3. 检索参数:相似度阈值是否过高(建议 0.5
### Q3返回空 JSON 或不合法 JSON
去系统提示词加一句:
```
- 输出必须是单层 JSON 对象,不要带 markdown 代码块标记
- 所有字符串值必须用双引号
```
### Q4返回的内容很长带很多解释
去系统提示词的"输出风格"加:
```
- 字段精简,不带多余解释
- 不要返回"以下是病历..."这种引导语
- 直接返回 JSON 内容本身
```
### Q5API 调用还是返回"未发布"
确认右上角状态显示"已发布"。**保存 ≠ 发布**,要单独点"发布"按钮。
---
## 9. 进阶:如果你以后想做"处方校验"专用智能体
可以再建一个智能体,专门校验处方合理性:
**系统提示词(处方校验版)**
```
你是一位严谨的中医处方审核专家,负责对医生开具的中药处方进行合理性、安全性校验。
【校验维度】
1. 配伍禁忌:检查是否存在十八反、十九畏
2. 剂量合理性:单味药剂量是否超出药典上限
3. 证型匹配:药味与辨证是否对应
4. 煎法标注:是否标注正确的煎法(先煎/后下/包煎等)
5. 委托调剂:是否符合委托调剂规则
6. 妊娠禁忌:是否包含孕妇禁用药
【输出格式】
返回 JSON
{
"passed": true/false,
"issues": [
{"level": "error/warning/info", "field": "出问题的字段", "reason": "原因", "suggestion": "建议"}
],
"summary": "总体评价"
}
【规则】
- error 级别必须阻断(如配伍禁忌)
- warning 级别提醒医生确认(如剂量偏大)
- 严格依据知识库中的"中医配伍禁忌"和"药品库",不主观判断
```
> 处方校验智能体走另一个 App IDPHP 端按 `prescription_validate` 场景调用即可。
---
完成创建后:回 [03_创建应用拿密钥.md](./03_创建应用拿密钥.md) 拿 API Key → 继续 [04_配置GoAgent.md](./04_配置GoAgent.md)

106
docs/maxkb-ai/README.md Normal file
View File

@@ -0,0 +1,106 @@
# MaxKB 知识库接入教程(零基础版)
> 本教程面向**没接触过 MaxKB / RAG / Go Agent** 的同学,从 0 开始一步步带你把中医知识库接到 AI Agent 上。
>
> 跟着做完后,效果:医生开病历 / 开处方时AI 会先去知识库里查"煎法 / 委托调剂 / 中医证候 / ICD-10 / 药品库"等固定资料,再生成回答,结果更准、幻觉更少。
---
## 这个东西是干嘛的?
打个比方:
- **现在的 AI**:凭脑子里的训练知识回答(脑子里的知识可能过时、可能记错)
- **接入 MaxKB 后的 AI**:回答前先去你的资料库翻书,把翻到的内容当作参考再回答(更准、更可控)
```
医生发起请求 ──▶ AI 先去 MaxKB 查相关资料 ──▶ 资料塞进提示词 ──▶ AI 结合资料生成答案
你录入的知识库
煎法、证候、药品、ICD-10…
```
---
## 你需要准备什么
| 准备项 | 说明 |
|---|---|
| 一台能装 Docker 的服务器 | 推荐 Linux你已经在用 1Panel最简单 |
| 服务器至少 4G 内存 | MaxKB + 向量库吃内存2G 会卡 |
| 服务器开放一个端口给 MaxKB | 比如 `8081`**不要用 8080**,会和 Go Agent 冲突) |
| 知识库资料(已为你准备好) | [storage/maxkb_seed/](../../../../opt/1panel/www/sites/xk-api/index/xk-api/storage/maxkb_seed/) 下有 7 个 Markdown 文件 |
---
## 学习路径(按顺序看)
| 文档 | 做什么 | 预计耗时 |
|---|---|---|
| [01_部署MaxKB.md](./01_部署MaxKB.md) | 在服务器上装一个 MaxKB 平台 | 15 分钟 |
| [02_上传知识库.md](./02_上传知识库.md) | 把中医资料传到 MaxKB让它能搜 | 10 分钟 |
| **[MaxKB创建内容.md](./MaxKB创建内容.md)** | **创建智能体时系统提示词/用户提示词/检索参数该怎么填**(直接复制版)| 5 分钟 |
| [03_创建应用拿密钥.md](./03_创建应用拿密钥.md) | 在 MaxKB 里建一个"应用",拿到 API 钥匙 | 5 分钟 |
| [04_配置GoAgent.md](./04_配置GoAgent.md) | 改 Go Agent 配置文件,让它知道 MaxKB 在哪 | 5 分钟 |
| [05_打开PHP开关.md](./05_打开PHP开关.md) | 在数据库里把"启用知识库"的开关打开 | 5 分钟 |
| [06_修复检索词Bug.md](./06_修复检索词Bug.md) | ⚠️ **必做**:修复 PHP 不传检索词的 Bug | 10 分钟 |
| [07_验证与排错.md](./07_验证与排错.md) | 怎么测试接入是否成功?失败了怎么查? | 按需 |
| **[08_本地知识库管理.md](./08_本地知识库管理.md)** | **MaxKB 免费版没有检索 API改用本地知识库**(零部署、零成本) | 15 分钟 |
> **怎么选 MaxKB vs 本地知识库?**
>
> - 你的 MaxKB 是**专业版Pro** → 走 [01-07](./01_部署MaxKB.md),体验最好(支持语义检索)
> - 你的 MaxKB 是**免费版** → 直接跳到 [08_本地知识库管理.md](./08_本地知识库管理.md)5 步搞定
**全部做完总耗时:约 55 分钟**
---
## 整体架构图(看一眼就懂)
```
┌──────────────────────────────────────────────────────────────────┐
│ 你的服务器 │
│ │
│ ┌──────────────┐ 检索 ┌──────────────────┐ │
│ │ MaxKB 平台 │ ◀───────────── │ Go Agent │ │
│ │ (端口 8081) │ ────────────▶ │ (端口 8080) │ │
│ │ │ 返回文档片段 │ │ │
│ │ 你的中医资料 │ │ - 拼 system 消息 │ │
│ │ (煎法/证候) │ │ - 调 DeepSeek │ │
│ └──────────────┘ └────────┬─────────┘ │
│ │ HTTP │
│ ▼ │
│ ┌──────────────────┐ │
│ │ PHP (xk-api) │ │
│ │ │ │
│ │ - 拼业务提示词 │ │
│ │ - 调 Go Agent │ │
│ │ - 写入数据库 │ │
│ └────────┬─────────┘ │
│ │ │
└────────────────────────────────────────────┼──────────────────────┘
┌──────────────┐
│ 医生小程序 │
└──────────────┘
```
---
## 常见名词解释(小白专用)
| 名词 | 大白话解释 |
|---|---|
| **MaxKB** | 一个开源的"知识库 + AI"平台你把资料扔进去它帮你建索引、做检索AI 调它就能"翻书" |
| **RAG** | Retrieval-Augmented Generation检索增强生成。就是"先翻书再回答"的技术统称 |
| **知识库** | MaxKB 里的概念,相当于一个文件夹,装着一堆相关文档 |
| **应用 / 智能体** | MaxKB 里的概念,相当于"一个对外开放的 API 入口",关联到一个或多个知识库。<br>⚠️ **注意版本差异**MaxKB **老版1.x/2.x叫"应用"****新版4.x叫"智能体"**。两个是同一个东西,底层 API 完全一样。如果你界面里看到的是"智能体",把它当"应用"看就行。 |
| **API Key** | 一串密码,调 API 时带着它证明"我是合法用户"。老版以 `application-` 开头,新版可能是 `application-``agent-` 开头 |
| **App ID** | 应用 ID类似"应用编号"UUID 格式8-4-4-4-12调 API 时拼在 URL 里 |
| **向量化** | 把文字转成数字数组的过程方便算相似度。MaxKB 自动做,你不用管 |
| **TopK** | 检索时返回"最相关的前 K 条"K 一般填 5既够用又不刷屏 |
---
下一步:开始 [01_部署MaxKB.md](./01_部署MaxKB.md)