Files
qiming/qiming-rcoder/specs/agent-config-instruction/01-agent-config-instruction-spec.md
2026-06-01 13:54:52 +08:00

34 KiB
Raw Blame History

Agent 配置入参功能实现计划

1. 需求概述

1.1 功能目标

/chat 接口增加三个可选配置入参,允许用户自定义 Agent 的提示词和配置:

参数 类型 说明
system_prompt Option<String> 系统提示词,可选
user_prompt Option<String> 用户提示词模板,可选
agent_config Option<ChatAgentConfig> Agent 运行时配置MCP 服务器等),可选

1.2 设计原则:职责分离,无冲突

提示词由独立入参控制,agent_config 只负责运行时配置

  • system_prompt:直接传入系统提示词文本
  • user_prompt:直接传入用户提示词模板(支持 {user_prompt} 变量)
  • agent_config:只包含 MCP 服务器等运行时配置,不包含提示词配置

这样设计的优势:

  1. 消除歧义:用户不会困惑"到底用哪个提示词"
  2. 简化代码:不需要复杂的优先级合并逻辑
  3. 职责清晰:提示词归提示词,配置归配置

1.3 变量注入

user_prompt 支持模板变量 {user_prompt},运行时用 /chatprompt 字段值替换

1.4 配置生效逻辑

用户入参                              内部组装
───────────────────────────────────────────────────────────────────
system_prompt ────────────────────────→ AgentStartConfig.system_prompt
user_prompt ──────────────────────────→ 模板替换后的最终 prompt
agent_config.agent_server ────────────→ AgentConfig (合并覆盖默认配置)
agent_config.context_servers ─────────→ AgentStartConfig.mcp_servers

(如果入参为空,使用默认配置)

2. 新增数据结构定义

2.1 结构对比:内部配置 vs 外部入参

内部配置 (AgentServersConfig) 外部入参 (ChatAgentConfig) 说明
agent_servers: HashMap<String, AgentConfig> agent_server: Option<ChatAgentServerConfig> 内部支持多 Agent外部只需单个
context_servers: HashMap<String, ContextServerConfig> context_servers: HashMap<String, ChatContextServerConfig> MCP 工具可以有多个,保持一致

2.2 ChatAgentConfig 结构体

位置: crates/shared_types/src/chat_agent_config.rs (新文件)

//! Chat 接口专用的 Agent 配置结构体
//!
//! 简化版本,只包含运行时配置,不包含提示词配置。
//! 提示词由独立的 system_prompt 和 user_prompt 入参控制。

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use utoipa::ToSchema;

/// Chat 接口的 Agent 配置
///
/// 包含单个 Agent 的运行时配置和多个 MCP 服务器配置。
/// 提示词由独立入参 (system_prompt, user_prompt) 控制,不在此结构中。
#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
pub struct ChatAgentConfig {
    /// 单个 Agent 服务器配置(可选)
    ///
    /// 用于覆盖默认的 Agent 执行命令、参数、环境变量等。
    /// 如果不传,使用内部默认配置 (claude-code-acp)。
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub agent_server: Option<ChatAgentServerConfig>,

    /// MCP 服务器配置Context Servers
    ///
    /// 可配置多个 MCP 工具服务器。
    /// 如果不传,使用内部默认的 MCP 配置。
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub context_servers: HashMap<String, ChatContextServerConfig>,
}

/// 单个 Agent 服务器配置
///
/// 对应内部 AgentConfig 的简化版本,只暴露必要的运行时配置。
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ChatAgentServerConfig {
    /// Agent 标识符(可选,默认使用 "claude-code-acp"
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub agent_id: Option<String>,

    /// 执行命令(如 "claude-code-acp", "custom-agent"
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub command: Option<String>,

    /// 命令参数
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub args: Option<Vec<String>>,

    /// 环境变量
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub env: Option<HashMap<String, String>>,

    /// 元数据(可选)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metadata: Option<HashMap<String, String>>,
}

/// MCP 服务器配置
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ChatContextServerConfig {
    /// 服务器来源类型: "custom" 或 "local"
    #[serde(default = "default_custom")]
    pub source: String,

    /// 是否启用
    #[serde(default = "default_true")]
    pub enabled: bool,

    /// 执行命令 (如 "bunx", "uvx", "npx")
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub command: Option<String>,

    /// 命令参数
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub args: Option<Vec<String>>,

    /// 环境变量
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub env: Option<HashMap<String, String>>,
}

fn default_custom() -> String {
    "custom".to_string()
}

fn default_true() -> bool {
    true
}

impl Default for ChatAgentServerConfig {
    fn default() -> Self {
        Self {
            agent_id: None,
            command: None,
            args: None,
            env: None,
            metadata: None,
        }
    }
}

impl Default for ChatContextServerConfig {
    fn default() -> Self {
        Self {
            source: "custom".to_string(),
            enabled: true,
            command: None,
            args: None,
            env: None,
        }
    }
}

impl ChatAgentConfig {
    /// 检查是否有 Agent 服务器配置
    pub fn has_agent_server(&self) -> bool {
        self.agent_server.is_some()
    }

    /// 检查是否有 MCP 服务器配置
    pub fn has_context_servers(&self) -> bool {
        !self.context_servers.is_empty()
    }

    /// 获取启用的 MCP 服务器
    pub fn get_enabled_context_servers(&self) -> HashMap<String, &ChatContextServerConfig> {
        self.context_servers
            .iter()
            .filter(|(_, config)| config.enabled)
            .map(|(name, config)| (name.clone(), config))
            .collect()
    }
}

impl ChatAgentServerConfig {
    /// 获取 Agent ID默认返回 "claude-code-acp"
    pub fn get_agent_id(&self) -> &str {
        self.agent_id.as_deref().unwrap_or("claude-code-acp")
    }
}

2.3 JSON 请求示例(完整配置)

{
  "prompt": "帮我写一个 Hello World",
  "project_id": "my_project",
  "system_prompt": "你是一个专业的 Rust 开发者,擅长编写高性能代码",
  "user_prompt": "请用 Rust 语言完成以下需求:{user_prompt}",
  "agent_config": {
    "agent_server": {
      "agent_id": "claude-code-acp",
      "command": "claude-code-acp",
      "args": [],
      "env": {
        "ANTHROPIC_MODEL": "claude-sonnet-4-20250514",
        "RUST_LOG": "debug"
      }
    },
    "context_servers": {
      "context7": {
        "source": "custom",
        "enabled": true,
        "command": "bunx",
        "args": ["-y", "@upstash/context7-mcp"]
      },
      "fetch": {
        "source": "custom",
        "enabled": true,
        "command": "uvx",
        "args": ["mcp-server-fetch"]
      }
    }
  }
}

2.4 JSON 请求示例(只配置 MCP

{
  "prompt": "帮我写一个 Hello World",
  "system_prompt": "你是 Rust 专家",
  "agent_config": {
    "context_servers": {
      "context7": {
        "enabled": true,
        "command": "bunx",
        "args": ["-y", "@upstash/context7-mcp"]
      }
    }
  }
}

2.5 最简请求示例

{
  "prompt": "帮我写一个 Hello World",
  "system_prompt": "你是 Rust 专家"
}

说明: 不传 agent_config 时,使用默认的 Agent 和 MCP 服务器配置。


3. 现有架构分析

3.1 数据流路径

HTTP /chat (rcoder)
    ↓
gRPC ChatRequest (shared_types)
    ↓
agent_runner gRPC AgentServiceImpl
    ↓
ChatPromptBuilder → PromptMessage
    ↓
AcpAgentWorker → AcpSessionManager
    ↓
ClaudeCodeLauncher (agent_abstraction)
    ↓
NewSessionRequest._meta.systemPrompt (ACP协议)

3.2 关键文件和结构体

文件 结构体/模块 作用
crates/rcoder/src/handler/chat_handler.rs ChatRequest HTTP 请求结构体
crates/shared_types/proto/agent.proto ChatRequest gRPC 请求定义
crates/agent_runner/src/grpc/agent_service_impl.rs AgentServiceImpl::chat() gRPC 服务实现
crates/agent_config/src/config/servers_config.rs AgentServersConfig Agent 配置集合
crates/agent_abstraction/src/traits/agent.rs AgentStartConfig Agent 启动配置
crates/agent_abstraction/src/compat/claude_code_launcher.rs ClaudeCodeLauncher Agent 启动器

3.3 现有默认配置结构

AgentServersConfig (默认 JSON 配置):

{
  "agent_servers": {
    "claude-code-acp": {
      "agent_id": "claude-code-acp",
      "system_prompt": {
        "source": "embedded",
        "template": "",
        "enabled": true
      },
      "user_prompt": {
        "template": "{user_prompt}",
        "enabled": false
      }
    }
  },
  "context_servers": {
    "context7": { ... },
    "fetch": { ... }
  }
}

4. 详细实现计划

4.1 阶段一:新增 ChatAgentConfig 结构体

4.1.1 创建新文件

文件: crates/shared_types/src/chat_agent_config.rs

内容见上文 2.1 节

4.1.2 导出新类型

文件: crates/shared_types/src/lib.rs

mod chat_agent_config;
pub use chat_agent_config::*;

4.2 阶段二:修改 HTTP/gRPC 请求结构

4.2.1 修改 rcoder ChatRequest (HTTP)

文件: crates/rcoder/src/handler/chat_handler.rs

use shared_types::ChatAgentConfig;

#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
pub struct ChatRequest {
    // ... 现有字段 ...

    /// 可选的系统提示词,覆盖默认配置
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[schema(example = "你是一个专业的 Rust 开发者")]
    pub system_prompt: Option<String>,

    /// 可选的用户提示词模板,支持 {user_prompt} 变量替换
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[schema(example = "请用 Rust 完成:{user_prompt}")]
    pub user_prompt: Option<String>,

    /// 可选的 Agent 运行时配置MCP 服务器等)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub agent_config: Option<ChatAgentConfig>,
}

4.2.2 修改 gRPC ChatRequest

文件: crates/shared_types/proto/agent.proto

message ChatRequest {
  string project_id = 1;
  string session_id = 2;
  string prompt = 3;
  optional ModelProviderConfig model_config = 4;
  repeated Attachment attachments = 5;
  optional string request_id = 6;
  repeated string data_source_attachments = 7;

  // 新增字段
  optional string system_prompt = 8;              // 系统提示词
  optional string user_prompt = 9;                // 用户提示词模板
  optional ChatAgentConfig agent_config = 10;     // Agent 运行时配置
}

// Agent 运行时配置
message ChatAgentConfig {
  optional ChatAgentServerConfig agent_server = 1;              // 单个 Agent 配置
  map<string, ChatContextServerConfig> context_servers = 2;     // 多个 MCP 配置
}

// 单个 Agent 服务器配置
message ChatAgentServerConfig {
  optional string agent_id = 1;           // Agent 标识符
  optional string command = 2;            // 执行命令
  repeated string args = 3;               // 命令参数
  map<string, string> env = 4;            // 环境变量
  map<string, string> metadata = 5;       // 元数据
}

// MCP 服务器配置
message ChatContextServerConfig {
  string source = 1;                      // "custom" 或 "local"
  bool enabled = 2;
  optional string command = 3;
  repeated string args = 4;
  map<string, string> env = 5;
}

4.3 阶段三:修改 shared_types 数据结构

4.3.1 修改 ChatPrompt

文件: crates/shared_types/src/lib.rs

use crate::ChatAgentConfig;

#[derive(Debug, Clone, Builder)]
pub struct ChatPrompt {
    // ... 现有字段 ...

    /// 可选的系统提示词覆盖
    #[builder(default)]
    pub system_prompt_override: Option<String>,

    /// 可选的用户提示词模板覆盖
    #[builder(default)]
    pub user_prompt_template_override: Option<String>,

    /// 可选的 Agent 运行时配置覆盖
    #[builder(default)]
    pub agent_config_override: Option<ChatAgentConfig>,
}

4.4 阶段四:修改 agent_abstraction 层

4.4.1 修改 PromptMessage

文件: crates/agent_abstraction/src/traits/agent.rs

use shared_types::ChatAgentConfig;

#[derive(Debug, Clone)]
pub struct PromptMessage {
    // ... 现有字段 ...

    /// 系统提示词覆盖
    pub system_prompt_override: Option<String>,

    /// 用户提示词模板覆盖
    pub user_prompt_template_override: Option<String>,

    /// Agent 运行时配置覆盖MCP 服务器等)
    pub agent_config_override: Option<ChatAgentConfig>,
}

4.4.2 修改 From 实现

impl From<shared_types::ChatPrompt> for PromptMessage {
    fn from(chat_prompt: shared_types::ChatPrompt) -> Self {
        Self {
            // ... 现有字段映射 ...
            system_prompt_override: chat_prompt.system_prompt_override,
            user_prompt_template_override: chat_prompt.user_prompt_template_override,
            agent_config_override: chat_prompt.agent_config_override,
        }
    }
}

4.5 阶段五:新增配置组装工具

4.5.1 新增 PromptConfigAssembler

文件: crates/agent_config/src/config/prompt_assembler.rs (新文件)

//! 提示词配置组装工具
//!
//! 将用户入参组装为内部使用的配置结构。
//! 简化设计:直接使用入参,无优先级冲突。

use shared_types::{ChatAgentConfig, ChatAgentServerConfig};
use super::servers_config::AgentServersConfig;
use crate::{AgentConfig, ContextServerConfig};
use std::collections::HashMap;

/// 提示词配置组装器
///
/// 职责:
/// 1. 组装系统提示词(入参 > 默认配置)
/// 2. 应用用户提示词模板
/// 3. 组装 Agent 服务器配置
/// 4. 合并 MCP 服务器配置
pub struct PromptConfigAssembler {
    /// 系统提示词入参
    system_prompt: Option<String>,
    /// 用户提示词模板入参
    user_prompt_template: Option<String>,
    /// Agent 运行时配置入参
    agent_config: Option<ChatAgentConfig>,
    /// 默认配置
    default_config: AgentServersConfig,
}

impl PromptConfigAssembler {
    pub fn new(default_config: AgentServersConfig) -> Self {
        Self {
            system_prompt: None,
            user_prompt_template: None,
            agent_config: None,
            default_config,
        }
    }

    pub fn with_system_prompt(mut self, system_prompt: Option<String>) -> Self {
        self.system_prompt = system_prompt;
        self
    }

    pub fn with_user_prompt_template(mut self, template: Option<String>) -> Self {
        self.user_prompt_template = template;
        self
    }

    pub fn with_agent_config(mut self, config: Option<ChatAgentConfig>) -> Self {
        self.agent_config = config;
        self
    }

    /// 获取最终的系统提示词
    ///
    /// 逻辑:入参有值则使用入参,否则使用默认配置
    pub fn get_system_prompt(&self, agent_id: &str) -> String {
        // 入参有值且非空,直接使用
        if let Some(ref sp) = self.system_prompt {
            if !sp.is_empty() {
                return sp.clone();
            }
        }

        // 使用默认配置
        self.default_config.get_system_prompt(agent_id)
    }

    /// 应用用户提示词模板
    ///
    /// 逻辑:
    /// 1. 如果有模板入参,使用模板替换 {user_prompt}
    /// 2. 如果没有模板入参,检查默认配置
    /// 3. 都没有,直接返回原始输入
    pub fn apply_user_prompt(&self, agent_id: &str, user_input: &str) -> String {
        // 入参有模板且非空,使用入参模板
        if let Some(ref template) = self.user_prompt_template {
            if !template.is_empty() {
                return template.replace("{user_prompt}", user_input);
            }
        }

        // 检查默认配置中的 user_prompt 模板
        if let Some(agent) = self.default_config.get_agent(agent_id) {
            if let Some(ref prompt_config) = agent.user_prompt {
                if prompt_config.enabled {
                    return prompt_config.apply(user_input);
                }
            }
        }

        // 无模板,直接返回原始输入
        user_input.to_string()
    }

    /// 获取最终的 Agent 服务器配置
    ///
    /// 逻辑:
    /// 1. 如果入参有 agent_server与默认配置合并入参字段覆盖默认值
    /// 2. 如果入参没有 agent_server使用默认配置
    pub fn get_agent_server_config(&self, default_agent_id: &str) -> AgentConfig {
        // 获取默认的 Agent 配置
        let default_agent = self.default_config
            .get_agent(default_agent_id)
            .cloned()
            .unwrap_or_default();

        // 如果入参有 agent_server 配置,合并覆盖
        if let Some(ref config) = self.agent_config {
            if let Some(ref agent_server) = config.agent_server {
                return self.merge_agent_config(&default_agent, agent_server);
            }
        }

        // 使用默认配置
        default_agent
    }

    /// 合并 Agent 配置(入参覆盖默认值)
    fn merge_agent_config(
        &self,
        default: &AgentConfig,
        override_config: &ChatAgentServerConfig,
    ) -> AgentConfig {
        let mut merged = default.clone();

        // agent_id: 入参有值则覆盖
        if let Some(ref agent_id) = override_config.agent_id {
            merged.agent_id = agent_id.clone();
        }

        // command: 入参有值则覆盖
        if let Some(ref command) = override_config.command {
            merged.command = command.clone();
        }

        // args: 入参有值则覆盖(替换而非追加)
        if let Some(ref args) = override_config.args {
            merged.args = args.clone();
        }

        // env: 入参有值则合并(入参优先)
        if let Some(ref env) = override_config.env {
            for (key, value) in env {
                merged.env.insert(key.clone(), value.clone());
            }
        }

        // metadata: 入参有值则合并
        if let Some(ref metadata) = override_config.metadata {
            for (key, value) in metadata {
                merged.metadata.insert(key.clone(), value.clone());
            }
        }

        merged
    }

    /// 获取最终的 MCP 服务器配置
    ///
    /// 逻辑:入参有配置则使用入参,否则使用默认配置
    pub fn get_context_servers(&self) -> HashMap<String, ContextServerConfig> {
        // 入参有 MCP 配置,使用入参
        if let Some(ref config) = self.agent_config {
            if config.has_context_servers() {
                return config.context_servers.iter()
                    .map(|(name, chat_config)| {
                        let ctx_config = ContextServerConfig {
                            source: chat_config.source.clone(),
                            enabled: chat_config.enabled,
                            command: chat_config.command.clone(),
                            args: chat_config.args.clone(),
                            env: chat_config.env.clone(),
                        };
                        (name.clone(), ctx_config)
                    })
                    .collect();
            }
        }

        // 使用默认配置
        self.default_config.context_servers.clone()
    }

    /// 获取使用的 Agent ID
    ///
    /// 逻辑:入参有指定则使用入参,否则使用默认
    pub fn get_agent_id(&self, default_agent_id: &str) -> String {
        if let Some(ref config) = self.agent_config {
            if let Some(ref agent_server) = config.agent_server {
                return agent_server.get_agent_id().to_string();
            }
        }
        default_agent_id.to_string()
    }
}

4.5.2 导出新模块

文件: crates/agent_config/src/config/mod.rs

pub mod prompt_assembler;
pub use prompt_assembler::PromptConfigAssembler;

文件: crates/agent_config/src/lib.rs

pub use config::prompt_assembler::PromptConfigAssembler;

4.6 阶段六:修改 agent_runner 业务逻辑

4.6.1 修改 gRPC AgentServiceImpl::chat()

文件: crates/agent_runner/src/grpc/agent_service_impl.rs

use shared_types::ChatAgentConfig;

async fn chat(&self, request: Request<GrpcChatRequest>) -> Result<Response<GrpcChatResponse>, Status> {
    let req = request.into_inner();

    // ... 现有验证逻辑 ...

    // 转换 gRPC ChatAgentConfig -> shared_types ChatAgentConfig
    let agent_config_override: Option<ChatAgentConfig> = req.agent_config.map(ChatAgentConfig::from);

    // 构建 ChatPrompt包含覆盖配置
    let chat_prompt = ChatPromptBuilder::default()
        .project_id(project_id.clone())
        .project_path(project_dir)
        .session_id(session_id.clone())
        .prompt(req.prompt)
        .system_prompt_override(req.system_prompt)           // 新增
        .user_prompt_template_override(req.user_prompt)      // 新增
        .agent_config_override(agent_config_override)        // 新增
        // ... 其他字段 ...
        .build()
        .map_err(|e| Status::internal(format!("构建 ChatPrompt 失败: {}", e)))?;

    // ... 后续处理 ...
}

4.6.2 修改 AcpAgentWorker

文件: crates/agent_abstraction/src/session/acp_worker.rs

use agent_config::PromptConfigAssembler;

impl AgentWorker for AcpAgentWorker {
    async fn process_request(&self, request: WorkerRequest) -> Result<WorkerResponse> {
        // 加载默认配置
        let default_config = AgentServersConfig::load_or_default().await;

        // 创建配置组装器
        let assembler = PromptConfigAssembler::new(default_config)
            .with_system_prompt(request.prompt_message.system_prompt_override.clone())
            .with_user_prompt_template(request.prompt_message.user_prompt_template_override.clone())
            .with_agent_config(request.prompt_message.agent_config_override.clone());

        // 获取最终的系统提示词
        let system_prompt = assembler.get_system_prompt("claude-code-acp");

        // 构建 AgentStartConfig
        let start_config = AgentStartConfig::new()
            .with_system_prompt(system_prompt);

        // 获取 MCP 服务器配置
        let context_servers = assembler.get_context_servers();
        // TODO: 将 context_servers 转换为 Vec<McpServer> 传递给 start_config

        // 应用用户提示词模板
        let final_user_prompt = assembler.apply_user_prompt(
            "claude-code-acp",
            &request.prompt_message.content,
        );

        // 更新 prompt_message 的 content 为处理后的用户提示词
        let mut prompt_message = request.prompt_message.clone();
        prompt_message.content = final_user_prompt;

        // ... 创建会话 / 发送 prompt ...
    }
}

4.7 阶段七:修改 ClaudeCodeLauncher

文件: crates/agent_abstraction/src/compat/claude_code_launcher.rs

use shared_types::ChatAgentConfig;

pub async fn load_agent_config_with_override(
    model_provider: Option<&ModelProviderConfig>,
    agent_config_override: Option<&ChatAgentConfig>,
) -> Result<AgentLaunchConfig> {
    // 加载默认配置
    let default_config = AgentServersConfig::load_or_default().await;

    // 合并 context_servers
    let context_servers = if let Some(override_config) = agent_config_override {
        if override_config.has_context_servers() {
            // 使用覆盖的 MCP 配置
            override_config.context_servers.iter()
                .map(|(name, config)| {
                    (name.clone(), ContextServerConfig {
                        source: config.source.clone(),
                        enabled: config.enabled,
                        command: config.command.clone(),
                        args: config.args.clone(),
                        env: config.env.clone(),
                    })
                })
                .collect()
        } else {
            default_config.context_servers.clone()
        }
    } else {
        default_config.context_servers.clone()
    };

    // ... 后续使用 context_servers 构建 AgentLaunchConfig ...
}

5. 测试计划

5.1 单元测试

  1. ChatAgentConfig 结构体测试

    • 测试 JSON 序列化/反序列化
    • 测试默认值
    • 测试 has_agent_server() 方法
    • 测试 has_context_servers() 方法
    • 测试 get_enabled_context_servers() 方法
  2. ChatAgentServerConfig 结构体测试

    • 测试 JSON 序列化/反序列化
    • 测试 get_agent_id() 默认值
    • 测试部分字段覆盖
  3. PromptConfigAssembler 测试

    • 测试入参有值时直接使用入参
    • 测试入参为空时使用默认配置
    • 测试用户提示词模板 {user_prompt} 替换
    • 测试 Agent 服务器配置合并(get_agent_server_config()
    • 测试 Agent 配置字段覆盖command, args, env
    • 测试 MCP 服务器配置合并

5.2 集成测试

# 测试只传 system_prompt
curl -X POST http://localhost:8087/chat \
  -H "Content-Type: application/json" \
  -d '{"prompt": "hello", "system_prompt": "你是 Rust 专家"}'

# 测试只传 user_prompt
curl -X POST http://localhost:8087/chat \
  -H "Content-Type: application/json" \
  -d '{"prompt": "hello", "user_prompt": "请用中文回答:{user_prompt}"}'

# 测试只传 agent_config仅 agent_server
curl -X POST http://localhost:8087/chat \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "hello",
    "agent_config": {
      "agent_server": {
        "env": {
          "ANTHROPIC_MODEL": "claude-sonnet-4-20250514",
          "RUST_LOG": "debug"
        }
      }
    }
  }'

# 测试只传 agent_config仅 MCP 服务器)
curl -X POST http://localhost:8087/chat \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "hello",
    "agent_config": {
      "context_servers": {
        "my-mcp": {
          "enabled": true,
          "command": "bunx",
          "args": ["-y", "my-mcp-server"]
        }
      }
    }
  }'

# 测试 agent_server + context_servers 组合
curl -X POST http://localhost:8087/chat \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "hello",
    "agent_config": {
      "agent_server": {
        "agent_id": "claude-code-acp",
        "env": {"RUST_LOG": "info"}
      },
      "context_servers": {
        "context7": {"enabled": true, "command": "bunx", "args": ["-y", "@upstash/context7-mcp"]}
      }
    }
  }'

# 测试完整组合(所有入参)
curl -X POST http://localhost:8087/chat \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "帮我写一个 Hello World",
    "system_prompt": "你是 Rust 专家",
    "user_prompt": "请用 Rust 完成:{user_prompt}",
    "agent_config": {
      "agent_server": {
        "env": {"ANTHROPIC_MODEL": "claude-sonnet-4-20250514"}
      },
      "context_servers": {
        "context7": {"enabled": true, "command": "bunx", "args": ["-y", "@upstash/context7-mcp"]}
      }
    }
  }'

5.3 端到端测试

  • 验证 Agent 实际使用了自定义的系统提示词
  • 验证用户提示词模板替换正确
  • 验证自定义 agent_server 的 env 变量生效
  • 验证自定义 MCP 服务器配置生效
  • 验证 agent_server 配置的 command/args 覆盖生效

6. 修改文件清单

文件 修改类型 说明
crates/shared_types/src/chat_agent_config.rs 新增 ChatAgentConfig 结构体(简化版)
crates/shared_types/src/lib.rs 修改 导出 ChatAgentConfig
crates/shared_types/proto/agent.proto 修改 添加 gRPC 消息定义
crates/shared_types/src/grpc/agent.rs 自动生成 proto 编译
crates/rcoder/src/handler/chat_handler.rs 修改 HTTP ChatRequest 添加新字段
crates/rcoder/src/grpc/chat_client.rs 修改 传递新字段到 gRPC
crates/agent_runner/src/grpc/agent_service_impl.rs 修改 处理新字段
crates/agent_config/src/config/prompt_assembler.rs 新增 配置组装逻辑
crates/agent_config/src/config/mod.rs 修改 导出新模块
crates/agent_config/src/lib.rs 修改 导出 PromptConfigAssembler
crates/agent_abstraction/src/traits/agent.rs 修改 PromptMessage 添加新字段
crates/agent_abstraction/src/session/acp_worker.rs 修改 使用配置组装器
crates/agent_abstraction/src/compat/claude_code_launcher.rs 修改 支持配置覆盖

7. 风险评估

7.1 兼容性风险

  • 低风险: 所有新字段都是可选的,不影响现有调用
  • 缓解措施: 添加版本检查,确保旧客户端可以正常工作

7.2 性能风险

  • 低风险: 配置组装逻辑简单,不涉及 I/O
  • 缓解措施: 配置组装器使用惰性求值

7.3 安全风险

  • 中风险: agent_config 允许用户传入自定义 MCP 服务器配置
  • 缓解措施:
    • 使用强类型结构体验证配置格式
    • 记录所有配置变更的日志
    • 考虑添加白名单机制(可选)

8. 实现顺序建议

  1. 第一批 (基础结构)

    • 创建 ChatAgentConfig 结构体 (shared_types)
    • 修改 proto 文件,添加 gRPC 消息定义
    • 修改 ChatRequest (HTTP/gRPC)
    • 修改 ChatPrompt 和 PromptMessage
  2. 第二批 (核心逻辑)

    • 实现 PromptConfigAssembler
    • 修改 AgentServiceImpl
    • 修改 AcpAgentWorker
  3. 第三批 (启动器)

    • 修改 ClaudeCodeLauncher
    • 添加 gRPC 类型转换
  4. 第四批 (测试和文档)

    • 编写单元测试
    • 编写集成测试
    • 更新 API 文档 (Swagger/OpenAPI)

9. 附录:类型对照表

9.1 HTTP/gRPC 层

层级 类型名 用途
HTTP ChatRequest.system_prompt: Option<String> 接收系统提示词
HTTP ChatRequest.user_prompt: Option<String> 接收用户提示词模板
HTTP ChatRequest.agent_config: Option<ChatAgentConfig> 接收 Agent 运行时配置
gRPC ChatRequest.system_prompt, user_prompt, agent_config 跨服务传输

9.2 shared_types 层(外部入参结构)

类型名 字段 用途
ChatAgentConfig agent_server, context_servers 外部入参的 Agent 配置(简化版)
ChatAgentServerConfig agent_id, command, args, env, metadata 单个 Agent 服务器配置
ChatContextServerConfig source, enabled, command, args, env 单个 MCP 服务器配置

9.3 agent_config 层(内部配置结构)

类型名 字段 用途
AgentServersConfig agent_servers, context_servers 内部完整配置(支持多 Agent
AgentConfig agent_id, command, args, env, system_prompt, user_prompt, ... 内部 Agent 配置(包含提示词模板)
ContextServerConfig source, enabled, command, args, env 内部 MCP 配置

9.4 业务逻辑层

层级 类型名 用途
agent_config PromptConfigAssembler 配置组装逻辑(外部 → 内部)
agent_abstraction PromptMessage.*_override Agent 层配置传递
agent_abstraction AgentStartConfig Agent 启动时的最终配置

9.5 外部入参 vs 内部配置 映射关系

外部入参 (ChatAgentConfig)              内部配置 (AgentServersConfig)
─────────────────────────────────────────────────────────────────────
ChatAgentServerConfig (单个)       →    AgentConfig (HashMap 中的一个)
  - agent_id                       →      - agent_id
  - command                        →      - command
  - args                           →      - args
  - env                            →      - env
  - metadata                       →      - metadata
  (无 system_prompt)               →      - system_prompt (保留默认)
  (无 user_prompt)                 →      - user_prompt (保留默认)

ChatContextServerConfig (多个)     →    ContextServerConfig (多个)
  - source                         →      - source
  - enabled                        →      - enabled
  - command                        →      - command
  - args                           →      - args
  - env                            →      - env

10. 设计对比:简化方案 vs 原方案

方面 原方案 简化方案(当前)
agent_config 内容 包含 system_prompt, user_prompt, context_servers 包含 agent_server (单个) + context_servers (多个)
提示词配置 agent_config 独立入参 (system_prompt, user_prompt)
优先级冲突 存在(入参 vs agent_config 中的提示词) 无(职责分离)
Agent 配置 无法覆盖 支持单个 Agent 的 command/args/env 覆盖
MCP 配置 可配置多个 可配置多个(保持一致)
合并逻辑 复杂的三级优先级 简单的二级(入参 > 默认)
用户理解成本 高(需要理解优先级规则) 低(职责分离,直观)
代码复杂度 ConfigMerger 需要处理多种情况 PromptConfigAssembler 逻辑简单