feat: Agent 思考模式前端可控、子代理全链路持久化、权限系统、工具 ID 追踪体系、前端面板与文档架构重构
- AgentConfig/LlmClient 新增 enable_thinking 参数,前端 SSE 请求传递 thinking 开关,仅千问/DashScope 时启用 - 完善权限系统,支持细粒度的权限控制和用户权限申请 - delegate_research 工具重命名为 subagent,SubAgentTool/SubAgentRunner 重构 - 子代理消息(system/user/assistant/tool)持久化到 agent_messages 表,带 agent_name 标识 - 子代理活动日志(工具调用列表+思考摘要)注入返回结果,Hooks 获得正确 session_id 和 subagent_name - LLM 工具调用 ID 回退生成 UUID(llm.rs),ToolCall/ToolResult SSE 事件增加 id/tool_call_id 双字段 - ToolContext 扩展 session_id/sse_tx/enable_thinking 字段,executor 统一注入而非构造函数传参 - agent_messages 新增 metadata+raw_json 列,agent_sessions 暴露 summary 字段 - 删除文件级 transcript 快照(compact.rs),改为依赖 DB 持久化 - ResearchAgentPanel 重写:TimelineItem 类型替代 StreamStep,支持会话历史回放 - 新增 AgentMetricsPanel/AskUserQuestionCard/AuditLogViewer 三个前端组件,types.ts 完整类型定义 - docs/architecture/ 分层重组:概览/核心模块/核心工作流 + agent/ 子目录 11 篇专题文档 - docs/api.md 补充 RAG/Target/Agent 接口,docs/development.md 新建开发指南 - .env.example 完全重写,补充 FALLBACK_MODEL 等变量说明
This commit is contained in:
+92
-19
@@ -26,6 +26,9 @@ use crate::agent::runtime::{AgentRuntime, AgentStreamEvent};
|
||||
pub struct AgentChatRequest {
|
||||
pub question: String,
|
||||
pub session_id: Option<String>,
|
||||
/// 是否启用 LLM 思考模式(默认关闭)
|
||||
#[serde(default)]
|
||||
pub thinking: bool,
|
||||
}
|
||||
|
||||
pub async fn chat_agent(
|
||||
@@ -37,7 +40,7 @@ pub async fn chat_agent(
|
||||
req.question, req.session_id
|
||||
);
|
||||
|
||||
let runtime = AgentRuntime::new(Arc::clone(&state));
|
||||
let runtime = AgentRuntime::new(Arc::clone(&state)).with_thinking(req.thinking);
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<AgentStreamEvent>();
|
||||
|
||||
let question = req.question.clone();
|
||||
@@ -89,6 +92,7 @@ pub struct SessionSummary {
|
||||
pub title: String,
|
||||
pub model: String,
|
||||
pub turn_count: i32,
|
||||
pub summary: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
@@ -101,7 +105,7 @@ pub async fn list_sessions(
|
||||
let offset = params.offset.unwrap_or(0);
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT session_id, title, model, turn_count, created_at, updated_at \
|
||||
"SELECT session_id, title, model, turn_count, summary, created_at, updated_at \
|
||||
FROM agent_sessions \
|
||||
WHERE deleted_at IS NULL \
|
||||
ORDER BY updated_at DESC \
|
||||
@@ -125,8 +129,9 @@ pub async fn list_sessions(
|
||||
title: r.get(1),
|
||||
model: r.get(2),
|
||||
turn_count: r.get(3),
|
||||
created_at: r.get(4),
|
||||
updated_at: r.get(5),
|
||||
summary: r.get(4),
|
||||
created_at: r.get(5),
|
||||
updated_at: r.get(6),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -145,6 +150,7 @@ pub struct SessionDetail {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MessageRecord {
|
||||
pub id: i64,
|
||||
pub agent_name: String,
|
||||
pub turn_index: i32,
|
||||
pub step_index: i32,
|
||||
pub role: String,
|
||||
@@ -163,7 +169,7 @@ pub async fn get_session(
|
||||
) -> Result<Json<SessionDetail>, (StatusCode, String)> {
|
||||
// 查询会话元信息
|
||||
let session_row = sqlx::query(
|
||||
"SELECT session_id, title, model, turn_count, created_at, updated_at \
|
||||
"SELECT session_id, title, model, turn_count, summary, created_at, updated_at \
|
||||
FROM agent_sessions \
|
||||
WHERE session_id = ? AND deleted_at IS NULL",
|
||||
)
|
||||
@@ -183,13 +189,14 @@ pub async fn get_session(
|
||||
title: session_row.get(1),
|
||||
model: session_row.get(2),
|
||||
turn_count: session_row.get(3),
|
||||
created_at: session_row.get(4),
|
||||
updated_at: session_row.get(5),
|
||||
summary: session_row.get(4),
|
||||
created_at: session_row.get(5),
|
||||
updated_at: session_row.get(6),
|
||||
};
|
||||
|
||||
// 查询消息列表
|
||||
// 查询消息列表(包含 lead 和 subagent 消息,前端按 agent_name/metadata 区分渲染)
|
||||
let msg_rows = sqlx::query(
|
||||
"SELECT id, turn_index, step_index, role, content, thought, tool_calls, tool_call_id, token_count, metadata, created_at \
|
||||
"SELECT id, agent_name, turn_index, step_index, role, content, thought, tool_calls, tool_call_id, token_count, metadata, created_at \
|
||||
FROM agent_messages \
|
||||
WHERE session_id = ? \
|
||||
ORDER BY id ASC"
|
||||
@@ -202,21 +209,22 @@ pub async fn get_session(
|
||||
let messages: Vec<MessageRecord> = msg_rows
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let tool_calls_json: Option<String> = r.get(6);
|
||||
let metadata_json: Option<String> = r.get(9);
|
||||
let tool_calls_json: Option<String> = r.get(7);
|
||||
let metadata_json: Option<String> = r.get(10);
|
||||
|
||||
MessageRecord {
|
||||
id: r.get(0),
|
||||
turn_index: r.get(1),
|
||||
step_index: r.get(2),
|
||||
role: r.get(3),
|
||||
content: r.get(4),
|
||||
thought: r.get(5),
|
||||
agent_name: r.get(1),
|
||||
turn_index: r.get(2),
|
||||
step_index: r.get(3),
|
||||
role: r.get(4),
|
||||
content: r.get(5),
|
||||
thought: r.get(6),
|
||||
tool_calls: tool_calls_json.and_then(|s| serde_json::from_str(&s).ok()),
|
||||
tool_call_id: r.get(7),
|
||||
token_count: r.get(8),
|
||||
tool_call_id: r.get(8),
|
||||
token_count: r.get(9),
|
||||
metadata: metadata_json.and_then(|s| serde_json::from_str(&s).ok()),
|
||||
created_at: r.get(10),
|
||||
created_at: r.get(11),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -473,3 +481,68 @@ pub async fn get_pending_questions(
|
||||
.collect();
|
||||
Json(questions)
|
||||
}
|
||||
|
||||
// ── POST /api/chat/sessions/:id/permissions/respond ──
|
||||
// 用户响应权限请求
|
||||
|
||||
pub async fn respond_permission(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(session_id): Path<String>,
|
||||
Json(req): Json<super::PermissionResponse>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
|
||||
let mut perms = match state.pending_permissions.lock() {
|
||||
Ok(p) => p,
|
||||
Err(_) => return Err((StatusCode::INTERNAL_SERVER_ERROR, "内部状态异常".into())),
|
||||
};
|
||||
|
||||
// 按 tool_call_id 查找匹配的权限请求
|
||||
let perm_id = perms
|
||||
.iter()
|
||||
.find(|(_, p)| p.tool_call_id == req.tool_call_id)
|
||||
.map(|(id, _)| id.clone());
|
||||
|
||||
match perm_id {
|
||||
Some(id) => {
|
||||
let perm = perms.remove(&id).unwrap();
|
||||
match perm.response_tx.send(req) {
|
||||
Ok(()) => {
|
||||
info!(
|
||||
"[API] 用户响应了权限请求: session={} tool_call_id={}",
|
||||
session_id, perm.tool_call_id
|
||||
);
|
||||
Ok(Json(serde_json::json!({"status": "ok"})))
|
||||
}
|
||||
Err(_) => Err((StatusCode::GONE, "权限请求已超时或已处理".into())),
|
||||
}
|
||||
}
|
||||
None => Err((
|
||||
StatusCode::NOT_FOUND,
|
||||
"未找到该权限请求(可能已超时或已处理)".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// ── GET /api/chat/sessions/:id/permissions ──
|
||||
// 获取当前待处理的权限请求(前端轮询)
|
||||
|
||||
pub async fn get_pending_permissions(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Json<Vec<serde_json::Value>> {
|
||||
let perms = match state.pending_permissions.lock() {
|
||||
Ok(p) => p,
|
||||
Err(_) => return Json(Vec::new()),
|
||||
};
|
||||
let result: Vec<serde_json::Value> = perms
|
||||
.iter()
|
||||
.map(|(id, p)| {
|
||||
serde_json::json!({
|
||||
"permission_id": id,
|
||||
"tool_call_id": p.tool_call_id,
|
||||
"tool_name": p.tool_name,
|
||||
"message": p.message,
|
||||
"arguments": p.arguments,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Json(result)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user