Commit da795257 authored by yuguo's avatar yuguo

fix

parent 79589e01
......@@ -56,7 +56,8 @@
"Bash(sed:*)",
"Bash(docker-compose ps:*)",
"Bash(cat web/src/app/\\\\\\(main\\\\\\)/admin/agents/page.tsx)",
"Bash(awk NR==68,NR==376:*)"
"Bash(awk NR==68,NR==376:*)",
"Bash(LANG=en_US.UTF-8 sed:*)"
]
}
}
This diff is collapsed.
......@@ -11,6 +11,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"context"
internalagent "internet-hospital/internal/agent"
"internet-hospital/internal/model"
"internet-hospital/internal/service/knowledgesvc"
......@@ -25,6 +27,7 @@ import (
"internet-hospital/internal/service/chronic"
"internet-hospital/internal/service/health"
"internet-hospital/internal/service/user"
"internet-hospital/pkg/agent/tools"
"internet-hospital/pkg/ai"
"internet-hospital/pkg/config"
"internet-hospital/pkg/database"
......@@ -202,6 +205,69 @@ func main() {
// 注入跨包回调(AgentCallFn / WorkflowTriggerFn)
internalagent.WireCallbacks()
// v17: 注入问诊业务回调(避免 internal/agent ↔ internal/service 循环引用)
tools.CreateConsultFn = func(ctx context.Context, patientID, doctorID, consultType, chiefComplaint, medicalHistory string) (string, string, error) {
consultSvc := consult.NewService()
resp, err := consultSvc.CreateConsult(ctx, patientID, &consult.CreateConsultRequest{
DoctorID: doctorID,
Type: consultType,
ChiefComplaint: chiefComplaint,
MedicalHistory: medicalHistory,
})
if err != nil {
return "", "", err
}
return resp.ID, resp.SerialNumber, nil
}
tools.AcceptConsultFn = func(ctx context.Context, consultID, doctorUserID string) error {
dpSvc := doctorportal.NewService()
_, err := dpSvc.AcceptConsult(ctx, consultID, doctorUserID)
return err
}
log.Println("[Main] 问诊业务回调注入完成")
// v17 第2批: 注入慢病/续方/健康指标/排班回调
tools.CreateChronicRecordFn = func(ctx context.Context, userID, diseaseName, hospital, doctorName, currentMeds, notes string) (string, error) {
chronicSvc := chronic.NewService()
rec, err := chronicSvc.CreateChronicRecord(ctx, userID, &chronic.ChronicRecordReq{
DiseaseName: diseaseName, Hospital: hospital,
DoctorName: doctorName, CurrentMeds: currentMeds, Notes: notes,
})
if err != nil {
return "", err
}
return rec.ID, nil
}
tools.CreateRenewalFn = func(ctx context.Context, userID, chronicID, diseaseName, reason string, medicines []string) (string, error) {
chronicSvc := chronic.NewService()
rec, err := chronicSvc.CreateRenewal(ctx, userID, &chronic.RenewalReq{
ChronicID: chronicID, DiseaseName: diseaseName,
Medicines: medicines, Reason: reason,
})
if err != nil {
return "", err
}
return rec.ID, nil
}
tools.RecordHealthMetricFn = func(ctx context.Context, userID, metricType string, value1, value2 float64, unit, notes string) (string, error) {
chronicSvc := chronic.NewService()
rec, err := chronicSvc.CreateMetric(ctx, userID, &chronic.MetricReq{
MetricType: metricType, Value1: value1, Value2: value2,
Unit: unit, Notes: notes,
})
if err != nil {
return "", err
}
return rec.ID, nil
}
tools.CreateScheduleFn = func(ctx context.Context, doctorUserID, date, startTime, endTime string, maxCount int) error {
dpSvc := doctorportal.NewService()
return dpSvc.CreateSchedule(ctx, doctorUserID, []doctorportal.ScheduleSlotReq{
{Date: date, StartTime: startTime, EndTime: endTime, MaxCount: maxCount},
})
}
log.Println("[Main] 第2批业务回调注入完成")
// 设置 Gin 模式
gin.SetMode(cfg.Server.Mode)
......
......@@ -16,6 +16,21 @@ func defaultAgentDefinitions() []model.AgentDefinition {
"search_medical_knowledge", "query_drug",
"query_medical_record", "generate_follow_up_plan",
"send_notification", "navigate_page",
// v17: 问诊管理
"query_consultation_list", "query_consultation_detail", "create_consultation",
// v17: 处方查询
"query_prescription_list", "query_prescription_detail",
// v17: 患者信息
"query_patient_profile", "query_health_metrics", "query_lab_reports",
// v17: 医生/科室
"query_doctor_list", "query_doctor_detail", "query_department_list",
// v17: 慢病管理
"query_chronic_records", "create_chronic_record",
"query_renewal_requests", "create_renewal_request",
// v17: 健康指标写入 + 排班查询
"record_health_metric", "query_doctor_schedule",
// v17: 支付订单查询
"query_order_list", "query_order_detail",
})
// 医生通用智能体 — 合并 diagnosis + prescription + follow_up 能力
......@@ -23,6 +38,19 @@ func defaultAgentDefinitions() []model.AgentDefinition {
"query_medical_record", "query_symptom_knowledge", "search_medical_knowledge",
"query_drug", "check_drug_interaction", "check_contraindication", "calculate_dosage",
"generate_follow_up_plan", "send_notification", "navigate_page",
// v17: 问诊管理
"query_consultation_list", "query_consultation_detail",
"accept_consultation", "query_waiting_queue",
// v17: 处方管理
"query_prescription_list", "query_prescription_detail", "search_medicine_catalog",
// v17: 患者信息
"query_patient_profile", "query_health_metrics", "query_lab_reports",
// v17: 慢病续方审批
"query_renewal_requests",
// v17: 排班管理
"query_doctor_schedule", "create_doctor_schedule",
// v17: 收入统计
"query_income_stats", "query_income_records",
})
// 管理员通用智能体 — 合并 admin_assistant + general 管理能力
......@@ -31,6 +59,14 @@ func defaultAgentDefinitions() []model.AgentDefinition {
"trigger_workflow", "request_human_review",
"list_knowledge_collections", "send_notification",
"query_drug", "search_medical_knowledge", "navigate_page",
// v17: 业务数据查询
"query_consultation_list", "query_consultation_detail",
"query_prescription_list", "query_prescription_detail",
"generate_tool",
// v17: 管理统计 + 用户管理 + 系统日志 + 订单
"query_dashboard_stats", "query_dashboard_trend",
"query_user_list", "query_system_logs",
"query_order_list", "query_order_detail",
})
return []model.AgentDefinition{
......
......@@ -6,6 +6,7 @@ import (
"log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"internet-hospital/internal/model"
"internet-hospital/pkg/agent"
......@@ -31,6 +32,7 @@ func NewHandler() *Handler {
// RegisterRoutes 用户侧路由(全角色可用,仅需 JWT)
func (h *Handler) RegisterRoutes(r gin.IRouter) {
g := r.Group("/agent")
g.POST("/sessions", h.CreateSession)
g.POST("/:agent_id/chat", h.Chat)
g.POST("/:agent_id/chat/stream", h.ChatStream)
g.GET("/sessions", h.ListSessions)
......@@ -139,6 +141,39 @@ func (h *Handler) ListAgents(c *gin.Context) {
response.Success(c, h.svc.ListAgents())
}
// CreateSession 创建新会话,返回 session_id
func (h *Handler) CreateSession(c *gin.Context) {
var req struct {
AgentID string `json:"agent_id" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
userID, _ := c.Get("user_id")
uid, _ := userID.(string)
role, _ := c.Get("role")
userRole, _ := role.(string)
sessionID := uuid.New().String()
session := model.AgentSession{
SessionID: sessionID,
AgentID: req.AgentID,
UserID: uid,
History: "[]",
Context: "null",
PageContext: "null",
EntityContext: "null",
Status: "active",
UserRole: userRole,
}
if err := database.GetDB().Create(&session).Error; err != nil {
response.Error(c, 500, "创建会话失败: "+err.Error())
return
}
response.Success(c, gin.H{"session_id": sessionID})
}
// ListSessions 获取用户的 Agent 会话列表
func (h *Handler) ListSessions(c *gin.Context) {
userID, _ := c.Get("user_id")
......@@ -308,6 +343,7 @@ func (h *Handler) CreateDefinition(c *gin.Context) {
Description string `json:"description"`
Category string `json:"category"`
SystemPrompt string `json:"system_prompt"`
PromptTemplateID *uint `json:"prompt_template_id"`
Tools []string `json:"tools"`
Skills []string `json:"skills"`
MaxIterations int `json:"max_iterations"`
......@@ -327,6 +363,7 @@ func (h *Handler) CreateDefinition(c *gin.Context) {
Description: req.Description,
Category: req.Category,
SystemPrompt: req.SystemPrompt,
PromptTemplateID: req.PromptTemplateID,
Tools: string(toolsJSON),
Skills: string(skillsJSON),
MaxIterations: req.MaxIterations,
......@@ -348,6 +385,8 @@ func (h *Handler) UpdateDefinition(c *gin.Context) {
Description string `json:"description"`
Category string `json:"category"`
SystemPrompt string `json:"system_prompt"`
PromptTemplateID *uint `json:"prompt_template_id"`
ClearTemplate bool `json:"clear_template"`
Tools []string `json:"tools"`
Skills []string `json:"skills"`
MaxIterations int `json:"max_iterations"`
......@@ -378,6 +417,11 @@ func (h *Handler) UpdateDefinition(c *gin.Context) {
if req.SystemPrompt != "" {
updates["system_prompt"] = req.SystemPrompt
}
if req.PromptTemplateID != nil {
updates["prompt_template_id"] = *req.PromptTemplateID
} else if req.ClearTemplate {
updates["prompt_template_id"] = nil
}
if req.Tools != nil {
toolsJSON, _ := json.Marshal(req.Tools)
updates["tools"] = string(toolsJSON)
......
......@@ -62,6 +62,55 @@ func InitTools() {
// v15: 热代码 Tool 生成器
r.Register(&tools.CodeGenTool{})
// v17: 第1批业务工具 — 问诊管理
r.Register(&tools.ConsultationListTool{DB: db})
r.Register(&tools.ConsultationDetailTool{DB: db})
r.Register(&tools.CreateConsultationTool{})
r.Register(&tools.AcceptConsultationTool{})
r.Register(&tools.WaitingQueueTool{DB: db})
// v17: 第1批业务工具 — 处方管理
r.Register(&tools.PrescriptionListTool{DB: db})
r.Register(&tools.PrescriptionDetailTool{DB: db})
r.Register(&tools.MedicineCatalogTool{DB: db})
// v17: 第1批业务工具 — 患者信息
r.Register(&tools.PatientProfileTool{DB: db})
r.Register(&tools.HealthMetricsTool{DB: db})
r.Register(&tools.LabReportsTool{DB: db})
// v17: 第2批业务工具 — 医生/科室
r.Register(&tools.DoctorListTool{DB: db})
r.Register(&tools.DoctorDetailTool{DB: db})
r.Register(&tools.DepartmentListTool{DB: db})
// v17: 第2批业务工具 — 慢病管理
r.Register(&tools.ChronicRecordsTool{DB: db})
r.Register(&tools.CreateChronicRecordTool{})
r.Register(&tools.RenewalRequestsTool{DB: db})
r.Register(&tools.CreateRenewalTool{})
// v17: 第2批业务工具 — 健康指标写入
r.Register(&tools.RecordHealthMetricTool{})
// v17: 第2批业务工具 — 排班管理
r.Register(&tools.DoctorScheduleTool{DB: db})
r.Register(&tools.CreateDoctorScheduleTool{})
// v17: 第3批业务工具 — 支付订单
r.Register(&tools.OrderListTool{DB: db})
r.Register(&tools.OrderDetailTool{DB: db})
// v17: 第3批业务工具 — 医生收入
r.Register(&tools.IncomeStatsTool{DB: db})
r.Register(&tools.IncomeRecordsTool{DB: db})
// v17: 第3批业务工具 — 管理统计
r.Register(&tools.DashboardStatsTool{DB: db})
r.Register(&tools.DashboardTrendTool{DB: db})
r.Register(&tools.UserListTool{DB: db})
r.Register(&tools.SystemLogsTool{DB: db})
// v15: 从数据库加载动态 SQL 工具
loadDynamicSQLTools(r, db)
......@@ -75,8 +124,17 @@ func InitTools() {
log.Println("[InitTools] ToolMonitor & ToolSelector 初始化完成")
// v16: 初始化智能工具推荐器(使用pgvector)
agent.InitToolRecommender(db, embedder)
recommender := agent.InitToolRecommender(db, embedder)
log.Println("[InitTools] ToolRecommender 初始化完成")
// v17: 自动同步工具向量索引(异步,不阻塞启动)
if recommender != nil && embedder != nil {
go func() {
if err := recommender.IndexToolEmbeddings(context.Background()); err != nil {
log.Printf("[InitTools] 工具向量索引构建失败(embedder可能未配置API key): %v", err)
}
}()
}
}
// WireCallbacks 注入跨包回调(在 InitTools 和 GetService 初始化完成后调用)
......@@ -155,6 +213,45 @@ func syncToolsToDB(r *agent.ToolRegistry) {
"navigate_page": "navigation",
// v15: 热代码生成
"generate_tool": "code_gen",
// v17: 问诊管理
"query_consultation_list": "consult",
"query_consultation_detail": "consult",
"create_consultation": "consult",
"accept_consultation": "consult",
"query_waiting_queue": "consult",
// v17: 处方管理
"query_prescription_list": "prescription",
"query_prescription_detail": "prescription",
"search_medicine_catalog": "pharmacy",
// v17: 患者信息
"query_patient_profile": "patient",
"query_health_metrics": "health",
"query_lab_reports": "health",
// v17: 医生/科室
"query_doctor_list": "doctor",
"query_doctor_detail": "doctor",
"query_department_list": "department",
// v17: 慢病管理
"query_chronic_records": "chronic",
"create_chronic_record": "chronic",
"query_renewal_requests": "chronic",
"create_renewal_request": "chronic",
// v17: 健康指标写入
"record_health_metric": "health",
// v17: 排班管理
"query_doctor_schedule": "schedule",
"create_doctor_schedule": "schedule",
// v17: 支付订单
"query_order_list": "payment",
"query_order_detail": "payment",
// v17: 医生收入
"query_income_stats": "income",
"query_income_records": "income",
// v17: 管理统计
"query_dashboard_stats": "dashboard",
"query_dashboard_trend": "dashboard",
"query_user_list": "user_manage",
"query_system_logs": "system",
}
for name, tool := range r.All() {
......@@ -252,6 +349,21 @@ func buildToolKeywords(name, description, category string) string {
"通知", "提醒", "随访", "复诊", "安全", "禁忌",
"相互作用", "剂量", "用量", "计算", "表达式",
"患者", "医生", "管理", "查询",
// v17: 新增业务关键词
"问诊", "就诊", "接诊", "挂号", "预约", "主诉",
"等候", "队列", "排队",
"档案", "信息", "资料", "健康",
"指标", "血压", "血糖", "心率", "体温",
"化验", "解读", "目录", "库存",
// v17: 第2批业务关键词
"慢病", "慢性", "续方", "续药", "审批",
"排班", "时段", "预约",
"科室", "专长", "评分", "在线",
// v17: 第3批业务关键词
"订单", "支付", "付款", "退款", "收入", "余额",
"提现", "账单", "收益", "分成",
"仪表盘", "统计", "趋势", "运营",
"用户", "日志", "操作", "系统",
} {
if strings.Contains(description, kw) {
descKeywords[kw] = true
......@@ -299,6 +411,45 @@ func initToolSelector(r *agent.ToolRegistry) {
"navigate_page": "navigation",
// v15: 热代码生成
"generate_tool": "code_gen",
// v17: 问诊管理
"query_consultation_list": "consult",
"query_consultation_detail": "consult",
"create_consultation": "consult",
"accept_consultation": "consult",
"query_waiting_queue": "consult",
// v17: 处方管理
"query_prescription_list": "prescription",
"query_prescription_detail": "prescription",
"search_medicine_catalog": "pharmacy",
// v17: 患者信息
"query_patient_profile": "patient",
"query_health_metrics": "health",
"query_lab_reports": "health",
// v17: 医生/科室
"query_doctor_list": "doctor",
"query_doctor_detail": "doctor",
"query_department_list": "department",
// v17: 慢病管理
"query_chronic_records": "chronic",
"create_chronic_record": "chronic",
"query_renewal_requests": "chronic",
"create_renewal_request": "chronic",
// v17: 健康指标写入
"record_health_metric": "health",
// v17: 排班管理
"query_doctor_schedule": "schedule",
"create_doctor_schedule": "schedule",
// v17: 支付订单
"query_order_list": "payment",
"query_order_detail": "payment",
// v17: 医生收入
"query_income_stats": "income",
"query_income_records": "income",
// v17: 管理统计
"query_dashboard_stats": "dashboard",
"query_dashboard_trend": "dashboard",
"query_user_list": "user_manage",
"query_system_logs": "system",
}
for name, tool := range r.All() {
......
This diff is collapsed.
This diff is collapsed.
......@@ -28,15 +28,15 @@ func (ChronicRecord) TableName() string { return "chronic_records" }
type RenewalRequest struct {
ID string `gorm:"type:uuid;primaryKey" json:"id"`
UserID string `gorm:"type:uuid;index;not null" json:"user_id"`
ChronicID string `gorm:"type:uuid;index" json:"chronic_id"`
ChronicID *string `gorm:"type:uuid;index" json:"chronic_id"`
DiseaseName string `gorm:"type:varchar(100)" json:"disease_name"`
Medicines string `gorm:"type:text" json:"medicines"` // JSON array
Reason string `gorm:"type:text" json:"reason"`
Status string `gorm:"type:varchar(20);default:'pending'" json:"status"` // pending|approved|rejected
DoctorID string `gorm:"type:uuid" json:"doctor_id"`
DoctorID *string `gorm:"type:uuid" json:"doctor_id"`
DoctorName string `gorm:"type:varchar(50)" json:"doctor_name"`
DoctorNote string `gorm:"type:text" json:"doctor_note"`
PrescriptionID string `gorm:"type:uuid" json:"prescription_id"`
PrescriptionID *string `gorm:"type:uuid" json:"prescription_id"`
AIAdvice string `gorm:"type:text" json:"ai_advice"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
......
......@@ -61,7 +61,7 @@ func (h *Handler) CreateRecord(c *gin.Context) {
}
r, err := h.service.CreateChronicRecord(c.Request.Context(), userID.(string), &req)
if err != nil {
response.Error(c, 500, "创建慢病档案失败")
response.Error(c, 500, "创建慢病档案失败: "+err.Error())
return
}
response.Success(c, r)
......@@ -110,7 +110,7 @@ func (h *Handler) CreateRenewal(c *gin.Context) {
}
r, err := h.service.CreateRenewal(c.Request.Context(), userID.(string), &req)
if err != nil {
response.Error(c, 500, "创建续方申请失败")
response.Error(c, 500, "创建续方申请失败: "+err.Error())
return
}
response.Success(c, r)
......
......@@ -93,9 +93,13 @@ func (s *Service) ListRenewals(ctx context.Context, userID string) ([]model.Rene
func (s *Service) CreateRenewal(ctx context.Context, userID string, req *RenewalReq) (*model.RenewalRequest, error) {
medsJSON, _ := json.Marshal(req.Medicines)
var chronicID *string
if req.ChronicID != "" {
chronicID = &req.ChronicID
}
r := &model.RenewalRequest{
ID: uuid.New().String(), UserID: userID,
ChronicID: req.ChronicID, DiseaseName: req.DiseaseName,
ChronicID: chronicID, DiseaseName: req.DiseaseName,
Medicines: string(medsJSON), Reason: req.Reason, Status: "pending",
}
if err := s.db.WithContext(ctx).Create(r).Error; err != nil {
......
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateChronicRecordFn 创建慢病记录回调,由 main.go 注入
var CreateChronicRecordFn func(ctx context.Context, userID, diseaseName, hospital, doctorName, currentMeds, notes string) (string, error)
// CreateChronicRecordTool 创建慢病档案
type CreateChronicRecordTool struct{}
func (t *CreateChronicRecordTool) Name() string { return "create_chronic_record" }
func (t *CreateChronicRecordTool) Description() string {
return "患者创建慢病档案记录,记录慢性疾病的诊断信息和当前用药情况"
}
func (t *CreateChronicRecordTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "disease_name", Type: "string", Description: "疾病名称(如高血压、糖尿病)", Required: true},
{Name: "hospital", Type: "string", Description: "确诊医院(可选)", Required: false},
{Name: "doctor_name", Type: "string", Description: "确诊医生(可选)", Required: false},
{Name: "current_meds", Type: "string", Description: "当前用药情况(可选)", Required: false},
{Name: "notes", Type: "string", Description: "备注(可选)", Required: false},
}
}
func (t *CreateChronicRecordTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
if userRole != "patient" {
return nil, fmt.Errorf("仅患者可创建慢病记录")
}
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
diseaseName, ok := params["disease_name"].(string)
if !ok || diseaseName == "" {
return nil, fmt.Errorf("disease_name 必填")
}
hospital, _ := params["hospital"].(string)
doctorName, _ := params["doctor_name"].(string)
currentMeds, _ := params["current_meds"].(string)
notes, _ := params["notes"].(string)
if CreateChronicRecordFn == nil {
return nil, fmt.Errorf("慢病服务未初始化")
}
recordID, err := CreateChronicRecordFn(ctx, userID, diseaseName, hospital, doctorName, currentMeds, notes)
if err != nil {
return nil, fmt.Errorf("创建慢病记录失败: %v", err)
}
return map[string]interface{}{
"record_id": recordID,
"disease_name": diseaseName,
"message": "慢病档案已创建",
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ChronicRecordsTool 查询慢病记录
type ChronicRecordsTool struct {
DB *gorm.DB
}
func (t *ChronicRecordsTool) Name() string { return "query_chronic_records" }
func (t *ChronicRecordsTool) Description() string {
return "查询患者的慢病档案记录(疾病名称、确诊日期、当前用药、控制状态)"
}
func (t *ChronicRecordsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "patient_id", Type: "string", Description: "患者ID(医生使用,患者无需传入)", Required: false},
}
}
func (t *ChronicRecordsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
targetID := userID
if pid, ok := params["patient_id"].(string); ok && pid != "" {
if userRole == "patient" && pid != userID {
return nil, fmt.Errorf("患者只能查看自己的慢病记录")
}
targetID = pid
}
if targetID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(`
SELECT id, disease_name, diagnosis_date, hospital, doctor_name,
current_meds, control_status, notes, created_at
FROM chronic_records
WHERE user_id = ? AND deleted_at IS NULL
ORDER BY created_at DESC`, targetID).Rows()
if err != nil {
return map[string]interface{}{"records": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"records": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// AcceptConsultFn 接诊回调,由 WireCallbacks 注入
var AcceptConsultFn func(ctx context.Context, consultID, doctorUserID string) error
// AcceptConsultationTool 医生接诊
type AcceptConsultationTool struct{}
func (t *AcceptConsultationTool) Name() string { return "accept_consultation" }
func (t *AcceptConsultationTool) Description() string {
return "医生接受一个等候中的问诊,将问诊状态从pending变为in_progress"
}
func (t *AcceptConsultationTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "consultation_id", Type: "string", Description: "要接诊的问诊ID", Required: true},
}
}
func (t *AcceptConsultationTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
if userRole != "doctor" {
return nil, fmt.Errorf("仅医生可接诊")
}
consultID, ok := params["consultation_id"].(string)
if !ok || consultID == "" {
return nil, fmt.Errorf("consultation_id 必填")
}
if AcceptConsultFn == nil {
return nil, fmt.Errorf("接诊服务未初始化")
}
if err := AcceptConsultFn(ctx, consultID, userID); err != nil {
return nil, fmt.Errorf("接诊失败: %v", err)
}
return map[string]interface{}{
"consultation_id": consultID,
"status": "in_progress",
"message": "接诊成功,问诊已开始",
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateConsultFn 创建问诊回调,由 WireCallbacks 注入
// 参数: ctx, patientID, doctorID, consultType, chiefComplaint, medicalHistory
// 返回: 问诊ID, 流水号, error
var CreateConsultFn func(ctx context.Context, patientID, doctorID, consultType, chiefComplaint, medicalHistory string) (string, string, error)
// CreateConsultationTool 创建问诊(患者发起)
type CreateConsultationTool struct{}
func (t *CreateConsultationTool) Name() string { return "create_consultation" }
func (t *CreateConsultationTool) Description() string {
return "患者创建新的问诊,需要指定医生ID、问诊类型和主诉。创建成功后返回问诊ID和流水号"
}
func (t *CreateConsultationTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "doctor_id", Type: "string", Description: "接诊医生ID(从 query_doctor_list 获取)", Required: true},
{Name: "type", Type: "string", Description: "问诊类型", Required: true, Enum: []string{"text", "video"}},
{Name: "chief_complaint", Type: "string", Description: "主诉(患者症状描述)", Required: true},
{Name: "medical_history", Type: "string", Description: "既往病史(可选)", Required: false},
}
}
func (t *CreateConsultationTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
if userRole != "patient" {
return nil, fmt.Errorf("仅患者可创建问诊")
}
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
doctorID, ok := params["doctor_id"].(string)
if !ok || doctorID == "" {
return nil, fmt.Errorf("doctor_id 必填")
}
consultType, ok := params["type"].(string)
if !ok || (consultType != "text" && consultType != "video") {
return nil, fmt.Errorf("type 必须为 text 或 video")
}
chiefComplaint, ok := params["chief_complaint"].(string)
if !ok || chiefComplaint == "" {
return nil, fmt.Errorf("chief_complaint 必填")
}
medicalHistory, _ := params["medical_history"].(string)
if CreateConsultFn == nil {
return nil, fmt.Errorf("问诊服务未初始化")
}
consultID, serialNumber, err := CreateConsultFn(ctx, userID, doctorID, consultType, chiefComplaint, medicalHistory)
if err != nil {
return nil, fmt.Errorf("创建问诊失败: %v", err)
}
return map[string]interface{}{
"consultation_id": consultID,
"serial_number": serialNumber,
"status": "pending",
"message": "问诊已创建,等待医生接诊",
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ConsultationDetailTool 查询问诊详情
type ConsultationDetailTool struct {
DB *gorm.DB
}
func (t *ConsultationDetailTool) Name() string { return "query_consultation_detail" }
func (t *ConsultationDetailTool) Description() string {
return "查询问诊详情,包括患者信息、医生信息、主诉、诊断、消息记录,支持UUID或流水号(C开头)查询"
}
func (t *ConsultationDetailTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "consultation_id", Type: "string", Description: "问诊ID(UUID)或流水号(C开头,如C20260305-0001)", Required: true},
{Name: "include_messages", Type: "boolean", Description: "是否包含消息记录,默认true", Required: false},
}
}
func (t *ConsultationDetailTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
consultID, ok := params["consultation_id"].(string)
if !ok || consultID == "" {
return nil, fmt.Errorf("consultation_id 必填")
}
includeMessages := true
if v, ok := params["include_messages"].(bool); ok {
includeMessages = v
}
// 支持流水号查询
resolvedID := consultID
if len(consultID) > 0 && consultID[0] == 'C' {
var id string
if err := t.DB.WithContext(ctx).Raw("SELECT id FROM consultations WHERE serial_number = ?", consultID).Scan(&id).Error; err != nil || id == "" {
return nil, fmt.Errorf("流水号 %s 对应的问诊不存在", consultID)
}
resolvedID = id
}
// 查询问诊详情
var detail map[string]interface{}
detailRows, err := t.DB.WithContext(ctx).Raw(`
SELECT c.id, c.serial_number, c.patient_id, c.doctor_id, c.type, c.status,
c.chief_complaint, c.medical_history, c.diagnosis, c.summary,
c.started_at, c.ended_at, c.created_at, c.satisfaction_score,
d.name as doctor_name, d.title as doctor_title, dep.name as department_name,
u.real_name as patient_name, u.phone as patient_phone
FROM consultations c
LEFT JOIN doctors d ON c.doctor_id = d.id
LEFT JOIN departments dep ON d.department_id = dep.id
LEFT JOIN users u ON c.patient_id = u.id
WHERE c.id = ? AND c.deleted_at IS NULL`, resolvedID).Rows()
if err != nil {
return nil, fmt.Errorf("问诊不存在: %s", consultID)
}
defer detailRows.Close()
cols, _ := detailRows.Columns()
if !detailRows.Next() {
return nil, fmt.Errorf("问诊不存在: %s", consultID)
}
detail = make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := detailRows.Scan(ptrs...); err != nil {
return nil, fmt.Errorf("问诊不存在: %s", consultID)
}
for i, col := range cols {
detail[col] = vals[i]
}
// 权限校验:患者只能查自己的,医生只能查自己接诊的
if userRole == "patient" && detail["patient_id"] != userID {
return nil, fmt.Errorf("无权查看该问诊记录")
}
if userRole == "doctor" {
var doctorID string
t.DB.WithContext(ctx).Raw("SELECT id FROM doctors WHERE user_id = ?", userID).Scan(&doctorID)
if detail["doctor_id"] != doctorID {
return nil, fmt.Errorf("无权查看该问诊记录")
}
}
// 可选:查询消息记录
if includeMessages {
var messages []map[string]interface{}
msgRows, err := t.DB.WithContext(ctx).Raw(`
SELECT id, sender_type, content, content_type, created_at
FROM consult_messages
WHERE consult_id = ? AND deleted_at IS NULL
ORDER BY created_at ASC LIMIT 50`, resolvedID).Rows()
if err == nil {
defer msgRows.Close()
msgCols, _ := msgRows.Columns()
for msgRows.Next() {
msg := make(map[string]interface{})
mVals := make([]interface{}, len(msgCols))
mPtrs := make([]interface{}, len(msgCols))
for i := range mVals {
mPtrs[i] = &mVals[i]
}
if err := msgRows.Scan(mPtrs...); err == nil {
for i, col := range msgCols {
msg[col] = mVals[i]
}
messages = append(messages, msg)
}
}
}
detail["messages"] = messages
detail["message_count"] = len(messages)
}
return detail, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// ConsultationListTool 查询问诊列表
type ConsultationListTool struct {
DB *gorm.DB
}
func (t *ConsultationListTool) Name() string { return "query_consultation_list" }
func (t *ConsultationListTool) Description() string {
return "查询问诊列表:患者查自己的问诊记录,医生查自己接诊的问诊记录,支持按状态过滤"
}
func (t *ConsultationListTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "status", Type: "string", Description: "问诊状态过滤(可选):pending/in_progress/completed/cancelled", Required: false, Enum: []string{"pending", "in_progress", "completed", "cancelled"}},
{Name: "limit", Type: "number", Description: "返回记录数量,默认10,最大50", Required: false},
}
}
func (t *ConsultationListTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
limit := 10
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 50 {
limit = 50
}
}
status, _ := params["status"].(string)
// 根据角色决定查询条件
var roleField string
switch userRole {
case "doctor":
roleField = "doctor_id"
var doctorID string
if err := t.DB.WithContext(ctx).Raw("SELECT id FROM doctors WHERE user_id = ?", userID).Scan(&doctorID).Error; err != nil || doctorID == "" {
return map[string]interface{}{"consultations": []interface{}{}, "total": 0}, nil
}
userID = doctorID
case "admin":
roleField = ""
default:
roleField = "patient_id"
}
query := "SELECT c.id, c.serial_number, c.chief_complaint, c.status, c.type, c.created_at, c.started_at, c.ended_at, " +
"d.name as doctor_name, dep.name as department_name, u.real_name as patient_name " +
"FROM consultations c " +
"LEFT JOIN doctors d ON c.doctor_id = d.id " +
"LEFT JOIN departments dep ON d.department_id = dep.id " +
"LEFT JOIN users u ON c.patient_id = u.id " +
"WHERE c.deleted_at IS NULL"
args := []interface{}{}
if roleField != "" {
query += " AND c." + roleField + " = ?"
args = append(args, userID)
}
if status != "" {
query += " AND c.status = ?"
args = append(args, status)
}
query += " ORDER BY c.created_at DESC LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"consultations": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"consultations": results,
"total": len(results),
"role": userRole,
}, nil
}
package tools
import (
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DashboardStatsTool 查询管理端仪表盘统计数据
type DashboardStatsTool struct {
DB *gorm.DB
}
func (t *DashboardStatsTool) Name() string { return "query_dashboard_stats" }
func (t *DashboardStatsTool) Description() string {
return "查询管理端仪表盘统计:总用户数、总医生数、总问诊数、今日问诊、待审核医生、今日/本月收入,仅管理员可用"
}
func (t *DashboardStatsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{}
}
func (t *DashboardStatsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userRole != "admin" {
return nil, fmt.Errorf("仅管理员可查看仪表盘数据")
}
today := time.Now().Format("2006-01-02")
monthStart := time.Now().Format("2006-01") + "-01"
stats := map[string]interface{}{}
var count int64
// 总用户数
t.DB.WithContext(ctx).Raw("SELECT COUNT(*) FROM users WHERE deleted_at IS NULL").Scan(&count)
stats["total_users"] = count
// 总医生数
t.DB.WithContext(ctx).Raw("SELECT COUNT(*) FROM users WHERE role = 'doctor' AND deleted_at IS NULL").Scan(&count)
stats["total_doctors"] = count
// 总问诊数
t.DB.WithContext(ctx).Raw("SELECT COUNT(*) FROM consultations WHERE deleted_at IS NULL").Scan(&count)
stats["total_consultations"] = count
// 今日问诊数
t.DB.WithContext(ctx).Raw("SELECT COUNT(*) FROM consultations WHERE DATE(created_at) = ? AND deleted_at IS NULL", today).Scan(&count)
stats["today_consultations"] = count
// 待审核医生数
t.DB.WithContext(ctx).Raw("SELECT COUNT(*) FROM doctor_reviews WHERE status = 'pending'").Scan(&count)
stats["pending_doctor_reviews"] = count
// 今日收入(已支付订单)
var todayRevenue int64
t.DB.WithContext(ctx).Raw(
"SELECT COALESCE(SUM(amount), 0) FROM payment_orders WHERE DATE(paid_at) = ? AND status = 'paid' AND deleted_at IS NULL",
today,
).Scan(&todayRevenue)
stats["revenue_today"] = todayRevenue
// 本月收入
var monthRevenue int64
t.DB.WithContext(ctx).Raw(
"SELECT COALESCE(SUM(amount), 0) FROM payment_orders WHERE paid_at >= ? AND status = 'paid' AND deleted_at IS NULL",
monthStart,
).Scan(&monthRevenue)
stats["revenue_month"] = monthRevenue
// 总处方数
t.DB.WithContext(ctx).Raw("SELECT COUNT(*) FROM prescriptions WHERE deleted_at IS NULL").Scan(&count)
stats["total_prescriptions"] = count
return stats, nil
}
package tools
import (
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DashboardTrendTool 查询管理端运营趋势数据
type DashboardTrendTool struct {
DB *gorm.DB
}
func (t *DashboardTrendTool) Name() string { return "query_dashboard_trend" }
func (t *DashboardTrendTool) Description() string {
return "查询最近7天运营趋势:每日问诊量、完成量、收入,仅管理员可用"
}
func (t *DashboardTrendTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "days", Type: "number", Description: "查询天数,默认7,最大30", Required: false},
}
}
func (t *DashboardTrendTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userRole != "admin" {
return nil, fmt.Errorf("仅管理员可查看运营趋势")
}
days := 7
if v, ok := params["days"].(float64); ok && v > 0 {
days = int(v)
if days > 30 {
days = 30
}
}
startDate := time.Now().AddDate(0, 0, -(days - 1)).Format("2006-01-02")
rows, err := t.DB.WithContext(ctx).Raw(`
SELECT
DATE(created_at)::text AS date,
COUNT(*) AS consult_count,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count
FROM consultations
WHERE DATE(created_at) >= ? AND deleted_at IS NULL
GROUP BY DATE(created_at)
ORDER BY date
`, startDate).Rows()
if err != nil {
return map[string]interface{}{"trend": []interface{}{}}, nil
}
defer rows.Close()
var results []map[string]interface{}
for rows.Next() {
var date string
var consultCount, completedCount int
if err := rows.Scan(&date, &consultCount, &completedCount); err == nil {
results = append(results, map[string]interface{}{
"date": date,
"consult_count": consultCount,
"completed_count": completedCount,
})
}
}
if results == nil {
results = []map[string]interface{}{}
}
return map[string]interface{}{
"trend": results,
"days": days,
}, nil
}
package tools
import (
"context"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DepartmentListTool 查询科室列表
type DepartmentListTool struct {
DB *gorm.DB
}
func (t *DepartmentListTool) Name() string { return "query_department_list" }
func (t *DepartmentListTool) Description() string {
return "查询所有科室列表(含层级关系),为推荐科室后查询具体医生提供数据支持"
}
func (t *DepartmentListTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{}
}
func (t *DepartmentListTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(`
SELECT id, name, icon, parent_id, sort_order
FROM departments
WHERE deleted_at IS NULL
ORDER BY sort_order ASC, name ASC`).Rows()
if err != nil {
return map[string]interface{}{"departments": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"departments": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DoctorDetailTool 查询医生详情+排班
type DoctorDetailTool struct {
DB *gorm.DB
}
func (t *DoctorDetailTool) Name() string { return "query_doctor_detail" }
func (t *DoctorDetailTool) Description() string {
return "查询医生详情信息和近7天排班,包括简介、专长、评分、价格、可预约时段"
}
func (t *DoctorDetailTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "doctor_id", Type: "string", Description: "医生ID", Required: true},
}
}
func (t *DoctorDetailTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
doctorID, ok := params["doctor_id"].(string)
if !ok || doctorID == "" {
return nil, fmt.Errorf("doctor_id 必填")
}
// 查询医生详情
detailRows, err := t.DB.WithContext(ctx).Raw(`
SELECT d.id, d.name, d.title, d.hospital, d.specialties, d.introduction,
d.rating, d.consult_count, d.price, d.is_online, d.avatar,
dep.name as department_name
FROM doctors d
LEFT JOIN departments dep ON d.department_id = dep.id
WHERE d.id = ? AND d.deleted_at IS NULL`, doctorID).Rows()
if err != nil {
return nil, fmt.Errorf("医生不存在: %s", doctorID)
}
defer detailRows.Close()
cols, _ := detailRows.Columns()
if !detailRows.Next() {
return nil, fmt.Errorf("医生不存在: %s", doctorID)
}
detail := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := detailRows.Scan(ptrs...); err != nil {
return nil, fmt.Errorf("医生不存在: %s", doctorID)
}
for i, col := range cols {
detail[col] = vals[i]
}
// 查询近7天排班
today := time.Now().Format("2006-01-02")
endDate := time.Now().AddDate(0, 0, 7).Format("2006-01-02")
var schedules []map[string]interface{}
schedRows, err := t.DB.WithContext(ctx).Raw(`
SELECT id, date, start_time, end_time, max_count, remaining
FROM doctor_schedules
WHERE doctor_id = ? AND date >= ? AND date <= ?
ORDER BY date ASC, start_time ASC`, doctorID, today, endDate).Rows()
if err == nil {
defer schedRows.Close()
sCols, _ := schedRows.Columns()
for schedRows.Next() {
sched := make(map[string]interface{})
sVals := make([]interface{}, len(sCols))
sPtrs := make([]interface{}, len(sCols))
for i := range sVals {
sPtrs[i] = &sVals[i]
}
if err := schedRows.Scan(sPtrs...); err == nil {
for i, col := range sCols {
sched[col] = sVals[i]
}
schedules = append(schedules, sched)
}
}
}
detail["schedules"] = schedules
return detail, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DoctorListTool 搜索医生列表
type DoctorListTool struct {
DB *gorm.DB
}
func (t *DoctorListTool) Name() string { return "query_doctor_list" }
func (t *DoctorListTool) Description() string {
return "搜索医生列表,支持按科室、关键词筛选,按评分/问诊量/价格排序,返回医生基本信息和在线状态"
}
func (t *DoctorListTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "department_id", Type: "string", Description: "科室ID过滤(可选)", Required: false},
{Name: "keyword", Type: "string", Description: "医生姓名关键词(可选)", Required: false},
{Name: "sort_by", Type: "string", Description: "排序方式,默认按评分", Required: false, Enum: []string{"rating", "consult_count", "price"}},
{Name: "limit", Type: "number", Description: "返回数量,默认10", Required: false},
}
}
func (t *DoctorListTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userRole != "patient" && userRole != "admin" {
return nil, fmt.Errorf("仅患者和管理员可搜索医生")
}
limit := 10
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 30 {
limit = 30
}
}
departmentID, _ := params["department_id"].(string)
keyword, _ := params["keyword"].(string)
sortBy, _ := params["sort_by"].(string)
query := "SELECT d.id, d.name, d.title, d.hospital, d.specialties, d.rating, " +
"d.consult_count, d.price, d.is_online, d.avatar, " +
"dep.name as department_name " +
"FROM doctors d " +
"LEFT JOIN departments dep ON d.department_id = dep.id " +
"WHERE d.status = 'approved' AND d.deleted_at IS NULL"
args := []interface{}{}
if departmentID != "" {
query += " AND d.department_id = ?"
args = append(args, departmentID)
}
if keyword != "" {
query += " AND d.name ILIKE ?"
args = append(args, "%"+keyword+"%")
}
switch sortBy {
case "consult_count":
query += " ORDER BY d.consult_count DESC"
case "price":
query += " ORDER BY d.price ASC"
default:
query += " ORDER BY d.rating DESC"
}
query += " LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"doctors": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"doctors": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// DoctorScheduleTool 查询医生排班
type DoctorScheduleTool struct {
DB *gorm.DB
}
func (t *DoctorScheduleTool) Name() string { return "query_doctor_schedule" }
func (t *DoctorScheduleTool) Description() string {
return "查询医生排班信息,患者用于预约,医生用于查看自己的排班"
}
func (t *DoctorScheduleTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "doctor_id", Type: "string", Description: "医生ID(患者必填,医生可不填查自己的)", Required: false},
{Name: "days", Type: "number", Description: "查询未来天数,默认7天", Required: false},
}
}
func (t *DoctorScheduleTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
doctorID, _ := params["doctor_id"].(string)
// 医生查自己排班时无需传 doctor_id
if doctorID == "" && userRole == "doctor" {
var id string
t.DB.WithContext(ctx).Raw("SELECT id FROM doctors WHERE user_id = ?", userID).Scan(&id)
doctorID = id
}
if doctorID == "" {
return nil, fmt.Errorf("doctor_id 必填")
}
days := 7
if v, ok := params["days"].(float64); ok && v > 0 {
days = int(v)
if days > 30 {
days = 30
}
}
today := time.Now().Format("2006-01-02")
endDate := time.Now().AddDate(0, 0, days).Format("2006-01-02")
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(`
SELECT id, date, start_time, end_time, max_count, remaining
FROM doctor_schedules
WHERE doctor_id = ? AND date >= ? AND date <= ?
ORDER BY date ASC, start_time ASC`, doctorID, today, endDate).Rows()
if err != nil {
return map[string]interface{}{"schedules": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"schedules": results,
"total": len(results),
"doctor_id": doctorID,
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateScheduleFn 创建排班回调,由 main.go 注入
// 参数: ctx, doctorUserID, date, startTime, endTime, maxCount
var CreateScheduleFn func(ctx context.Context, doctorUserID, date, startTime, endTime string, maxCount int) error
// CreateDoctorScheduleTool 医生创建排班
type CreateDoctorScheduleTool struct{}
func (t *CreateDoctorScheduleTool) Name() string { return "create_doctor_schedule" }
func (t *CreateDoctorScheduleTool) Description() string {
return "医生创建排班时段,设置日期、时间段和最大接诊人数"
}
func (t *CreateDoctorScheduleTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "date", Type: "string", Description: "排班日期(格式:2006-01-02)", Required: true},
{Name: "start_time", Type: "string", Description: "开始时间(格式:09:00)", Required: true},
{Name: "end_time", Type: "string", Description: "结束时间(格式:12:00)", Required: true},
{Name: "max_count", Type: "number", Description: "最大接诊人数", Required: true},
}
}
func (t *CreateDoctorScheduleTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
if userRole != "doctor" {
return nil, fmt.Errorf("仅医生可创建排班")
}
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
date, ok := params["date"].(string)
if !ok || date == "" {
return nil, fmt.Errorf("date 必填")
}
startTime, ok := params["start_time"].(string)
if !ok || startTime == "" {
return nil, fmt.Errorf("start_time 必填")
}
endTime, ok := params["end_time"].(string)
if !ok || endTime == "" {
return nil, fmt.Errorf("end_time 必填")
}
maxCount := 0
if v, ok := params["max_count"].(float64); ok {
maxCount = int(v)
}
if maxCount <= 0 {
return nil, fmt.Errorf("max_count 必须大于0")
}
if CreateScheduleFn == nil {
return nil, fmt.Errorf("排班服务未初始化")
}
if err := CreateScheduleFn(ctx, userID, date, startTime, endTime, maxCount); err != nil {
return nil, fmt.Errorf("创建排班失败: %v", err)
}
return map[string]interface{}{
"date": date,
"start_time": startTime,
"end_time": endTime,
"max_count": maxCount,
"message": "排班已创建",
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// RecordHealthMetricFn 记录健康指标回调,由 main.go 注入
var RecordHealthMetricFn func(ctx context.Context, userID, metricType string, value1, value2 float64, unit, notes string) (string, error)
// RecordHealthMetricTool 记录健康指标
type RecordHealthMetricTool struct{}
func (t *RecordHealthMetricTool) Name() string { return "record_health_metric" }
func (t *RecordHealthMetricTool) Description() string {
return "患者记录健康指标(血压、血糖、心率、体温),异常值会自动触发健康告警"
}
func (t *RecordHealthMetricTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "metric_type", Type: "string", Description: "指标类型", Required: true, Enum: []string{"blood_pressure", "blood_glucose", "heart_rate", "body_temperature"}},
{Name: "value1", Type: "number", Description: "主值(血压为收缩压,血糖/心率/体温为测量值)", Required: true},
{Name: "value2", Type: "number", Description: "副值(血压为舒张压,其他类型可不填)", Required: false},
{Name: "unit", Type: "string", Description: "单位(可选,如mmHg、mmol/L、bpm、℃)", Required: false},
{Name: "notes", Type: "string", Description: "备注(可选,如饭前/饭后、运动后等)", Required: false},
}
}
func (t *RecordHealthMetricTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
if userRole != "patient" {
return nil, fmt.Errorf("仅患者可记录健康指标")
}
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
metricType, ok := params["metric_type"].(string)
if !ok || metricType == "" {
return nil, fmt.Errorf("metric_type 必填")
}
value1, ok := params["value1"].(float64)
if !ok {
return nil, fmt.Errorf("value1 必填")
}
var value2 float64
if v, ok := params["value2"].(float64); ok {
value2 = v
}
unit, _ := params["unit"].(string)
notes, _ := params["notes"].(string)
// 默认单位
if unit == "" {
switch metricType {
case "blood_pressure":
unit = "mmHg"
case "blood_glucose":
unit = "mmol/L"
case "heart_rate":
unit = "bpm"
case "body_temperature":
unit = "℃"
}
}
if RecordHealthMetricFn == nil {
return nil, fmt.Errorf("健康指标服务未初始化")
}
metricID, err := RecordHealthMetricFn(ctx, userID, metricType, value1, value2, unit, notes)
if err != nil {
return nil, fmt.Errorf("记录健康指标失败: %v", err)
}
return map[string]interface{}{
"metric_id": metricID,
"metric_type": metricType,
"value1": value1,
"value2": value2,
"unit": unit,
"message": "健康指标已记录",
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// HealthMetricsTool 查询健康指标记录
type HealthMetricsTool struct {
DB *gorm.DB
}
func (t *HealthMetricsTool) Name() string { return "query_health_metrics" }
func (t *HealthMetricsTool) Description() string {
return "查询患者健康指标记录(血压、血糖、心率、体温),支持类型过滤,按时间倒序返回"
}
func (t *HealthMetricsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "patient_id", Type: "string", Description: "患者ID(医生使用,患者无需传入)", Required: false},
{Name: "metric_type", Type: "string", Description: "指标类型过滤(可选)", Required: false, Enum: []string{"blood_pressure", "blood_glucose", "heart_rate", "body_temperature"}},
{Name: "limit", Type: "number", Description: "返回数量,默认20", Required: false},
}
}
func (t *HealthMetricsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
targetID := userID
if pid, ok := params["patient_id"].(string); ok && pid != "" {
if userRole == "patient" && pid != userID {
return nil, fmt.Errorf("患者只能查看自己的健康指标")
}
targetID = pid
}
if targetID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
limit := 20
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 100 {
limit = 100
}
}
metricType, _ := params["metric_type"].(string)
query := "SELECT id, metric_type, value1, value2, unit, notes, recorded_at, created_at " +
"FROM health_metrics WHERE user_id = ?"
args := []interface{}{targetID}
if metricType != "" {
query += " AND metric_type = ?"
args = append(args, metricType)
}
query += " ORDER BY recorded_at DESC LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"metrics": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"metrics": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// IncomeRecordsTool 查询医生收入明细
type IncomeRecordsTool struct {
DB *gorm.DB
}
func (t *IncomeRecordsTool) Name() string { return "query_income_records" }
func (t *IncomeRecordsTool) Description() string {
return "查询医生收入明细记录,包含收入类型、金额、患者姓名、状态,支持日期过滤"
}
func (t *IncomeRecordsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "start_date", Type: "string", Description: "开始日期(可选,格式YYYY-MM-DD)", Required: false},
{Name: "end_date", Type: "string", Description: "结束日期(可选,格式YYYY-MM-DD)", Required: false},
{Name: "limit", Type: "number", Description: "返回数量,默认20,最大100", Required: false},
}
}
func (t *IncomeRecordsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
if userRole != "doctor" {
return nil, fmt.Errorf("仅医生可查看收入明细")
}
// 查询医生ID
var doctorID string
t.DB.WithContext(ctx).Raw("SELECT id FROM doctors WHERE user_id = ?", userID).Scan(&doctorID)
if doctorID == "" {
return nil, fmt.Errorf("医生信息不存在")
}
limit := 20
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 100 {
limit = 100
}
}
startDate, _ := params["start_date"].(string)
endDate, _ := params["end_date"].(string)
query := "SELECT id, consult_id, income_type, amount, status, patient_name, settled_at, created_at " +
"FROM doctor_incomes WHERE doctor_id = ?"
args := []interface{}{doctorID}
if startDate != "" {
query += " AND created_at >= ?"
args = append(args, startDate)
}
if endDate != "" {
query += " AND created_at <= ?"
args = append(args, endDate+" 23:59:59")
}
query += " ORDER BY created_at DESC LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"records": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"records": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"time"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// IncomeStatsTool 查询医生收入统计
type IncomeStatsTool struct {
DB *gorm.DB
}
func (t *IncomeStatsTool) Name() string { return "query_income_stats" }
func (t *IncomeStatsTool) Description() string {
return "查询医生收入统计:可提现余额、本月收入、本月问诊量,仅医生可用"
}
func (t *IncomeStatsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{}
}
func (t *IncomeStatsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
if userRole != "doctor" {
return nil, fmt.Errorf("仅医生可查看收入统计")
}
// 查询医生ID
var doctorID string
t.DB.WithContext(ctx).Raw("SELECT id FROM doctors WHERE user_id = ?", userID).Scan(&doctorID)
if doctorID == "" {
return nil, fmt.Errorf("医生信息不存在")
}
var totalBalance int64
var monthIncome int64
var monthConsults int64
// 可提现余额(已结算未提现)
t.DB.WithContext(ctx).Raw(
"SELECT COALESCE(SUM(amount), 0) FROM doctor_incomes WHERE doctor_id = ? AND status = 'settled'",
doctorID,
).Scan(&totalBalance)
// 本月收入
startOfMonth := time.Now().Format("2006-01") + "-01"
t.DB.WithContext(ctx).Raw(
"SELECT COALESCE(SUM(amount), 0) FROM doctor_incomes WHERE doctor_id = ? AND created_at >= ?",
doctorID, startOfMonth,
).Scan(&monthIncome)
// 本月问诊量
t.DB.WithContext(ctx).Raw(
"SELECT COUNT(*) FROM doctor_incomes WHERE doctor_id = ? AND created_at >= ?",
doctorID, startOfMonth,
).Scan(&monthConsults)
return map[string]interface{}{
"total_balance": totalBalance,
"month_income": monthIncome,
"month_consults": monthConsults,
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// LabReportsTool 查询检验报告
type LabReportsTool struct {
DB *gorm.DB
}
func (t *LabReportsTool) Name() string { return "query_lab_reports" }
func (t *LabReportsTool) Description() string {
return "查询患者的检验报告列表,包括AI解读结果"
}
func (t *LabReportsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "patient_id", Type: "string", Description: "患者ID(医生使用,患者无需传入)", Required: false},
{Name: "limit", Type: "number", Description: "返回数量,默认10", Required: false},
}
}
func (t *LabReportsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
targetID := userID
if pid, ok := params["patient_id"].(string); ok && pid != "" {
if userRole == "patient" && pid != userID {
return nil, fmt.Errorf("患者只能查看自己的检验报告")
}
targetID = pid
}
if targetID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
limit := 10
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 30 {
limit = 30
}
}
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(`
SELECT id, title, report_date, file_url, file_type, category,
ai_interpret, created_at
FROM lab_reports
WHERE user_id = ? AND deleted_at IS NULL
ORDER BY report_date DESC, created_at DESC LIMIT ?`, targetID, limit).Rows()
if err != nil {
return map[string]interface{}{"reports": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"reports": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// MedicineCatalogTool 搜索药品目录
type MedicineCatalogTool struct {
DB *gorm.DB
}
func (t *MedicineCatalogTool) Name() string { return "search_medicine_catalog" }
func (t *MedicineCatalogTool) Description() string {
return "搜索药品目录,查询药品名称、规格、库存、价格等信息,为开方提供数据支持"
}
func (t *MedicineCatalogTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "keyword", Type: "string", Description: "药品名称或关键词", Required: true},
{Name: "category", Type: "string", Description: "药品分类过滤(可选)", Required: false},
{Name: "limit", Type: "number", Description: "返回数量,默认10", Required: false},
}
}
func (t *MedicineCatalogTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userRole != "doctor" && userRole != "admin" {
return nil, fmt.Errorf("仅医生和管理员可搜索药品目录")
}
keyword, ok := params["keyword"].(string)
if !ok || keyword == "" {
return nil, fmt.Errorf("keyword 必填")
}
limit := 10
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 30 {
limit = 30
}
}
category, _ := params["category"].(string)
query := "SELECT id, name, generic_name, specification, manufacturer, " +
"unit, price, stock, category, status, usage_method, contraindication " +
"FROM medicines " +
"WHERE (name ILIKE ? OR generic_name ILIKE ?) AND status = 'active'"
searchPattern := "%" + keyword + "%"
args := []interface{}{searchPattern, searchPattern}
if category != "" {
query += " AND category = ?"
args = append(args, category)
}
query += " ORDER BY name ASC LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"medicines": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"medicines": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// OrderDetailTool 查询支付订单详情
type OrderDetailTool struct {
DB *gorm.DB
}
func (t *OrderDetailTool) Name() string { return "query_order_detail" }
func (t *OrderDetailTool) Description() string {
return "查询支付订单详情,包含订单号、金额、状态、支付方式、关联业务等完整信息"
}
func (t *OrderDetailTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "order_id", Type: "string", Description: "订单ID", Required: true},
}
}
func (t *OrderDetailTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
orderID, _ := params["order_id"].(string)
if orderID == "" {
return nil, fmt.Errorf("请提供订单ID")
}
query := "SELECT id, order_no, user_id, order_type, related_id, amount, status, " +
"payment_method, transaction_id, paid_at, refunded_at, expire_at, remark, created_at " +
"FROM payment_orders WHERE id = ? AND deleted_at IS NULL"
args := []interface{}{orderID}
// 患者只能查自己的订单
if userRole == "patient" {
query += " AND user_id = ?"
args = append(args, userID)
}
var result map[string]interface{}
row := t.DB.WithContext(ctx).Raw(query, args...)
rows, err := row.Rows()
if err != nil {
return nil, fmt.Errorf("查询订单失败")
}
defer rows.Close()
cols, _ := rows.Columns()
if rows.Next() {
result = make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
result[col] = vals[i]
}
}
}
if result == nil {
return nil, fmt.Errorf("订单不存在")
}
return result, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// OrderListTool 查询支付订单列表
type OrderListTool struct {
DB *gorm.DB
}
func (t *OrderListTool) Name() string { return "query_order_list" }
func (t *OrderListTool) Description() string {
return "查询支付订单列表:患者查自己的订单,管理员查所有订单,支持按状态和类型过滤"
}
func (t *OrderListTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "status", Type: "string", Description: "订单状态过滤(可选)", Required: false, Enum: []string{"pending", "paid", "refunded", "cancelled", "completed"}},
{Name: "order_type", Type: "string", Description: "订单类型过滤(可选)", Required: false, Enum: []string{"consult", "prescription", "pharmacy"}},
{Name: "limit", Type: "number", Description: "返回数量,默认10,最大50", Required: false},
}
}
func (t *OrderListTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
limit := 10
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 50 {
limit = 50
}
}
status, _ := params["status"].(string)
orderType, _ := params["order_type"].(string)
query := "SELECT id, order_no, order_type, related_id, amount, status, payment_method, paid_at, created_at " +
"FROM payment_orders WHERE deleted_at IS NULL"
args := []interface{}{}
switch userRole {
case "patient":
query += " AND user_id = ?"
args = append(args, userID)
case "admin":
// 管理员查所有
default:
return nil, fmt.Errorf("无权限查询订单")
}
if status != "" {
query += " AND status = ?"
args = append(args, status)
}
if orderType != "" {
query += " AND order_type = ?"
args = append(args, orderType)
}
query += " ORDER BY created_at DESC LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"orders": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"orders": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PatientProfileTool 查询患者健康档案
type PatientProfileTool struct {
DB *gorm.DB
}
func (t *PatientProfileTool) Name() string { return "query_patient_profile" }
func (t *PatientProfileTool) Description() string {
return "查询患者健康档案(基本信息、过敏史、病史、保险信息),患者查自己的,医生按patient_id查"
}
func (t *PatientProfileTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "patient_id", Type: "string", Description: "患者ID(医生/管理员使用,患者无需传入自动查自己的)", Required: false},
}
}
func (t *PatientProfileTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
targetID := userID
if pid, ok := params["patient_id"].(string); ok && pid != "" {
if userRole == "patient" && pid != userID {
return nil, fmt.Errorf("患者只能查看自己的健康档案")
}
targetID = pid
}
if targetID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
// 查询用户基本信息
userInfo := make(map[string]interface{})
uRows, err := t.DB.WithContext(ctx).Raw(`
SELECT id, real_name, phone, gender, age, avatar, role, status, created_at
FROM users WHERE id = ?`, targetID).Rows()
if err != nil {
return nil, fmt.Errorf("用户不存在")
}
defer uRows.Close()
if uRows.Next() {
uCols, _ := uRows.Columns()
uVals := make([]interface{}, len(uCols))
uPtrs := make([]interface{}, len(uCols))
for i := range uVals {
uPtrs[i] = &uVals[i]
}
if err := uRows.Scan(uPtrs...); err == nil {
for i, col := range uCols {
userInfo[col] = uVals[i]
}
}
} else {
return nil, fmt.Errorf("用户不存在")
}
// 查询健康档案
profile := make(map[string]interface{})
pRows, err := t.DB.WithContext(ctx).Raw(`
SELECT gender, birth_date, medical_history, allergy_history,
emergency_contact, insurance_type, insurance_no
FROM patient_profiles WHERE user_id = ?`, targetID).Rows()
if err == nil {
defer pRows.Close()
if pRows.Next() {
pCols, _ := pRows.Columns()
pVals := make([]interface{}, len(pCols))
pPtrs := make([]interface{}, len(pCols))
for i := range pVals {
pPtrs[i] = &pVals[i]
}
if err := pRows.Scan(pPtrs...); err == nil {
for i, col := range pCols {
profile[col] = pVals[i]
}
}
}
}
// 查询最近问诊数
var consultCount int64
t.DB.WithContext(ctx).Raw("SELECT COUNT(*) FROM consultations WHERE patient_id = ? AND deleted_at IS NULL", targetID).Scan(&consultCount)
return map[string]interface{}{
"user": userInfo,
"health_profile": profile,
"consult_count": consultCount,
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PrescriptionDetailTool 查询处方详情
type PrescriptionDetailTool struct {
DB *gorm.DB
}
func (t *PrescriptionDetailTool) Name() string { return "query_prescription_detail" }
func (t *PrescriptionDetailTool) Description() string {
return "查询处方详情,包括药品明细、用法用量、状态和费用,支持处方ID或处方编号(RX开头)查询"
}
func (t *PrescriptionDetailTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "prescription_id", Type: "string", Description: "处方ID(UUID)或处方编号(RX开头)", Required: true},
}
}
func (t *PrescriptionDetailTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
prescriptionID, ok := params["prescription_id"].(string)
if !ok || prescriptionID == "" {
return nil, fmt.Errorf("prescription_id 必填")
}
// 支持处方编号查询
resolvedID := prescriptionID
if len(prescriptionID) >= 2 && prescriptionID[:2] == "RX" {
var id string
if err := t.DB.WithContext(ctx).Raw("SELECT id FROM prescriptions WHERE prescription_no = ?", prescriptionID).Scan(&id).Error; err != nil || id == "" {
return nil, fmt.Errorf("处方编号 %s 不存在", prescriptionID)
}
resolvedID = id
}
// 查询处方主记录
detailRows, err := t.DB.WithContext(ctx).Raw(`
SELECT p.id, p.prescription_no, p.consult_id, p.patient_id, p.patient_name,
p.patient_gender, p.patient_age, p.diagnosis, p.allergy_history,
p.remark, p.total_amount, p.status, p.created_at,
d.name as doctor_name, d.title as doctor_title, dep.name as department_name
FROM prescriptions p
LEFT JOIN doctors d ON p.doctor_id = d.id
LEFT JOIN departments dep ON d.department_id = dep.id
WHERE p.id = ? AND p.deleted_at IS NULL`, resolvedID).Rows()
if err != nil {
return nil, fmt.Errorf("处方不存在: %s", prescriptionID)
}
defer detailRows.Close()
cols, _ := detailRows.Columns()
if !detailRows.Next() {
return nil, fmt.Errorf("处方不存在: %s", prescriptionID)
}
detail := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := detailRows.Scan(ptrs...); err != nil {
return nil, fmt.Errorf("处方不存在: %s", prescriptionID)
}
for i, col := range cols {
detail[col] = vals[i]
}
// 权限校验
if userRole == "patient" && detail["patient_id"] != userID {
return nil, fmt.Errorf("无权查看该处方")
}
// 查询药品明细
var items []map[string]interface{}
itemRows, err := t.DB.WithContext(ctx).Raw(`
SELECT medicine_name, specification, usage, dosage, frequency,
days, quantity, unit, price, note
FROM prescription_items
WHERE prescription_id = ?
ORDER BY created_at ASC`, resolvedID).Rows()
if err == nil {
defer itemRows.Close()
itemCols, _ := itemRows.Columns()
for itemRows.Next() {
item := make(map[string]interface{})
iVals := make([]interface{}, len(itemCols))
iPtrs := make([]interface{}, len(itemCols))
for i := range iVals {
iPtrs[i] = &iVals[i]
}
if err := itemRows.Scan(iPtrs...); err == nil {
for i, col := range itemCols {
item[col] = iVals[i]
}
items = append(items, item)
}
}
}
detail["items"] = items
detail["drug_count"] = len(items)
return detail, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// PrescriptionListTool 查询处方列表
type PrescriptionListTool struct {
DB *gorm.DB
}
func (t *PrescriptionListTool) Name() string { return "query_prescription_list" }
func (t *PrescriptionListTool) Description() string {
return "查询处方列表:患者查自己的处方,医生查自己开的处方,支持按状态过滤"
}
func (t *PrescriptionListTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "status", Type: "string", Description: "处方状态过滤(可选):pending/signed/dispensed/completed/cancelled", Required: false},
{Name: "limit", Type: "number", Description: "返回数量,默认10,最大50", Required: false},
}
}
func (t *PrescriptionListTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
limit := 10
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 50 {
limit = 50
}
}
status, _ := params["status"].(string)
query := "SELECT p.id, p.prescription_no, p.patient_name, p.diagnosis, " +
"p.total_amount, p.status, p.created_at, " +
"d.name as doctor_name, dep.name as department_name, " +
"(SELECT COUNT(*) FROM prescription_items pi WHERE pi.prescription_id = p.id) as drug_count " +
"FROM prescriptions p " +
"LEFT JOIN doctors d ON p.doctor_id = d.id " +
"LEFT JOIN departments dep ON d.department_id = dep.id " +
"WHERE p.deleted_at IS NULL"
args := []interface{}{}
switch userRole {
case "patient":
query += " AND p.patient_id = ?"
args = append(args, userID)
case "doctor":
var doctorID string
t.DB.WithContext(ctx).Raw("SELECT id FROM doctors WHERE user_id = ?", userID).Scan(&doctorID)
if doctorID == "" {
return map[string]interface{}{"prescriptions": []interface{}{}, "total": 0}, nil
}
query += " AND p.doctor_id = ?"
args = append(args, doctorID)
case "admin":
// 管理员查所有
default:
return nil, fmt.Errorf("无权限查询处方")
}
if status != "" {
query += " AND p.status = ?"
args = append(args, status)
}
query += " ORDER BY p.created_at DESC LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"prescriptions": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"prescriptions": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
)
// CreateRenewalFn 创建续方申请回调,由 main.go 注入
var CreateRenewalFn func(ctx context.Context, userID, chronicID, diseaseName, reason string, medicines []string) (string, error)
// CreateRenewalTool 患者创建续方申请
type CreateRenewalTool struct{}
func (t *CreateRenewalTool) Name() string { return "create_renewal_request" }
func (t *CreateRenewalTool) Description() string {
return "患者发起续方申请,需要指定疾病名称和续方药品列表,提交后等待医生审批"
}
func (t *CreateRenewalTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "disease_name", Type: "string", Description: "疾病名称", Required: true},
{Name: "medicines", Type: "array", Description: "续方药品名称列表", Required: true},
{Name: "chronic_id", Type: "string", Description: "关联的慢病记录ID(可选)", Required: false},
{Name: "reason", Type: "string", Description: "续方原因(可选)", Required: false},
}
}
func (t *CreateRenewalTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
if userRole != "patient" {
return nil, fmt.Errorf("仅患者可发起续方申请")
}
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
diseaseName, ok := params["disease_name"].(string)
if !ok || diseaseName == "" {
return nil, fmt.Errorf("disease_name 必填")
}
// 解析药品列表
var medicines []string
if arr, ok := params["medicines"].([]interface{}); ok {
for _, v := range arr {
if s, ok := v.(string); ok {
medicines = append(medicines, s)
}
}
}
if len(medicines) == 0 {
return nil, fmt.Errorf("medicines 必填且至少包含一种药品")
}
chronicID, _ := params["chronic_id"].(string)
reason, _ := params["reason"].(string)
if CreateRenewalFn == nil {
return nil, fmt.Errorf("续方服务未初始化")
}
renewalID, err := CreateRenewalFn(ctx, userID, chronicID, diseaseName, reason, medicines)
if err != nil {
return nil, fmt.Errorf("创建续方申请失败: %v", err)
}
return map[string]interface{}{
"renewal_id": renewalID,
"disease_name": diseaseName,
"status": "pending",
"message": "续方申请已提交,等待医生审批",
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// RenewalRequestsTool 查询续方申请列表
type RenewalRequestsTool struct {
DB *gorm.DB
}
func (t *RenewalRequestsTool) Name() string { return "query_renewal_requests" }
func (t *RenewalRequestsTool) Description() string {
return "查询续方申请列表:患者查自己的续方申请(含状态和AI建议),医生查待审批的续方申请"
}
func (t *RenewalRequestsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "status", Type: "string", Description: "状态过滤(可选):pending/approved/rejected", Required: false, Enum: []string{"pending", "approved", "rejected"}},
}
}
func (t *RenewalRequestsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userID == "" {
return nil, fmt.Errorf("未获取到用户信息")
}
status, _ := params["status"].(string)
query := "SELECT r.id, r.disease_name, r.medicines, r.reason, r.status, " +
"r.doctor_note, r.ai_advice, r.created_at, " +
"u.real_name as patient_name " +
"FROM renewal_requests r " +
"LEFT JOIN users u ON r.user_id = u.id " +
"WHERE r.deleted_at IS NULL"
args := []interface{}{}
switch userRole {
case "patient":
query += " AND r.user_id = ?"
args = append(args, userID)
case "doctor":
// 医生查所有待审批的
if status == "" {
status = "pending"
}
case "admin":
// 管理员查所有
default:
return nil, fmt.Errorf("无权限查询续方申请")
}
if status != "" {
query += " AND r.status = ?"
args = append(args, status)
}
query += " ORDER BY r.created_at DESC LIMIT 30"
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"renewals": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"renewals": results,
"total": len(results),
}, nil
}
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// SystemLogsTool 查询系统操作日志(管理员专用)
type SystemLogsTool struct {
DB *gorm.DB
}
func (t *SystemLogsTool) Name() string { return "query_system_logs" }
func (t *SystemLogsTool) Description() string {
return "查询系统操作日志,支持按操作类型、资源过滤,仅管理员可用"
}
func (t *SystemLogsTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "action", Type: "string", Description: "操作类型过滤(可选,如login/create/update/delete)", Required: false},
{Name: "resource", Type: "string", Description: "资源类型过滤(可选,如user/doctor/prescription)", Required: false},
{Name: "limit", Type: "number", Description: "返回数量,默认20,最大100", Required: false},
}
}
func (t *SystemLogsTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
if userRole != "admin" {
return nil, fmt.Errorf("仅管理员可查看系统日志")
}
limit := 20
if v, ok := params["limit"].(float64); ok && v > 0 {
limit = int(v)
if limit > 100 {
limit = 100
}
}
action, _ := params["action"].(string)
resource, _ := params["resource"].(string)
query := "SELECT sl.id, sl.user_id, sl.action, sl.resource, sl.detail, sl.ip, sl.created_at, " +
"u.real_name as user_name " +
"FROM system_logs sl LEFT JOIN users u ON sl.user_id = u.id WHERE 1=1"
args := []interface{}{}
if action != "" {
query += " AND sl.action = ?"
args = append(args, action)
}
if resource != "" {
query += " AND sl.resource LIKE ?"
args = append(args, "%"+resource+"%")
}
query += " ORDER BY sl.created_at DESC LIMIT ?"
args = append(args, limit)
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(query, args...).Rows()
if err != nil {
return map[string]interface{}{"logs": []interface{}{}, "total": 0}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"logs": results,
"total": len(results),
}, nil
}
This diff is collapsed.
package tools
import (
"context"
"fmt"
"internet-hospital/pkg/agent"
"gorm.io/gorm"
)
// WaitingQueueTool 查询医生的等候队列
type WaitingQueueTool struct {
DB *gorm.DB
}
func (t *WaitingQueueTool) Name() string { return "query_waiting_queue" }
func (t *WaitingQueueTool) Description() string {
return "查询当前医生的等候队列,显示等候中的患者数量和列表"
}
func (t *WaitingQueueTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{}
}
func (t *WaitingQueueTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
userRole, _ := ctx.Value(agent.ContextKeyUserRole).(string)
userID, _ := ctx.Value(agent.ContextKeyUserID).(string)
if userRole != "doctor" {
return nil, fmt.Errorf("仅医生可查看等候队列")
}
var doctorID string
if err := t.DB.WithContext(ctx).Raw("SELECT id FROM doctors WHERE user_id = ?", userID).Scan(&doctorID).Error; err != nil || doctorID == "" {
return map[string]interface{}{"waiting_count": 0, "patients": []interface{}{}}, nil
}
var results []map[string]interface{}
rows, err := t.DB.WithContext(ctx).Raw(`
SELECT c.id, c.serial_number, c.chief_complaint, c.type, c.created_at,
u.real_name as patient_name,
EXTRACT(EPOCH FROM (NOW() - c.created_at))::int as wait_seconds
FROM consultations c
LEFT JOIN users u ON c.patient_id = u.id
WHERE c.doctor_id = ? AND c.status = 'pending' AND c.deleted_at IS NULL
ORDER BY c.created_at ASC`, doctorID).Rows()
if err != nil {
return map[string]interface{}{"waiting_count": 0, "patients": []interface{}{}}, nil
}
defer rows.Close()
cols, _ := rows.Columns()
for rows.Next() {
row := make(map[string]interface{})
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err == nil {
for i, col := range cols {
row[col] = vals[i]
}
results = append(results, row)
}
}
return map[string]interface{}{
"waiting_count": len(results),
"patients": results,
}, nil
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment