今天基本没再加新页面,主要是在收拾之前留下的问题。

这个项目的功能已经不少了,患者端能问诊,医生端能处理,管理端也能看数据。但真正准备提交时,还是发现了不少细节:医生已经点了完成,管理端还显示异常;接口数据一多就开始变慢;Docker 写好了却起不来;CI 虽然通过了,GitHub 还在提示 Action 版本过旧。

所以今天做的事情比较杂,但都绕着一个目标:让项目不只是我电脑上看起来能用。

先把导诊流程重新理了一遍

患者发送消息后,请求进入 /api/chat,后端会拿到当前患者和 sessionId,然后交给 TriageHarness。

现在的一次导诊大概会经过这些步骤:

患者消息和健康档案
  → 整理当前会话
  → 检索医疗知识
  → 主导诊和意图识别并行执行
  → 风险、科室、病史分析
  → 按情况追加用药或急症分析
  → Supervisor 汇总
  → 最多两轮 Review
  → 返回回答
  → 后台整理成结构化报告

Copy

最开始我也想过每次把所有 Agent 都跑一遍,但这样做没有必要,还会增加等待时间。

现在风险、科室和病史是默认节点。只有问题里确实出现药物、剂量、孕妇、儿童、胸痛、呼吸困难等内容时,才追加用药安全或急症节点。

专业 Agent 用 goroutine 并行执行,同时用一个容量为 5 的 channel 限制并发。每个专业节点最多运行 12 秒,Supervisor 最多 10 秒,整次导诊最多 45 秒。某个节点失败时只记录降级,不会直接把整个请求打断。

Review 也限制为两轮。它主要检查有没有漏掉急症提示、有没有把推测写成确诊、有没有给出不合适的用药内容。两轮之后就停止,不继续反复改答案。

RAG 这块没有硬说成向量数据库

项目里的医疗知识目前保存在 MySQL 中,每条记录带标题、正文、关键词、来源、版本、审核状态和启用状态。

查询时只读取已经审核并启用的内容:

Where("enabled = ? AND review_status = ?", true, "approved")

go

Copy

知识量现在不大,所以我没有为了简历强行接一套向量数据库。当前做法是关键词和规则打分:普通关键词命中加 4 分,急诊、120 这种比较宽泛的词只加 1 分。

这里还专门处理了否定表达。比如用户说“没有胸痛”,胸痛不能继续给急症知识加分。代码会检查关键词前面的“没有、否认、未出现、不伴、无”等内容,被否定的症状会被排除。

结果按分数排序,同一主题最多保留两条,最后取三条放进模型上下文。

每次命中的知识还会把来源、版本、匹配原因和检索时间保存到导诊记录里。这样以后知识库内容改了,打开旧报告时仍然能知道当时参考了什么。

如果没有检索到知识,风险评估和急症判断照常运行。我不希望因为知识库少一条内容,系统就跳过必要的安全提醒。

最麻烦的不是模型,是三个端的状态

今天修得比较久的是医生端、患者端和管理端之间的状态同步。

之前出现过一种情况:医生已经点击“确认处理完成”,医生端看着没问题,但管理端仍然显示异常升级,患者端也看不到医生的处理结果。

原因很直接:不同页面只改了自己那张表,没有把同一次导诊相关的数据一起处理。

现在导诊记录、Agent 轨迹、人工复核工单、预约和随访都用 sessionId 关联。医生完成处理后,后端会一起更新导诊状态、工单、异常随访和患者通知,同时保存处理人、处理时间和医生意见。

人工复核工单也限制了状态顺序:

pending → accepted → replied → closed

Copy

没接单不能回复,状态不对时后端直接返回 409。这比只把前端按钮禁用更稳,因为接口本身也不会接受错误操作。

目前有些跨表更新还是顺序执行,并没有全部放进事务。个人项目阶段先用状态校正和验收脚本兜住,后面如果继续往正式部署方向做,这部分要补事务,通知类操作可以再考虑 Outbox。

接口慢了以后,开始补性能账

功能刚做出来的时候数据少,很多问题看不出来。后面报告、随访和 Agent 轨迹越来越多,管理端开始明显变慢。

排查下来,主要是几个老问题:同一个患者档案在列表里被反复查询;仪表盘每个数字都单独查一次;Agent 轨迹列表直接返回完整长文本;部分只读接口还会反复刷新随访状态。

这次把患者档案查询做了复用,仪表盘改成条件聚合,列表只查页面真正需要的字段。Agent 轨迹则先返回摘要,点开详情时再加载完整内容。MySQL 连接池也补上了最大连接数、空闲连接数和连接回收时间。

我写了一个简单的 Go 压测工具,测试 10 个只读接口,每个接口请求 50 次,并发数为 10,总共 500 次。

接口优化前 P95优化后 P95
管理端仪表盘4211.5 ms684.9 ms
管理端分诊记录4362.3 ms538.6 ms
管理端 Agent 轨迹7134.7 ms2151.1 ms
患者分诊记录2944.5 ms1218.0 ms

500 次请求的错误率是 0。

不过这只是本地 Windows 和本地 MySQL 的结果,没有计算模型、地图和公网耗时,所以只能说明数据库和接口查询比之前顺了,不能拿来证明系统已经能扛医院级并发。

把启动配置收紧了一点

项目原来为了开发方便,部分账号和数据库配置有默认值。自己电脑上没什么问题,但如果换个环境,很容易忘记修改。

现在所有环境启动时都要求配置:

BaseUrl
APIKey
MYSQL_DSN

Copy

当 APP_ENV=production 时,还会继续检查医生账号、管理员账号、密码、百度地图 AK 和短信配置。密码不能继续使用项目里的演示值,配置缺失时服务直接停止启动。

真实 .env、模型密钥、数据库连接、短信密钥和地图 AK 都已经放进 Git 忽略规则。提交之前我又扫了一遍,仓库里没有出现之前使用过的真实 AK。

Docker 真跑起来以后才发现端口撞了

Dockerfile 用了两阶段构建。第一阶段编译 Go 程序,第二阶段只留下二进制和运行需要的证书、时区、curl、中文字体。应用不是用 root 跑,而是单独建了 UID 10001 的用户。

Compose 里放了 Go 服务和 MySQL 8.4。MySQL 先通过健康检查,应用再启动。

应用本身也加了两个接口:

/health   检查 HTTP 进程
/ready    Ping MySQL,失败时返回 503

Copy

一开始镜像能构建,我以为这部分差不多结束了。真正执行 docker compose up 时才发现本地的 aggo-sse.exe 已经占用了 8080,Docker 无法再绑定同一个端口。

报错内容很直接:

ports are not available: listen tcp 0.0.0.0:8080

Copy

为了不关掉原来的本地数据环境,我把 Docker 默认宿主机端口改成了 8081:

ports:
  - "${DOCKER_PORT:-8081}:8080"

yaml

Copy

现在本地服务继续用 8080,Docker 环境使用 8081,两边可以同时打开。

CI 没做复杂,只做我能讲清楚的部分

GitHub Actions 目前只做四件事:

go test
go vet
go build
docker build

Copy

没有自动发布,也没有去连接真实数据库。原因也很简单:项目现在没有正式服务器,硬加自动部署除了让简历看起来热闹,面试时还会给自己增加解释成本。

第一次 CI 实际上已经跑成功了,但 GitHub 提示部分 Action 还基于 Node 20。随后把 Checkout、Setup Go 和 Docker Build Action 更新到了对应的新版本,又推了一次。第二次 Go 检查和 Docker 构建都正常通过。

提交前最后检查了一遍

这次最终检查的结果是:

  • 患者端、医生端、管理端和健康接口验收 8/8 通过;
  • Go 测试覆盖率 31.8%;
  • go vet 通过;
  • Docker 镜像实际构建成功;
  • Compose 配置可以正常解析;
  • 本地 8080 和 Docker 8081 都能访问;
  • .env、日志、二进制和真实密钥没有进入提交;
  • 代码已推送到 GitHub,CI 运行成功。

今天没有做出什么新的大页面,但项目比昨天更像一个完整项目了。

以前比较关注“功能有没有”,今天更多是在处理“状态能不能对上、出错后怎么办、换个环境能不能启动、提交后有没有人替我检查”。这些东西在页面上不一定显眼,但少一个都会在后面出问题。

接下来暂时不准备继续加业务。先把 Harness、登录鉴权和跨端状态更新相关测试补一些,再把项目里的技术细节整理成自己能讲明白的话。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐