200 lines
6.2 KiB
Go
200 lines
6.2 KiB
Go
package tool
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"tcm-agent/internal/config"
|
||
)
|
||
|
||
// ========================================================================
|
||
// Agent 工具集
|
||
// ========================================================================
|
||
// 工具是 Agent 的"手脚"——LLM 通过 Function Calling 自主决定
|
||
// 何时调用哪个工具。
|
||
//
|
||
// 已注册工具:
|
||
// - maxkb_retrieve :检索中医知识库(方剂/药典/指南)
|
||
// - his_query :查询 HIS 患者数据
|
||
// - pharmacopoeia_query:查询《中国药典》单味药详情
|
||
// - rule_check :校验处方配伍禁忌
|
||
//
|
||
// 新增工具只需:实现 types.Tool 接口 → 在 Runner 中 RegisterTool
|
||
// ========================================================================
|
||
|
||
// ========== 工具1:MaxKB 知识库检索 ==========
|
||
|
||
// MaxKBRetrieveTool 知识库检索工具
|
||
type MaxKBRetrieveTool struct {
|
||
client *MaxKBClient
|
||
}
|
||
|
||
// NewMaxKBRetrieveTool 创建知识库检索工具
|
||
func NewMaxKBRetrieveTool(client *MaxKBClient) *MaxKBRetrieveTool {
|
||
return &MaxKBRetrieveTool{client: client}
|
||
}
|
||
|
||
func (t *MaxKBRetrieveTool) Name() string { return "maxkb_retrieve" }
|
||
|
||
func (t *MaxKBRetrieveTool) Description() string {
|
||
return "检索中医知识库,获取权威的中医典籍、方剂、药典、病历书写规范等内容。输入应为检索关键词或问题。"
|
||
}
|
||
|
||
func (t *MaxKBRetrieveTool) Execute(ctx context.Context, params map[string]any) (string, error) {
|
||
query, _ := params["query"].(string)
|
||
if query == "" {
|
||
return "", fmt.Errorf("检索关键词不能为空")
|
||
}
|
||
log.Printf("[工具] 知识库检索 | 关键词: %s", query)
|
||
return t.client.Chat(ctx, query)
|
||
}
|
||
|
||
// ========== 工具2:HIS 系统查询 ==========
|
||
|
||
// HISTool 医院信息系统查询工具
|
||
//
|
||
// 实际项目对接真实的 HIS/PACS/LIS 系统。
|
||
// 这里用模拟数据演示。
|
||
type HISTool struct {
|
||
cfg config.MaxKBConfig // 复用配置结构(实际应独立 HIS 配置)
|
||
client *http.Client
|
||
}
|
||
|
||
// NewHISTool 创建 HIS 查询工具
|
||
func NewHISTool(cfg *config.MaxKBConfig) *HISTool {
|
||
var c config.MaxKBConfig
|
||
if cfg != nil {
|
||
c = *cfg
|
||
}
|
||
return &HISTool{
|
||
cfg: c,
|
||
client: &http.Client{Timeout: 10 * time.Second},
|
||
}
|
||
}
|
||
|
||
func (t *HISTool) Name() string { return "his_query" }
|
||
|
||
func (t *HISTool) Description() string {
|
||
return "查询医院信息系统(HIS)中的患者数据,包括既往病史、检查报告、用药记录、过敏史等。输入为患者ID或查询条件。"
|
||
}
|
||
|
||
func (t *HISTool) Execute(ctx context.Context, params map[string]any) (string, error) {
|
||
patientID, _ := params["query"].(string)
|
||
log.Printf("[工具] HIS 查询 | 患者 ID: %s", patientID)
|
||
|
||
// ===== 实际项目这里调用 HIS 接口 =====
|
||
// 以下是模拟数据
|
||
mockData := map[string]string{
|
||
"P001": `患者 P001 既往病史:
|
||
- 2023 年: 慢性胃炎
|
||
- 2024 年: 高血压(轻度)
|
||
- 过敏: 青霉素
|
||
- 近期检查: 血常规正常, 肝功能正常`,
|
||
"P002": `患者 P002 既往病史:
|
||
- 2022 年: 子宫肌瘤手术
|
||
- 2024 年: 妊娠期糖尿病
|
||
- 过敏: 无
|
||
- 近期检查: 血糖偏高`,
|
||
}
|
||
|
||
result, ok := mockData[patientID]
|
||
if !ok {
|
||
result = fmt.Sprintf("未找到患者 %s 的 HIS 记录", patientID)
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// ========== 工具3:药典查询 ==========
|
||
|
||
// PharmacopoeiaTool 中国药典查询工具
|
||
//
|
||
// 复用 MaxKB 客户端(药典内容已录入知识库)。
|
||
type PharmacopoeiaTool struct {
|
||
client *MaxKBClient
|
||
}
|
||
|
||
// NewPharmacopoeiaTool 创建药典查询工具
|
||
func NewPharmacopoeiaTool(client *MaxKBClient) *PharmacopoeiaTool {
|
||
return &PharmacopoeiaTool{client: client}
|
||
}
|
||
|
||
func (t *PharmacopoeiaTool) Name() string { return "pharmacopoeia_query" }
|
||
|
||
func (t *PharmacopoeiaTool) Description() string {
|
||
return "查询《中国药典》中某味中药的性味归经、功效主治、用法用量、禁忌事项。输入为药名。"
|
||
}
|
||
|
||
func (t *PharmacopoeiaTool) Execute(ctx context.Context, params map[string]any) (string, error) {
|
||
herbName, _ := params["query"].(string)
|
||
log.Printf("[工具] 药典查询 | 药名: %s", herbName)
|
||
|
||
query := fmt.Sprintf("《中国药典》%s 的性味归经、功效、用法用量、禁忌", herbName)
|
||
return t.client.Chat(ctx, query)
|
||
}
|
||
|
||
// ========== 工具4:规则引擎校验 ==========
|
||
|
||
// RuleCheckTool 规则校验工具(供 Agent 自查)
|
||
type RuleCheckTool struct{}
|
||
|
||
// NewRuleCheckTool 创建规则校验工具
|
||
func NewRuleCheckTool() *RuleCheckTool { return &RuleCheckTool{} }
|
||
|
||
func (t *RuleCheckTool) Name() string { return "rule_check" }
|
||
|
||
func (t *RuleCheckTool) Description() string {
|
||
return "校验处方是否符合中医配伍规则(十八反、十九畏、剂量范围、孕妇禁忌等)。输入为处方文本。"
|
||
}
|
||
|
||
func (t *RuleCheckTool) Execute(ctx context.Context, params map[string]any) (string, error) {
|
||
prescription, _ := params["query"].(string)
|
||
log.Printf("[工具] 规则校验 | 处方: %.50s...", prescription)
|
||
|
||
// 十八反
|
||
conflicts18 := map[string][]string{
|
||
"甘草": {"甘遂", "大戟", "芫花", "海藻"},
|
||
"乌头": {"贝母", "瓜蒌", "半夏", "白蔹", "白及"},
|
||
"藜芦": {"人参", "沙参", "丹参", "玄参", "苦参", "细辛", "芍药"},
|
||
}
|
||
// 十九畏
|
||
conflicts19 := map[string]string{
|
||
"硫黄": "朴硝", "水银": "砒霜", "狼毒": "密陀僧",
|
||
"巴豆": "牵牛", "丁香": "郁金", "牙硝": "三棱",
|
||
"川乌": "犀角", "草乌": "犀角", "人参": "五灵脂", "官桂": "赤石脂",
|
||
}
|
||
|
||
issues := make([]string, 0)
|
||
|
||
// 检查十八反
|
||
for herb, conflicts := range conflicts18 {
|
||
if strings.Contains(prescription, herb) {
|
||
for _, conflict := range conflicts {
|
||
if strings.Contains(prescription, conflict) {
|
||
issues = append(issues, fmt.Sprintf("【十八反】%s 反 %s", herb, conflict))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查十九畏
|
||
for a, b := range conflicts19 {
|
||
if strings.Contains(prescription, a) && strings.Contains(prescription, b) {
|
||
issues = append(issues, fmt.Sprintf("【十九畏】%s 畏 %s", a, b))
|
||
}
|
||
}
|
||
|
||
if len(issues) == 0 {
|
||
return "处方校验通过,未发现配伍禁忌。", nil
|
||
}
|
||
|
||
return "校验发现问题:\n" + strings.Join(issues, "\n"), nil
|
||
}
|
||
|
||
// 确保 import 使用
|
||
var _ = json.Marshal
|