Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
I
Internet-hospital
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
yuguo
Internet-hospital
Commits
626473e4
Commit
626473e4
authored
Mar 02, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
ef9bb5d9
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1117 additions
and
159 deletions
+1117
-159
server/internal/agent/handler.go
server/internal/agent/handler.go
+94
-1
server/internal/service/admin/agent_handler.go
server/internal/service/admin/agent_handler.go
+87
-0
server/internal/service/admin/handler.go
server/internal/service/admin/handler.go
+6
-0
server/internal/service/admin/workflow_handler.go
server/internal/service/admin/workflow_handler.go
+65
-0
server/internal/service/consult/service.go
server/internal/service/consult/service.go
+30
-84
web/src/api/agent.ts
web/src/api/agent.ts
+163
-0
web/src/app/(main)/admin/agents/page.tsx
web/src/app/(main)/admin/agents/page.tsx
+4
-18
web/src/app/(main)/admin/knowledge/page.tsx
web/src/app/(main)/admin/knowledge/page.tsx
+9
-16
web/src/app/(main)/admin/tools/page.tsx
web/src/app/(main)/admin/tools/page.tsx
+3
-8
web/src/app/(main)/admin/workflows/page.tsx
web/src/app/(main)/admin/workflows/page.tsx
+10
-32
升级方案_智能体平台融合.md
升级方案_智能体平台融合.md
+646
-0
No files found.
server/internal/agent/handler.go
View file @
626473e4
package
internalagent
import
(
"encoding/json"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"internet-hospital/internal/model"
"internet-hospital/pkg/agent"
"internet-hospital/pkg/database"
)
// Handler Agent HTTP处理器
...
...
@@ -21,6 +27,7 @@ func (h *Handler) RegisterRoutes(r gin.IRouter) {
g
.
GET
(
"/sessions"
,
h
.
ListSessions
)
g
.
DELETE
(
"/session/:session_id"
,
h
.
DeleteSession
)
g
.
GET
(
"/list"
,
h
.
ListAgents
)
g
.
GET
(
"/tools"
,
h
.
ListTools
)
}
func
(
h
*
Handler
)
Chat
(
c
*
gin
.
Context
)
{
...
...
@@ -54,10 +61,96 @@ func (h *Handler) ListAgents(c *gin.Context) {
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
h
.
svc
.
ListAgents
()})
}
// ListSessions 获取用户的 Agent 会话列表
func
(
h
*
Handler
)
ListSessions
(
c
*
gin
.
Context
)
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
[]
interface
{}{}})
userID
,
_
:=
c
.
Get
(
"user_id"
)
agentID
:=
c
.
Query
(
"agent_id"
)
var
sessions
[]
model
.
AgentSession
query
:=
database
.
GetDB
()
.
Where
(
"user_id = ?"
,
userID
)
.
Order
(
"updated_at DESC"
)
if
agentID
!=
""
{
query
=
query
.
Where
(
"agent_id = ?"
,
agentID
)
}
query
.
Find
(
&
sessions
)
type
SessionSummary
struct
{
model
.
AgentSession
LastMessage
string
`json:"last_message"`
}
result
:=
make
([]
SessionSummary
,
0
,
len
(
sessions
))
for
_
,
s
:=
range
sessions
{
var
history
[]
map
[
string
]
string
json
.
Unmarshal
([]
byte
(
s
.
History
),
&
history
)
lastMsg
:=
""
if
len
(
history
)
>
0
{
lastMsg
=
history
[
len
(
history
)
-
1
][
"content"
]
if
len
(
lastMsg
)
>
60
{
lastMsg
=
lastMsg
[
:
60
]
+
"..."
}
}
result
=
append
(
result
,
SessionSummary
{
s
,
lastMsg
})
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
result
})
}
// DeleteSession 删除会话
func
(
h
*
Handler
)
DeleteSession
(
c
*
gin
.
Context
)
{
userID
,
_
:=
c
.
Get
(
"user_id"
)
sessionID
:=
c
.
Param
(
"session_id"
)
database
.
GetDB
()
.
Where
(
"session_id = ? AND user_id = ?"
,
sessionID
,
userID
)
.
Delete
(
&
model
.
AgentSession
{})
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"message"
:
"ok"
})
}
// ListTools 获取所有已注册工具列表
func
(
h
*
Handler
)
ListTools
(
c
*
gin
.
Context
)
{
registry
:=
agent
.
GetRegistry
()
allTools
:=
registry
.
All
()
type
ToolInfo
struct
{
ID
string
`json:"id"`
Name
string
`json:"name"`
Description
string
`json:"description"`
Category
string
`json:"category"`
Parameters
map
[
string
]
interface
{}
`json:"parameters"`
IsEnabled
bool
`json:"is_enabled"`
CreatedAt
string
`json:"created_at"`
}
categoryMap
:=
map
[
string
]
string
{
"query_symptom_knowledge"
:
"knowledge"
,
"recommend_department"
:
"recommendation"
,
"query_medical_record"
:
"medical"
,
"search_medical_knowledge"
:
"knowledge"
,
"query_drug"
:
"pharmacy"
,
"check_drug_interaction"
:
"safety"
,
"check_contraindication"
:
"safety"
,
"calculate_dosage"
:
"pharmacy"
,
"generate_follow_up_plan"
:
"follow_up"
,
"send_notification"
:
"notification"
,
}
result
:=
make
([]
ToolInfo
,
0
,
len
(
allTools
))
i
:=
1
for
name
,
tool
:=
range
allTools
{
params
:=
make
(
map
[
string
]
interface
{})
for
_
,
p
:=
range
tool
.
Parameters
()
{
params
[
p
.
Name
]
=
p
.
Type
}
category
:=
categoryMap
[
name
]
if
category
==
""
{
category
=
"other"
}
result
=
append
(
result
,
ToolInfo
{
ID
:
fmt
.
Sprintf
(
"%d"
,
i
),
Name
:
name
,
Description
:
tool
.
Description
(),
Category
:
category
,
Parameters
:
params
,
IsEnabled
:
true
,
CreatedAt
:
"2026-01-01"
,
})
i
++
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
result
})
}
server/internal/service/admin/agent_handler.go
0 → 100644
View file @
626473e4
package
admin
import
(
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"internet-hospital/internal/model"
"internet-hospital/pkg/database"
)
// GetAgentExecutionLogs 获取 Agent 执行日志(分页)
func
(
h
*
Handler
)
GetAgentExecutionLogs
(
c
*
gin
.
Context
)
{
agentID
:=
c
.
Query
(
"agent_id"
)
start
:=
c
.
Query
(
"start"
)
end
:=
c
.
Query
(
"end"
)
page
:=
1
pageSize
:=
20
if
p
:=
c
.
Query
(
"page"
);
p
!=
""
{
if
v
,
err
:=
strconv
.
Atoi
(
p
);
err
==
nil
&&
v
>
0
{
page
=
v
}
}
if
ps
:=
c
.
Query
(
"page_size"
);
ps
!=
""
{
if
v
,
err
:=
strconv
.
Atoi
(
ps
);
err
==
nil
&&
v
>
0
&&
v
<=
100
{
pageSize
=
v
}
}
query
:=
database
.
GetDB
()
.
Model
(
&
model
.
AgentExecutionLog
{})
if
agentID
!=
""
{
query
=
query
.
Where
(
"agent_id = ?"
,
agentID
)
}
if
start
!=
""
{
query
=
query
.
Where
(
"created_at >= ?"
,
start
)
}
if
end
!=
""
{
query
=
query
.
Where
(
"created_at <= ?"
,
end
)
}
var
total
int64
query
.
Count
(
&
total
)
var
logs
[]
model
.
AgentExecutionLog
query
.
Order
(
"created_at DESC"
)
.
Offset
((
page
-
1
)
*
pageSize
)
.
Limit
(
pageSize
)
.
Find
(
&
logs
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
gin
.
H
{
"list"
:
logs
,
"total"
:
total
,
"page"
:
page
,
"page_size"
:
pageSize
,
}})
}
// GetAgentStats 获取 Agent 执行统计
func
(
h
*
Handler
)
GetAgentStats
(
c
*
gin
.
Context
)
{
start
:=
c
.
Query
(
"start"
)
end
:=
c
.
Query
(
"end"
)
type
AgentStat
struct
{
AgentID
string
`json:"agent_id"`
Count
int64
`json:"count"`
AvgIterations
float64
`json:"avg_iterations"`
AvgTokens
float64
`json:"avg_tokens"`
SuccessRate
float64
`json:"success_rate"`
}
db
:=
database
.
GetDB
()
query
:=
db
.
Model
(
&
model
.
AgentExecutionLog
{})
if
start
!=
""
{
query
=
query
.
Where
(
"created_at >= ?"
,
start
)
}
if
end
!=
""
{
query
=
query
.
Where
(
"created_at <= ?"
,
end
)
}
var
stats
[]
AgentStat
query
.
Select
(
"agent_id, COUNT(*) as count, AVG(iterations) as avg_iterations, AVG(total_tokens) as avg_tokens, SUM(CASE WHEN success THEN 1 ELSE 0 END)::float / COUNT(*) as success_rate"
)
.
Group
(
"agent_id"
)
.
Scan
(
&
stats
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
stats
})
}
server/internal/service/admin/handler.go
View file @
626473e4
...
...
@@ -102,7 +102,13 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
// 工作流管理
adm
.
GET
(
"/workflows"
,
h
.
ListWorkflows
)
adm
.
POST
(
"/workflows"
,
h
.
CreateWorkflow
)
adm
.
PUT
(
"/workflows/:id"
,
h
.
UpdateWorkflow
)
adm
.
PUT
(
"/workflows/:id/publish"
,
h
.
PublishWorkflow
)
adm
.
GET
(
"/workflow/executions"
,
h
.
ListWorkflowExecutions
)
// Agent 执行监控
adm
.
GET
(
"/agent/logs"
,
h
.
GetAgentExecutionLogs
)
adm
.
GET
(
"/agent/stats"
,
h
.
GetAgentStats
)
}
}
...
...
server/internal/service/admin/workflow_handler.go
View file @
626473e4
...
...
@@ -2,6 +2,7 @@ package admin
import
(
"net/http"
"strconv"
"github.com/gin-gonic/gin"
...
...
@@ -42,6 +43,33 @@ func (h *Handler) CreateWorkflow(c *gin.Context) {
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
wf
})
}
// UpdateWorkflow 更新工作流(名称/描述/定义)
func
(
h
*
Handler
)
UpdateWorkflow
(
c
*
gin
.
Context
)
{
var
req
struct
{
Name
string
`json:"name"`
Description
string
`json:"description"`
Definition
string
`json:"definition"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
c
.
JSON
(
http
.
StatusBadRequest
,
gin
.
H
{
"error"
:
err
.
Error
()})
return
}
updates
:=
map
[
string
]
interface
{}{}
if
req
.
Definition
!=
""
{
updates
[
"definition"
]
=
req
.
Definition
}
if
req
.
Name
!=
""
{
updates
[
"name"
]
=
req
.
Name
}
if
req
.
Description
!=
""
{
updates
[
"description"
]
=
req
.
Description
}
database
.
GetDB
()
.
Model
(
&
model
.
WorkflowDefinition
{})
.
Where
(
"id = ?"
,
c
.
Param
(
"id"
))
.
Updates
(
updates
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"message"
:
"ok"
})
}
// PublishWorkflow 发布工作流
func
(
h
*
Handler
)
PublishWorkflow
(
c
*
gin
.
Context
)
{
database
.
GetDB
()
.
Model
(
&
model
.
WorkflowDefinition
{})
.
...
...
@@ -49,3 +77,40 @@ func (h *Handler) PublishWorkflow(c *gin.Context) {
Update
(
"status"
,
"active"
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"message"
:
"ok"
})
}
// ListWorkflowExecutions 工作流执行记录列表
func
(
h
*
Handler
)
ListWorkflowExecutions
(
c
*
gin
.
Context
)
{
workflowID
:=
c
.
Query
(
"workflow_id"
)
page
:=
1
pageSize
:=
20
if
p
:=
c
.
Query
(
"page"
);
p
!=
""
{
if
v
,
err
:=
strconv
.
Atoi
(
p
);
err
==
nil
&&
v
>
0
{
page
=
v
}
}
if
ps
:=
c
.
Query
(
"page_size"
);
ps
!=
""
{
if
v
,
err
:=
strconv
.
Atoi
(
ps
);
err
==
nil
&&
v
>
0
&&
v
<=
100
{
pageSize
=
v
}
}
var
total
int64
query
:=
database
.
GetDB
()
.
Model
(
&
model
.
WorkflowExecution
{})
if
workflowID
!=
""
{
query
=
query
.
Where
(
"workflow_id = ?"
,
workflowID
)
}
query
.
Count
(
&
total
)
var
executions
[]
model
.
WorkflowExecution
query
.
Order
(
"created_at DESC"
)
.
Offset
((
page
-
1
)
*
pageSize
)
.
Limit
(
pageSize
)
.
Find
(
&
executions
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
gin
.
H
{
"list"
:
executions
,
"total"
:
total
,
"page"
:
page
,
"page_size"
:
pageSize
,
}})
}
server/internal/service/consult/service.go
View file @
626473e4
...
...
@@ -10,8 +10,8 @@ import (
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
internalagent
"internet-hospital/internal/agent"
"internet-hospital/internal/model"
"internet-hospital/pkg/ai"
"internet-hospital/pkg/database"
)
...
...
@@ -196,107 +196,53 @@ func (s *Service) CancelConsult(ctx context.Context, consultID string) error {
return
s
.
db
.
Model
(
&
model
.
Consultation
{})
.
Where
(
"id = ?"
,
consultID
)
.
Update
(
"status"
,
"cancelled"
)
.
Error
}
// AIAssist AI辅助分析(鉴别诊断/用药建议)
// AIAssist AI辅助分析(鉴别诊断/用药建议)
—— 通过 Agent 调用
func
(
s
*
Service
)
AIAssist
(
ctx
context
.
Context
,
consultID
string
,
scene
string
)
(
map
[
string
]
interface
{},
error
)
{
// 获取问诊信息
var
consult
model
.
Consultation
if
err
:=
s
.
db
.
Where
(
"id = ?"
,
consultID
)
.
First
(
&
consult
)
.
Error
;
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"问诊不存在"
)
}
// 获取对话消息
var
messages
[]
model
.
ConsultMessage
s
.
db
.
Where
(
"consult_id = ?"
,
consultID
)
.
Order
(
"created_at ASC"
)
.
Find
(
&
messages
)
// 获取预问诊信息
// 获取预问诊信息以提供上下文
var
preConsult
model
.
PreConsultation
s
.
db
.
Where
(
"consultation_id = ?"
,
consultID
)
.
First
(
&
preConsult
)
// 构建对话上下文
var
chatContext
string
if
preConsult
.
AIAnalysis
!=
""
{
chatContext
+=
"【预问诊AI分析报告】
\n
"
+
preConsult
.
AIAnalysis
+
"
\n\n
"
}
if
consult
.
ChiefComplaint
!=
""
{
chatContext
+=
"【主诉】"
+
consult
.
ChiefComplaint
+
"
\n\n
"
}
chatContext
+=
"【问诊对话记录】
\n
"
for
_
,
msg
:=
range
messages
{
role
:=
"患者"
if
msg
.
SenderType
==
"doctor"
{
role
=
"医生"
}
else
if
msg
.
SenderType
==
"system"
{
role
=
"系统"
agentCtx
:=
map
[
string
]
interface
{}{
"patient_id"
:
consult
.
PatientID
,
"consult_id"
:
consultID
,
"chief_complaint"
:
consult
.
ChiefComplaint
,
}
chatContext
+=
role
+
":"
+
msg
.
Content
+
"
\n
"
if
preConsult
.
AIAnalysis
!=
""
{
agentCtx
[
"pre_consult_analysis"
]
=
preConsult
.
AIAnalysis
}
// 获取场景对应的Prompt模板
prompt
:=
ai
.
GetActivePromptByScene
(
scene
)
if
prompt
==
""
{
var
agentID
,
message
string
switch
scene
{
case
"consult_diagnosis"
:
prompt
=
`你是一位资深的临床医学专家,请根据以下问诊信息进行鉴别诊断分析。
请以如下markdown格式输出:
## 初步诊断
(最可能的诊断,附简要理由)
## 鉴别诊断
1. **疾病名称1** - 可能性:高/中/低,依据:...
2. **疾病名称2** - 可能性:高/中/低,依据:...
3. **疾病名称3** - 可能性:高/中/低,依据:...
## 建议检查
1. 检查项目1 - 目的:...
2. 检查项目2 - 目的:...
## 注意事项
(需要特别关注的情况)`
agentID
=
"diagnosis_agent"
message
=
"请对患者当前情况进行诊断分析,提供鉴别诊断建议"
case
"consult_medication"
:
prompt
=
`你是一位资深的临床药学专家,请根据以下问诊信息给出用药建议。
请以如下markdown格式输出:
## 推荐用药方案
1. **药品名称1** - 规格:...,用法用量:...,疗程:...
2. **药品名称2** - 规格:...,用法用量:...,疗程:...
## 用药注意事项
1. 注意事项1
2. 注意事项2
## 禁忌与过敏提示
(相关药物禁忌和需要询问的过敏史)
## 随访建议
(用药后的观察要点和复诊建议)`
agentID
=
"prescription_agent"
message
=
"请根据患者情况给出用药建议,包括推荐药物、用法用量和注意事项"
default
:
return
nil
,
fmt
.
Errorf
(
"不支持的AI场景: %s"
,
scene
)
}
}
// 调用AI(使用统一接口,自动记录日志)
aiMessages
:=
[]
ai
.
ChatMessage
{
{
Role
:
"system"
,
Content
:
prompt
},
{
Role
:
"user"
,
Content
:
chatContext
},
agentSvc
:=
internalagent
.
GetService
()
output
,
err
:=
agentSvc
.
Chat
(
ctx
,
agentID
,
consult
.
DoctorID
,
""
,
message
,
agentCtx
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"AI分析失败: %w"
,
err
)
}
result
:=
ai
.
Call
(
ctx
,
ai
.
CallParams
{
Scene
:
scene
,
UserID
:
consult
.
DoctorID
,
Messages
:
aiMessages
,
RequestSummary
:
chatContext
,
})
if
result
.
Error
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"AI分析失败: %w"
,
result
.
Error
)
if
output
==
nil
{
return
nil
,
fmt
.
Errorf
(
"agent不存在: %s"
,
agentID
)
}
return
map
[
string
]
interface
{}{
"scene"
:
scene
,
"content"
:
result
.
Content
,
"response"
:
output
.
Response
,
"tool_calls"
:
output
.
ToolCalls
,
"iterations"
:
output
.
Iterations
,
"total_tokens"
:
output
.
TotalTokens
,
},
nil
}
...
...
web/src/api/agent.ts
0 → 100644
View file @
626473e4
import
{
get
,
post
,
put
,
del
}
from
'
./request
'
;
// ==================== 类型定义 ====================
export
interface
ToolCall
{
tool_name
:
string
;
call_id
:
string
;
arguments
:
string
;
result
:
{
success
:
boolean
;
data
?:
unknown
;
error
?:
string
};
success
:
boolean
;
}
export
interface
AgentOutput
{
response
:
string
;
session_id
?:
string
;
tool_calls
?:
ToolCall
[];
iterations
?:
number
;
total_tokens
?:
number
;
finish_reason
?:
string
;
}
export
interface
AgentSession
{
id
:
number
;
session_id
:
string
;
agent_id
:
string
;
user_id
:
string
;
history
:
string
;
context
:
string
;
status
:
string
;
last_message
?:
string
;
created_at
:
string
;
updated_at
:
string
;
}
export
interface
AgentExecutionLog
{
id
:
number
;
session_id
:
string
;
agent_id
:
string
;
user_id
:
string
;
input
:
string
;
output
:
string
;
tool_calls
:
string
;
iterations
:
number
;
total_tokens
:
number
;
duration_ms
:
number
;
finish_reason
:
string
;
success
:
boolean
;
error_message
:
string
;
created_at
:
string
;
}
export
interface
WorkflowExecution
{
id
:
number
;
execution_id
:
string
;
workflow_id
:
string
;
trigger_type
:
string
;
trigger_by
:
string
;
input
:
string
;
output
:
string
;
status
:
string
;
started_at
:
string
;
completed_at
:
string
;
}
export
interface
WorkflowCreateParams
{
workflow_id
:
string
;
name
:
string
;
description
?:
string
;
category
?:
string
;
definition
?:
string
;
}
export
interface
KnowledgeCollectionParams
{
name
:
string
;
description
?:
string
;
category
?:
string
;
}
export
interface
KnowledgeDocumentParams
{
collection_id
:
string
;
title
:
string
;
content
:
string
;
}
// ==================== Agent API ====================
export
const
agentApi
=
{
chat
:
(
agentId
:
string
,
params
:
{
session_id
?:
string
;
message
:
string
;
context
?:
Record
<
string
,
unknown
>
;
})
=>
post
<
AgentOutput
>
(
`/agent/
${
agentId
}
/chat`
,
params
),
listAgents
:
()
=>
get
<
{
id
:
string
;
name
:
string
;
description
:
string
}[]
>
(
'
/agent/list
'
),
listTools
:
()
=>
get
<
{
id
:
string
;
name
:
string
;
description
:
string
;
category
:
string
;
parameters
:
Record
<
string
,
unknown
>
;
is_enabled
:
boolean
;
created_at
:
string
}[]
>
(
'
/agent/tools
'
),
getSessions
:
(
agentId
?:
string
)
=>
get
<
AgentSession
[]
>
(
'
/agent/sessions
'
,
{
params
:
agentId
?
{
agent_id
:
agentId
}
:
{}
}),
deleteSession
:
(
sessionId
:
string
)
=>
del
<
null
>
(
`/agent/session/
${
sessionId
}
`
),
getExecutionLogs
:
(
params
:
{
agent_id
?:
string
;
start
?:
string
;
end
?:
string
;
page
?:
number
;
page_size
?:
number
;
})
=>
get
<
{
list
:
AgentExecutionLog
[];
total
:
number
}
>
(
'
/admin/agent/logs
'
,
{
params
}),
getStats
:
(
params
?:
{
start
?:
string
;
end
?:
string
})
=>
get
<
{
agent_id
:
string
;
count
:
number
;
avg_iterations
:
number
;
avg_tokens
:
number
;
success_rate
:
number
}[]
>
(
'
/admin/agent/stats
'
,
{
params
}
),
};
// ==================== Workflow API ====================
export
const
workflowApi
=
{
list
:
()
=>
get
<
unknown
[]
>
(
'
/admin/workflows
'
),
create
:
(
data
:
WorkflowCreateParams
)
=>
post
<
unknown
>
(
'
/admin/workflows
'
,
data
),
update
:
(
id
:
number
,
data
:
Partial
<
WorkflowCreateParams
>
)
=>
put
<
unknown
>
(
`/admin/workflows/
${
id
}
`
,
data
),
publish
:
(
id
:
number
)
=>
post
<
null
>
(
`/admin/workflows/
${
id
}
/publish`
),
execute
:
(
workflowId
:
string
,
input
?:
Record
<
string
,
unknown
>
)
=>
post
<
{
execution_id
:
string
}
>
(
`/workflow/
${
workflowId
}
/execute`
,
input
||
{}),
getExecution
:
(
executionId
:
string
)
=>
get
<
WorkflowExecution
>
(
`/workflow/execution/
${
executionId
}
`
),
listExecutions
:
(
params
?:
{
workflow_id
?:
string
;
page
?:
number
;
page_size
?:
number
})
=>
get
<
{
list
:
WorkflowExecution
[];
total
:
number
}
>
(
'
/admin/workflow/executions
'
,
{
params
}),
getTasks
:
()
=>
get
<
unknown
[]
>
(
'
/workflow/tasks
'
),
completeTask
:
(
taskId
:
string
,
result
:
Record
<
string
,
unknown
>
)
=>
post
<
null
>
(
`/workflow/task/
${
taskId
}
/complete`
,
result
),
};
// ==================== Knowledge API ====================
export
const
knowledgeApi
=
{
listCollections
:
()
=>
get
<
unknown
[]
>
(
'
/knowledge/collections
'
),
createCollection
:
(
data
:
KnowledgeCollectionParams
)
=>
post
<
unknown
>
(
'
/knowledge/collections
'
,
data
),
listDocuments
:
(
collectionId
?:
string
)
=>
get
<
unknown
[]
>
(
'
/knowledge/documents
'
,
{
params
:
collectionId
?
{
collection_id
:
collectionId
}
:
{}
}),
createDocument
:
(
data
:
KnowledgeDocumentParams
)
=>
post
<
unknown
>
(
'
/knowledge/documents
'
,
data
),
search
:
(
query
:
string
,
topK
=
5
,
collectionId
?:
string
)
=>
post
<
unknown
[]
>
(
'
/knowledge/search
'
,
{
query
,
top_k
:
topK
,
collection_id
:
collectionId
}),
};
web/src/app/(main)/admin/agents/page.tsx
View file @
626473e4
...
...
@@ -3,10 +3,10 @@
import
{
useState
}
from
'
react
'
;
import
{
Card
,
Table
,
Tag
,
Button
,
Modal
,
Input
,
message
,
Space
,
Collapse
,
Timeline
,
Typography
}
from
'
antd
'
;
import
{
RobotOutlined
,
PlayCircleOutlined
,
ToolOutlined
,
CheckCircleOutlined
,
CloseCircleOutlined
}
from
'
@ant-design/icons
'
;
import
{
useUserStore
}
from
'
@/store/userStore
'
;
import
{
agentApi
}
from
'
@/api/agent
'
;
import
type
{
ToolCall
}
from
'
@/api/agent
'
;
const
{
Text
}
=
Typography
;
const
API
=
''
;
const
BUILTIN_AGENTS
=
[
{
id
:
'
pre_consult_agent
'
,
name
:
'
预问诊智能助手
'
,
description
:
'
通过多轮对话收集患者症状,生成预问诊报告
'
,
category
:
'
pre_consult
'
,
tools
:
[
'
query_symptom_knowledge
'
,
'
recommend_department
'
],
max_iterations
:
5
},
...
...
@@ -29,14 +29,6 @@ const categoryLabel: Record<string, string> = {
follow_up
:
'
随访管理
'
,
};
interface
ToolCall
{
tool_name
:
string
;
call_id
:
string
;
arguments
:
string
;
result
:
{
success
:
boolean
;
data
?:
unknown
;
error
?:
string
};
success
:
boolean
;
}
interface
AgentResponse
{
response
:
string
;
tool_calls
?:
ToolCall
[];
...
...
@@ -46,7 +38,6 @@ interface AgentResponse {
}
export
default
function
AgentsPage
()
{
const
{
accessToken
:
token
}
=
useUserStore
();
const
[
testModal
,
setTestModal
]
=
useState
<
{
open
:
boolean
;
agentId
:
string
;
agentName
:
string
}
>
({
open
:
false
,
agentId
:
''
,
agentName
:
''
});
const
[
testMessages
,
setTestMessages
]
=
useState
<
{
role
:
string
;
content
:
string
;
toolCalls
?:
ToolCall
[];
meta
?:
{
iterations
?:
number
;
tokens
?:
number
}
}[]
>
([]);
const
[
inputMsg
,
setInputMsg
]
=
useState
(
''
);
...
...
@@ -66,13 +57,8 @@ export default function AgentsPage() {
setTestMessages
(
prev
=>
[...
prev
,
{
role
:
'
user
'
,
content
:
userMsg
}]);
setLoading
(
true
);
try
{
const
res
=
await
fetch
(
`
${
API
}
/api/v1/agent/
${
testModal
.
agentId
}
/chat`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
session_id
:
sessionId
,
message
:
userMsg
}),
});
const
data
=
await
res
.
json
();
const
agentData
=
data
.
data
as
AgentResponse
;
const
res
=
await
agentApi
.
chat
(
testModal
.
agentId
,
{
session_id
:
sessionId
,
message
:
userMsg
});
const
agentData
=
res
.
data
as
AgentResponse
;
const
reply
=
agentData
?.
response
||
'
无响应
'
;
setTestMessages
(
prev
=>
[...
prev
,
{
role
:
'
assistant
'
,
...
...
web/src/app/(main)/admin/knowledge/page.tsx
View file @
626473e4
...
...
@@ -3,10 +3,9 @@
import
{
useEffect
,
useState
}
from
'
react
'
;
import
{
Card
,
Table
,
Tag
,
Button
,
Modal
,
Form
,
Input
,
Select
,
message
,
Space
,
Tabs
,
Typography
}
from
'
antd
'
;
import
{
BookOutlined
,
PlusOutlined
,
SearchOutlined
,
FileTextOutlined
,
ReloadOutlined
}
from
'
@ant-design/icons
'
;
import
{
useUserStore
}
from
'
@/store/userStore
'
;
import
{
knowledgeApi
}
from
'
@/api/agent
'
;
const
{
Text
}
=
Typography
;
const
API
=
''
;
const
categoryLabel
:
Record
<
string
,
string
>
=
{
clinical_guideline
:
'
临床指南
'
,
drug
:
'
药品说明
'
,
disease
:
'
疾病百科
'
,
paper
:
'
医学论文
'
,
...
...
@@ -16,7 +15,6 @@ const categoryColor: Record<string, string> = {
};
export
default
function
KnowledgePage
()
{
const
{
accessToken
:
token
}
=
useUserStore
();
const
[
collections
,
setCollections
]
=
useState
<
any
[]
>
([]);
const
[
documents
,
setDocuments
]
=
useState
<
any
[]
>
([]);
const
[
colLoading
,
setColLoading
]
=
useState
(
false
);
...
...
@@ -30,23 +28,19 @@ export default function KnowledgePage() {
const
[
docForm
]
=
Form
.
useForm
();
const
[
searchForm
]
=
Form
.
useForm
();
const
headers
=
{
Authorization
:
`Bearer
${
token
}
`
,
'
Content-Type
'
:
'
application/json
'
};
const
fetchCollections
=
async
()
=>
{
setColLoading
(
true
);
try
{
const
res
=
await
fetch
(
`
${
API
}
/api/v1/knowledge/collections`
,
{
headers
});
const
data
=
await
res
.
json
();
setCollections
(
data
.
data
||
[]);
const
res
=
await
knowledgeApi
.
listCollections
();
setCollections
((
res
.
data
as
any
[])
||
[]);
}
catch
{}
finally
{
setColLoading
(
false
);
}
};
const
fetchDocuments
=
async
()
=>
{
setDocLoading
(
true
);
try
{
const
res
=
await
fetch
(
`
${
API
}
/api/v1/knowledge/documents`
,
{
headers
});
const
data
=
await
res
.
json
();
setDocuments
(
data
.
data
||
[]);
const
res
=
await
knowledgeApi
.
listDocuments
();
setDocuments
((
res
.
data
as
any
[])
||
[]);
}
catch
{}
finally
{
setDocLoading
(
false
);
}
};
...
...
@@ -54,7 +48,7 @@ export default function KnowledgePage() {
const
createCollection
=
async
(
values
:
any
)
=>
{
try
{
await
fetch
(
`
${
API
}
/api/v1/knowledge/collections`
,
{
method
:
'
POST
'
,
headers
,
body
:
JSON
.
stringify
(
values
)
}
);
await
knowledgeApi
.
createCollection
(
values
);
message
.
success
(
'
集合创建成功
'
);
setColModal
(
false
);
colForm
.
resetFields
();
...
...
@@ -64,7 +58,7 @@ export default function KnowledgePage() {
const
createDocument
=
async
(
values
:
any
)
=>
{
try
{
await
fetch
(
`
${
API
}
/api/v1/knowledge/documents`
,
{
method
:
'
POST
'
,
headers
,
body
:
JSON
.
stringify
(
values
)
}
);
await
knowledgeApi
.
createDocument
(
values
);
message
.
success
(
'
文档已添加
'
);
setDocModal
(
false
);
docForm
.
resetFields
();
...
...
@@ -75,9 +69,8 @@ export default function KnowledgePage() {
const
doSearch
=
async
(
values
:
any
)
=>
{
setSearching
(
true
);
try
{
const
res
=
await
fetch
(
`
${
API
}
/api/v1/knowledge/search`
,
{
method
:
'
POST
'
,
headers
,
body
:
JSON
.
stringify
(
values
)
});
const
data
=
await
res
.
json
();
setSearchResults
(
data
.
data
||
[]);
const
res
=
await
knowledgeApi
.
search
(
values
.
query
,
values
.
top_k
||
5
);
setSearchResults
((
res
.
data
as
any
[])
||
[]);
}
catch
{
message
.
error
(
'
检索失败
'
);
}
finally
{
setSearching
(
false
);
}
};
...
...
web/src/app/(main)/admin/tools/page.tsx
View file @
626473e4
...
...
@@ -6,10 +6,9 @@ import {
ToolOutlined
,
SearchOutlined
,
InfoCircleOutlined
,
ApiOutlined
,
CheckCircleOutlined
,
CodeOutlined
,
ReloadOutlined
,
}
from
'
@ant-design/icons
'
;
import
{
useUserStore
}
from
'
@/store/userStore
'
;
import
{
agentApi
}
from
'
@/api/agent
'
;
const
{
Text
}
=
Typography
;
const
API
=
''
;
interface
AgentTool
{
id
:
string
;
...
...
@@ -52,7 +51,6 @@ const statCards = [
];
export
default
function
ToolsPage
()
{
const
{
accessToken
:
token
}
=
useUserStore
();
const
[
tools
,
setTools
]
=
useState
<
AgentTool
[]
>
(
BUILTIN_TOOLS
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
searchText
,
setSearchText
]
=
useState
(
''
);
...
...
@@ -63,11 +61,8 @@ export default function ToolsPage() {
const
fetchTools
=
async
()
=>
{
setLoading
(
true
);
try
{
const
res
=
await
fetch
(
`
${
API
}
/api/v1/agent/tools`
,
{
headers
:
{
Authorization
:
`Bearer
${
token
}
`
}
});
if
(
res
.
ok
)
{
const
data
=
await
res
.
json
();
if
(
data
.
data
?.
length
>
0
)
setTools
(
data
.
data
);
}
const
res
=
await
agentApi
.
listTools
();
if
(
res
.
data
?.
length
>
0
)
setTools
(
res
.
data
as
AgentTool
[]);
}
catch
{
/* 使用内置工具列表 */
}
finally
{
setLoading
(
false
);
}
...
...
web/src/app/(main)/admin/workflows/page.tsx
View file @
626473e4
...
...
@@ -3,12 +3,10 @@
import
{
useEffect
,
useState
,
useCallback
}
from
'
react
'
;
import
{
Card
,
Table
,
Tag
,
Button
,
Modal
,
Form
,
Input
,
Select
,
message
,
Space
,
Badge
}
from
'
antd
'
;
import
{
DeploymentUnitOutlined
,
PlayCircleOutlined
,
PlusOutlined
,
EditOutlined
}
from
'
@ant-design/icons
'
;
import
{
useUserStore
}
from
'
@/store/userStore
'
;
import
{
workflowApi
}
from
'
@/api/agent
'
;
import
VisualWorkflowEditor
from
'
@/components/workflow/VisualWorkflowEditor
'
;
import
type
{
Node
,
Edge
}
from
'
@xyflow/react
'
;
const
API
=
''
;
interface
Workflow
{
id
:
number
;
workflow_id
:
string
;
...
...
@@ -31,7 +29,6 @@ const categoryLabel: Record<string, string> = {
};
export
default
function
WorkflowsPage
()
{
const
{
accessToken
:
token
}
=
useUserStore
();
const
[
workflows
,
setWorkflows
]
=
useState
<
Workflow
[]
>
([]);
const
[
createModal
,
setCreateModal
]
=
useState
(
false
);
const
[
editorModal
,
setEditorModal
]
=
useState
(
false
);
...
...
@@ -43,9 +40,8 @@ export default function WorkflowsPage() {
const
fetchWorkflows
=
async
()
=>
{
setTableLoading
(
true
);
try
{
const
res
=
await
fetch
(
`
${
API
}
/api/v1/admin/workflows`
,
{
headers
:
{
Authorization
:
`Bearer
${
token
}
`
}
});
const
data
=
await
res
.
json
();
setWorkflows
(
data
.
data
||
[]);
const
res
=
await
workflowApi
.
list
();
setWorkflows
((
res
.
data
as
Workflow
[])
||
[]);
}
catch
{}
finally
{
setTableLoading
(
false
);
}
...
...
@@ -64,11 +60,7 @@ export default function WorkflowsPage() {
},
edges
:
[{
id
:
'
e1
'
,
source_node
:
'
start
'
,
target_node
:
'
end
'
}],
};
await
fetch
(
`
${
API
}
/api/v1/admin/workflows`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
...
values
,
definition
:
JSON
.
stringify
(
definition
)
}),
});
await
workflowApi
.
create
({
...
values
,
definition
:
JSON
.
stringify
(
definition
)
});
message
.
success
(
'
创建成功
'
);
setCreateModal
(
false
);
form
.
resetFields
();
...
...
@@ -83,38 +75,24 @@ export default function WorkflowsPage() {
const
handleSaveWorkflow
=
useCallback
(
async
(
nodes
:
Node
[],
edges
:
Edge
[])
=>
{
if
(
!
editingWorkflow
)
return
;
try
{
await
fetch
(
`
${
API
}
/api/v1/admin/workflows/
${
editingWorkflow
.
id
}
`
,
{
method
:
'
PUT
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
definition
:
JSON
.
stringify
({
nodes
,
edges
})
}),
});
await
workflowApi
.
update
(
editingWorkflow
.
id
,
{
definition
:
JSON
.
stringify
({
nodes
,
edges
})
});
message
.
success
(
'
工作流已保存
'
);
fetchWorkflows
();
}
catch
{
message
.
error
(
'
保存失败
'
);
}
},
[
editingWorkflow
,
token
]);
},
[
editingWorkflow
]);
const
handleExecuteFromEditor
=
useCallback
(
async
(
nodes
:
Node
[],
edges
:
Edge
[])
=>
{
if
(
!
editingWorkflow
)
return
;
try
{
const
res
=
await
fetch
(
`
${
API
}
/api/v1/workflow/
${
editingWorkflow
.
workflow_id
}
/execute`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
workflow_data
:
{
nodes
,
edges
}
}),
});
const
result
=
await
res
.
json
();
const
result
=
await
workflowApi
.
execute
(
editingWorkflow
.
workflow_id
,
{
workflow_data
:
{
nodes
,
edges
}
});
message
.
success
(
`执行已启动:
${
result
.
data
?.
execution_id
}
`);
} catch { message.error('执行失败'); }
}, [editingWorkflow
, token
]);
}, [editingWorkflow]);
const handleExecute = async (workflowId: string) => {
try {
const res = await fetch(`
$
{
API
}
/api/
v1
/
workflow
/
$
{
workflowId
}
/execute`,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
,
body: JSON.stringify({}),
});
const data = await res.json();
message.success(`
执行已启动
:
$
{
data
.
data
?.
execution_id
}
`);
const result = await workflowApi.execute(workflowId);
message.success(`
执行已启动
:
$
{
result
.
data
?.
execution_id
}
`);
} catch { message.error('执行失败'); }
};
...
...
升级方案_智能体平台融合.md
0 → 100644
View file @
626473e4
# 互联网医院平台智能体融合升级方案
> 版本:v1.0 | 日期:2026-03-02
---
## 一、现状诊断:端到端断点全景
经过完整代码审查,当前平台的
**后端智能体框架已具备生产级能力**
,但与主业务流程之间存在多处"功能孤岛"断点。以下为关键断点清单:
### 1.1 API 基础路径不一致(阻断性)
| 位置 | 当前写法 | 正确写法 |
|------|---------|---------|
|
`admin/agents/page.tsx`
|
`const API = ''`
(直接 fetch) | 使用统一 axios 客户端 |
|
`admin/tools/page.tsx`
|
`const API = ''`
(直接 fetch) | 使用统一 axios 客户端 |
|
`admin/workflows/page.tsx`
|
`const API = ''`
(直接 fetch) | 使用统一 axios 客户端 |
|
`admin/knowledge/page.tsx`
|
`const API = ''`
(直接 fetch) | 使用统一 axios 客户端 |
|
`GlobalAIFloat/ChatPanel.tsx`
|
`const API = ''`
(直接 fetch) | 使用统一 axios 客户端 |
**影响**
:上述页面的 Token 认证、统一错误处理、环境变量切换均失效。
---
### 1.2 智能体与主业务流程的断点
```
患者预问诊 ──▶ SSE Chat(已接通)──▶ AI 报告(已接通)──▶ 推荐医生(已接通)
│
▼
问诊中 ──▶ /consult/:id/ai-assist(仅调用基础 LLM) ✗ diagnosis_agent(带 tools)
↑ 未接通
处方创建 ──▶ 医生手动填写 ✗ prescription_agent(已有 4 个安全工具)
↑ 未接通
随访管理 ──▶ 患者慢病续药 AI 建议(独立调用) ✗ follow_up_agent
↑ 完全未接通
工作流执行 ──▶ 仅管理员手动触发 ✗ 问诊完成/预问诊完成/审核通过 等事件
↑ 无任何触发入口
```
---
### 1.3 知识库是数据孤岛
-
知识库 CRUD 功能完整,但
**库内没有任何内容**
-
`knowledge_search`
工具实际使用 11 条硬编码数据(非数据库内容)
-
预问诊 / 诊断辅助 / 处方审核均未从知识库获取临床指南
-
无任何内容导入入口(仅支持逐条手动添加)
---
### 1.4 管理端监控缺失
-
`AgentExecutionLog`
模型存在且后端已写入,但
**管理端无任何展示**
-
`AgentToolLog`
模型存在但工具执行时
**从未写入**
-
AI 配置页面有使用统计,但统计来源与 Agent 执行日志脱节
-
工作流执行记录(
`WorkflowExecution`
)无管理端查看入口
---
### 1.5 Agent 会话管理残缺
-
`GET /agent/sessions`
→ 返回空数组(存根)
-
`DELETE /agent/session/:id`
→ 无任何实现
-
患者/医生无法查看 AI 对话历史
-
前端 GlobalAIFloat 每次刷新页面对话历史丢失
---
## 二、升级目标
将智能体系统从"可独立运行的功能孤岛"升级为"深度嵌入临床业务流程的智能引擎",实现:
1.
**数据互通**
:患者病历、问诊记录、处方历史成为智能体的上下文输入
2.
**流程融合**
:智能体在问诊、处方、随访的关键节点自动介入
3.
**知识赋能**
:真实医学知识库驱动 AI 决策,而非硬编码规则
4.
**可观测性**
:管理员可监控所有智能体执行情况
5.
**工作流触发**
:复杂临床场景通过工作流编排自动化
---
## 三、升级方案详解
### P0 — 基础修复(本周)
#### 3.1 统一 Agent API 客户端
在
`web/src/api/`
新增
`agent.ts`
,将所有智能体相关 API 统一接入 axios:
```
typescript
// web/src/api/agent.ts
import
request
from
'
./request
'
;
export
const
agentApi
=
{
// 与智能体对话(含认证 header、统一错误处理)
chat
:
(
agentId
:
string
,
params
:
{
session_id
?:
string
;
message
:
string
;
context
?:
Record
<
string
,
unknown
>
;
})
=>
request
.
post
(
`/agent/
${
agentId
}
/chat`
,
params
),
// 获取会话列表
getSessions
:
(
userId
:
string
)
=>
request
.
get
(
'
/agent/sessions
'
,
{
params
:
{
user_id
:
userId
}
}),
// 获取智能体列表
listAgents
:
()
=>
request
.
get
(
'
/agent/list
'
),
// 获取工具列表
listTools
:
()
=>
request
.
get
(
'
/agent/tools
'
),
};
export
const
workflowApi
=
{
list
:
()
=>
request
.
get
(
'
/admin/workflows
'
),
create
:
(
data
:
WorkflowCreateParams
)
=>
request
.
post
(
'
/admin/workflows
'
,
data
),
update
:
(
id
:
number
,
data
:
Partial
<
WorkflowCreateParams
>
)
=>
request
.
put
(
`/admin/workflows/
${
id
}
`
,
data
),
publish
:
(
id
:
number
)
=>
request
.
post
(
`/admin/workflows/
${
id
}
/publish`
),
execute
:
(
workflowId
:
string
,
input
?:
Record
<
string
,
unknown
>
)
=>
request
.
post
(
`/workflow/
${
workflowId
}
/execute`
,
input
||
{}),
getExecution
:
(
executionId
:
string
)
=>
request
.
get
(
`/workflow/execution/
${
executionId
}
`
),
getTasks
:
()
=>
request
.
get
(
'
/workflow/tasks
'
),
completeTask
:
(
taskId
:
string
,
result
:
Record
<
string
,
unknown
>
)
=>
request
.
post
(
`/workflow/task/
${
taskId
}
/complete`
,
result
),
};
export
const
knowledgeApi
=
{
listCollections
:
()
=>
request
.
get
(
'
/knowledge/collections
'
),
createCollection
:
(
data
:
KnowledgeCollectionParams
)
=>
request
.
post
(
'
/knowledge/collections
'
,
data
),
listDocuments
:
(
collectionId
?:
string
)
=>
request
.
get
(
'
/knowledge/documents
'
,
{
params
:
{
collection_id
:
collectionId
}
}),
createDocument
:
(
data
:
KnowledgeDocumentParams
)
=>
request
.
post
(
'
/knowledge/documents
'
,
data
),
search
:
(
query
:
string
,
topK
=
5
,
collectionId
?:
string
)
=>
request
.
post
(
'
/knowledge/search
'
,
{
query
,
top_k
:
topK
,
collection_id
:
collectionId
}),
};
```
同步将四个管理页面的
`fetch()`
调用全部替换为上述 API 方法。
---
#### 3.2 修复工作流 Update 接口(后端缺失)
当前后端只有
`PublishWorkflow`
,没有
`UpdateWorkflow`
,导致可视化编辑器保存功能无效。
```
go
// server/internal/service/admin/workflow_handler.go 增加
func
(
h
*
Handler
)
UpdateWorkflow
(
c
*
gin
.
Context
)
{
var
req
struct
{
Name
string
`json:"name"`
Description
string
`json:"description"`
Definition
string
`json:"definition"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
c
.
JSON
(
http
.
StatusBadRequest
,
gin
.
H
{
"error"
:
err
.
Error
()})
return
}
updates
:=
map
[
string
]
interface
{}{}
if
req
.
Definition
!=
""
{
updates
[
"definition"
]
=
req
.
Definition
}
if
req
.
Name
!=
""
{
updates
[
"name"
]
=
req
.
Name
}
database
.
GetDB
()
.
Model
(
&
model
.
WorkflowDefinition
{})
.
Where
(
"id = ?"
,
c
.
Param
(
"id"
))
.
Updates
(
updates
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"message"
:
"ok"
})
}
```
路由注册:
`admin.PUT("/workflows/:id", h.UpdateWorkflow)`
---
### P1 — 核心流程融合(第 1~2 周)
#### 3.3 诊断辅助:从基础 LLM 升级为带工具的 Agent
**当前状态**
:
`/consult/:id/ai-assist`
调用基础 LLM,无工具调用能力
**升级方案**
:后端
`consult/service.go`
的
`AIAssist()`
改为调用
`diagnosis_agent`
```
go
// server/internal/service/consult/service.go
func
(
s
*
Service
)
AIAssist
(
ctx
context
.
Context
,
consultID
string
,
scene
string
)
(
map
[
string
]
interface
{},
error
)
{
// 加载问诊上下文(现有逻辑保留)
var
consult
model
.
Consultation
s
.
db
.
Where
(
"id = ?"
,
consultID
)
.
First
(
&
consult
)
var
preConsult
model
.
PreConsultation
s
.
db
.
Where
(
"consultation_id = ?"
,
consultID
)
.
First
(
&
preConsult
)
// 构建 Agent 上下文(传入患者信息,让工具能查询病历)
agentCtx
:=
map
[
string
]
interface
{}{
"patient_id"
:
consult
.
PatientID
,
"consult_id"
:
consultID
,
"chief_complaint"
:
consult
.
ChiefComplaint
,
"scene"
:
scene
,
}
if
preConsult
.
AIAnalysis
!=
""
{
agentCtx
[
"pre_consult_analysis"
]
=
preConsult
.
AIAnalysis
}
// 根据场景选择 Agent
agentID
:=
"diagnosis_agent"
message
:=
"请对患者当前情况进行诊断分析,提供鉴别诊断建议"
if
scene
==
"consult_medication"
{
agentID
=
"prescription_agent"
message
=
"请分析患者情况,提供用药建议"
}
agentSvc
:=
internalagent
.
GetService
()
output
,
err
:=
agentSvc
.
Chat
(
ctx
,
agentID
,
consult
.
DoctorID
,
""
,
message
,
agentCtx
)
if
err
!=
nil
{
return
nil
,
err
}
return
map
[
string
]
interface
{}{
"response"
:
output
.
Response
,
"tool_calls"
:
output
.
ToolCalls
,
"iterations"
:
output
.
Iterations
,
},
nil
}
```
**前端**
:医生工作台
`AIPanel.tsx`
展示 tool_calls 时间线(已有组件,需接数据)
---
#### 3.4 处方创建:接入 prescription_agent 安全审核
**当前状态**
:医生手动填写处方,无自动安全检查
**升级方案**
:处方创建 Modal 增加"AI 安全审核"步骤
**后端**
:在
`doctor-portal/prescription/check`
(新增接口)调用
`prescription_agent`
```
go
// server/internal/service/doctorportal/prescription_service.go 增加
func
(
s
*
Service
)
CheckPrescriptionSafety
(
ctx
context
.
Context
,
patientID
string
,
drugs
[]
string
)
(
map
[
string
]
interface
{},
error
)
{
drugList
:=
strings
.
Join
(
drugs
,
"、"
)
agentCtx
:=
map
[
string
]
interface
{}{
"patient_id"
:
patientID
,
"drugs"
:
drugs
,
}
message
:=
fmt
.
Sprintf
(
"请审核以下处方的安全性:%s"
,
drugList
)
agentSvc
:=
internalagent
.
GetService
()
output
,
err
:=
agentSvc
.
Chat
(
ctx
,
"prescription_agent"
,
"system"
,
""
,
message
,
agentCtx
)
if
err
!=
nil
{
return
nil
,
err
}
return
map
[
string
]
interface
{}{
"safe"
:
!
strings
.
Contains
(
output
.
Response
,
"禁忌"
)
&&
!
strings
.
Contains
(
output
.
Response
,
"危险"
),
"report"
:
output
.
Response
,
"tool_calls"
:
output
.
ToolCalls
,
"warnings"
:
extractWarnings
(
output
),
},
nil
}
```
**前端**
:处方 Modal 新增"AI 安全审核"按钮,提交前自动或手动触发,展示警告 Tag
---
#### 3.5 知识库内容建设
知识库是 AI 决策的基础,当前为空库。需要:
**方案 A:批量导入工具(管理端)**
在知识库管理页面增加批量导入入口,支持:
-
粘贴大段文本(自动分块)
-
JSON 格式批量导入
**方案 B:初始化种子数据**
后端提供初始化脚本,预置核心医学知识:
| 集合 | 内容 | 预计文档数 |
|------|------|---------|
| 临床指南 | 常见病诊疗规范(高血压、糖尿病、冠心病等 10 种) | 10 |
| 药品说明 | 高频使用药品说明书摘要(50 种) | 50 |
| 疾病百科 | 常见病症状、检查、鉴别诊断 | 30 |
| 药物相互作用 | 危险药对列表 | 1 批 |
**方案 C:`knowledge_search` 工具优先走数据库**
修改
`pkg/agent/tools/knowledge_search.go`
:只有在知识库完全没有结果时才降级到硬编码
```
go
// 当前:先用硬编码 fallback,几乎不走数据库
// 修改:先查数据库,结果数 < topK 时才补充硬编码
results
,
err
:=
r
.
retriever
.
Search
(
ctx
,
query
,
topK
)
if
err
!=
nil
||
len
(
results
)
==
0
{
return
builtinSearch
(
query
)
// 降级
}
```
---
#### 3.6 后端 Agent 会话接口补全
```
go
// server/internal/agent/handler.go - 补充完整实现
// ListSessions 获取用户的所有 Agent 会话
func
(
h
*
Handler
)
ListSessions
(
c
*
gin
.
Context
)
{
userID
,
_
:=
c
.
Get
(
"user_id"
)
agentID
:=
c
.
Query
(
"agent_id"
)
var
sessions
[]
model
.
AgentSession
query
:=
database
.
GetDB
()
.
Where
(
"user_id = ?"
,
userID
)
.
Order
(
"updated_at DESC"
)
if
agentID
!=
""
{
query
=
query
.
Where
(
"agent_id = ?"
,
agentID
)
}
query
.
Find
(
&
sessions
)
// 解析每个会话的最后一条消息作为摘要
type
SessionSummary
struct
{
model
.
AgentSession
LastMessage
string
`json:"last_message"`
}
result
:=
make
([]
SessionSummary
,
0
,
len
(
sessions
))
for
_
,
s
:=
range
sessions
{
var
history
[]
map
[
string
]
string
json
.
Unmarshal
([]
byte
(
s
.
History
),
&
history
)
lastMsg
:=
""
if
len
(
history
)
>
0
{
lastMsg
=
history
[
len
(
history
)
-
1
][
"content"
]
if
len
(
lastMsg
)
>
60
{
lastMsg
=
lastMsg
[
:
60
]
+
"..."
}
}
result
=
append
(
result
,
SessionSummary
{
s
,
lastMsg
})
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"data"
:
result
})
}
// DeleteSession 删除会话(清除历史)
func
(
h
*
Handler
)
DeleteSession
(
c
*
gin
.
Context
)
{
userID
,
_
:=
c
.
Get
(
"user_id"
)
sessionID
:=
c
.
Param
(
"session_id"
)
database
.
GetDB
()
.
Where
(
"session_id = ? AND user_id = ?"
,
sessionID
,
userID
)
.
Delete
(
&
model
.
AgentSession
{})
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"message"
:
"ok"
})
}
```
**前端**
:GlobalAIFloat 加入会话历史侧栏,可切换历史对话 / 新建对话
---
### P2 — 工作流触发入口融合(第 2~3 周)
#### 3.7 工作流触发入口设计
工作流的核心价值在于
**事件驱动的自动化**
,当前只能手动触发。需要在以下业务节点植入工作流触发:
| 触发事件 | 推荐工作流 | 触发位置 |
|---------|---------|---------|
| 预问诊完成 |
`pre_consult_complete_flow`
:生成标准化报告 → 推荐科室 → 匹配医生 |
`POST /pre-consult/chat/finish`
执行后 |
| 问诊结束 |
`post_consult_flow`
:生成病历摘要 → 触发随访提醒 → 推送用药指导 |
`POST /consult/:id/end`
执行后 |
| 处方开具 |
`prescription_review_flow`
:AI 安全审核 → 超阈值转人工审核 → 药房通知 |
`POST /doctor-portal/prescription/create`
后 |
| 慢病续药申请 |
`chronic_renewal_flow`
:AI 评估 → 医生审核 → 药房处理 |
`POST /chronic/renewals`
后 |
**后端实现**
:在各业务服务的关键节点异步触发工作流
```
go
// 在 preconsult/service.go 的 FinishChat 末尾添加(异步,不阻塞主流程)
go
func
()
{
workflowEngine
.
TriggerByCategory
(
"pre_consult"
,
map
[
string
]
interface
{}{
"pre_consult_id"
:
preConsult
.
ID
,
"patient_id"
:
preConsult
.
UserID
,
"department"
:
preConsult
.
AIDepartment
,
"severity"
:
preConsult
.
AISeverity
,
})
}()
```
**工作流引擎增加**
:
`TriggerByCategory()`
方法——查找该分类下状态为 active 的第一个工作流并执行
---
#### 3.8 工作流执行结果回写业务数据
工作流不仅要被触发,其输出也要回写到相关业务实体:
| 工作流输出 | 回写目标 |
|---------|---------|
| 标准化病历摘要 |
`Consultation.summary`
字段 |
| 随访计划 |
`ChronicRecord.follow_up_plan`
字段 |
| 处方审核结果 |
`Prescription.ai_review_result`
字段 |
| 下次就诊提醒 | 推送到患者 APP 通知 |
---
#### 3.9 随访 Agent 接入慢病管理
**当前状态**
:慢病续药的
`getAIAdvice()`
调用的是
`/chronic/renewals/:id/ai-advice`
,后端实现未可见
**升级方案**
:
```
go
// server/internal/service/chronic/service.go - GetAIAdvice 接入 follow_up_agent
func
(
s
*
Service
)
GetAIAdvice
(
ctx
context
.
Context
,
renewalID
,
patientID
string
)
(
string
,
error
)
{
var
renewal
model
.
ChronicRenewal
s
.
db
.
Where
(
"id = ?"
,
renewalID
)
.
First
(
&
renewal
)
agentCtx
:=
map
[
string
]
interface
{}{
"patient_id"
:
patientID
,
"diagnosis"
:
renewal
.
Diagnosis
,
"current_drugs"
:
renewal
.
DrugList
,
"renewal_duration_months"
:
renewal
.
Duration
,
}
message
:=
fmt
.
Sprintf
(
"患者%s需要续药%d个月,请评估续药合理性并给出建议"
,
renewal
.
Diagnosis
,
renewal
.
Duration
)
agentSvc
:=
internalagent
.
GetService
()
output
,
err
:=
agentSvc
.
Chat
(
ctx
,
"follow_up_agent"
,
"system"
,
""
,
message
,
agentCtx
)
if
err
!=
nil
{
return
"暂时无法获取AI建议"
,
nil
}
return
output
.
Response
,
nil
}
```
---
### P3 — 可观测性与数据质量(第 3~4 周)
#### 3.10 管理端智能体监控大屏
在现有
`admin/agents`
页面新增"执行监控"标签页,展示:
```
智能体管理
├── 智能体列表(当前)
├── 执行日志(新增) ← 来源:AgentExecutionLog 表
│ ├── 时间范围筛选
│ ├── 按智能体筛选
│ ├── 每条记录:时间、用户、输入摘要、响应摘要、迭代次数、Token数、耗时、工具调用数
│ └── 详情展开:完整 tool_calls 时间线
└── 统计概览(新增)
├── 各 Agent 调用次数(饼图)
├── 平均迭代次数趋势(折线图)
├── 工具调用成功率(条形图)
└── Token 消耗统计
```
**后端接口**
(新增):
```
GET /admin/agent/logs?agent_id=&start=&end=&page=&page_size=
GET /admin/agent/stats?start=&end=
```
---
#### 3.11 工具调用日志补全
修改
`pkg/agent/executor.go`
,在工具执行后写入
`AgentToolLog`
:
```
go
// pkg/agent/executor.go - Execute() 末尾补充日志
start
:=
time
.
Now
()
result
,
err
:=
tool
.
Execute
(
ctx
,
args
)
duration
:=
int
(
time
.
Since
(
start
)
.
Milliseconds
())
// 写入工具调用日志
go
database
.
GetDB
()
.
Create
(
&
model
.
AgentToolLog
{
ToolName
:
name
,
AgentID
:
e
.
agentID
,
// 需要在 Executor 中传入
SessionID
:
e
.
sessionID
,
InputParams
:
string
(
argsJSON
),
OutputResult
:
marshalResult
(
result
),
Success
:
err
==
nil
,
DurationMs
:
duration
,
})
```
---
#### 3.12 GlobalAIFloat 会话持久化
前端对话面板刷新后历史丢失,需接入会话 API:
```
typescript
// web/src/components/GlobalAIFloat/ChatPanel.tsx
// 1. 页面加载时拉取该 Agent 的最近会话
useEffect
(()
=>
{
agentApi
.
getSessions
(
user
.
id
)
.
then
(
res
=>
{
const
sessions
=
res
.
data
?.
filter
(
s
=>
s
.
agent_id
===
agentId
);
if
(
sessions
?.
length
>
0
)
{
const
latest
=
sessions
[
0
];
setSessionId
(
latest
.
session_id
);
// 恢复历史消息
const
history
=
JSON
.
parse
(
latest
.
history
||
'
[]
'
);
setMessages
(
history
.
map
(
convertHistoryToMessage
));
}
});
},
[
agentId
]);
// 2. 发消息时携带 session_id(实现连续对话)
const
response
=
await
agentApi
.
chat
(
agentId
,
{
session_id
:
sessionId
,
message
:
inputMsg
,
context
:
patientContext
,
});
setSessionId
(
response
.
data
.
session_id
);
```
---
#### 3.13 pgvector 向量检索升级
当前知识库检索退化为 ILIKE 关键词匹配,语义搜索能力缺失。
**升级步骤**
:
```
sql
-- 在 PostgreSQL 中安装 pgvector
CREATE
EXTENSION
IF
NOT
EXISTS
vector
;
-- 为 knowledge_chunks 表添加向量列
ALTER
TABLE
knowledge_chunks
ADD
COLUMN
IF
NOT
EXISTS
embedding
vector
(
1536
);
CREATE
INDEX
IF
NOT
EXISTS
knowledge_chunks_embedding_idx
ON
knowledge_chunks
USING
ivfflat
(
embedding
vector_cosine_ops
);
```
**配置**
:在 AI Config 中启用嵌入模型(如
`text-embedding-3-small`
),知识库新增文档时自动向量化。
**效果**
:搜索"患者胸闷气短"能匹配到"心力衰竭诊疗指南"而非仅靠关键词
---
## 四、数据流融合全景图(升级后)
```
患者端
│
├─ 预问诊 Chat(SSE)
│ └─ pre_consult_agent ──工具──▶ query_symptom_knowledge
│ ──工具──▶ recommend_department
│ ──输出──▶ AI报告 + 推荐医生
│ ──触发──▶ pre_consult_complete_flow(工作流)
│
├─ 问诊中 GlobalAIFloat
│ └─ pre_consult_agent / follow_up_agent(持久化会话,刷新可恢复)
│
├─ 慢病续药
│ └─ follow_up_agent ──工具──▶ query_medical_record
│ ──工具──▶ query_drug
│ ──输出──▶ 续药合理性评估
│
医生端
│
├─ 问诊大厅 AI 面板
│ └─ diagnosis_agent ──工具──▶ query_medical_record(患者病史)
│ ──工具──▶ search_medical_knowledge(临床指南)
│ ──工具──▶ query_symptom_knowledge
│ ──输出──▶ 鉴别诊断建议(带 tool 调用时间线)
│
├─ 处方创建
│ └─ prescription_agent ──工具──▶ query_drug(药品信息)
│ ──工具──▶ check_drug_interaction(药物相互作用)
│ ──工具──▶ check_contraindication(禁忌症)
│ ──工具──▶ calculate_dosage(剂量验证)
│ ──输出──▶ 安全审核报告 + 警告标注
│
├─ 问诊结束
│ └─ 触发 post_consult_flow(工作流)
│ ├─ agent 节点:生成病历摘要
│ ├─ tool 节点:更新 Consultation.summary
│ └─ human_review 节点:高风险病例转主任审核
│
管理端
│
├─ 智能体监控
│ ├─ AgentExecutionLog 实时展示
│ └─ 工具调用成功率统计
│
├─ 知识库管理
│ ├─ 批量导入(文件/粘贴)
│ ├─ 向量化状态展示
│ └─ 检索测试(语义搜索)
│
└─ 工作流管理
├─ 可视化编辑器(已有)
├─ 执行记录查看(新增)
└─ 触发规则配置(事件 → 工作流 映射)
```
---
## 五、优先级排期
| 优先级 | 任务 | 工作量 | 影响 |
|--------|------|--------|------|
|
**P0**
| 统一 Agent API 客户端(api/agent.ts) | 0.5 天 | 修复认证、错误处理 |
|
**P0**
| 修复工作流 Update 接口 | 0.5 天 | 可视化编辑器可保存 |
|
**P1**
| 诊断辅助接入 diagnosis_agent | 1 天 | 医生获得真正的智能诊断 |
|
**P1**
| 处方创建接入 prescription_agent | 1 天 | 处方安全审核生效 |
|
**P1**
| knowledge_search 工具优先走数据库 | 0.5 天 | 知识库内容生效 |
|
**P1**
| 知识库种子数据导入 | 2 天 | AI 决策有真实依据 |
|
**P1**
| Agent 会话接口补全 + 前端持久化 | 1 天 | 对话历史不丢失 |
|
**P2**
| 工作流触发入口(预问诊/问诊结束) | 2 天 | 自动化流程 |
|
**P2**
| follow_up_agent 接入慢病续药 | 0.5 天 | 随访 Agent 生效 |
|
**P2**
| 管理端智能体执行监控 | 1 天 | 可观测性 |
|
**P3**
| 工具调用日志写入 AgentToolLog | 0.5 天 | 工具层监控 |
|
**P3**
| pgvector 语义检索升级 | 1.5 天 | 语义搜索能力 |
|
**P3**
| 工作流执行记录管理端展示 | 1 天 | 工作流可观测 |
**总工作量预估**
:约 2~3 周(后端 + 前端并行)
---
## 六、质量保障
### 6.1 端到端测试用例(主要场景)
| 场景 | 验收标准 |
|------|---------|
| 患者完成预问诊 | 能看到 AI 报告 + 3 位推荐医生,点击可直接创建问诊 |
| 医生查看 AI 诊断面板 | 能看到带 tool_calls 时间线的鉴别诊断建议 |
| 医生开具多种药物处方 | AI 安全审核自动检测药物相互作用并显示警告 |
| 患者申请慢病续药 | follow_up_agent 返回续药合理性评估 |
| 刷新页面后打开 AI 助手 | 对话历史自动恢复 |
| 知识库搜索测试 | 输入"心力衰竭"返回数据库中的临床指南内容 |
| 管理员查看 Agent 执行日志 | 能看到每次对话的 tool_calls、Token 消耗 |
### 6.2 回归风险点
-
接入
`diagnosis_agent`
后
`ai-assist`
响应时间会增加(Agent 需多轮 LLM 调用),需加 loading 状态
-
工作流异步触发不得影响主流程响应时间(已用 goroutine 隔离)
-
prescription_agent 的安全审核结果仅作为建议,最终决定权仍在医生
---
*文档结束*
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment