Skip to main content

agent_base/types/
config.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
5pub enum Language {
6    En,
7    Zh,
8}
9
10impl Default for Language {
11    fn default() -> Self {
12        Language::En
13    }
14}
15
16impl std::fmt::Display for Language {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Language::En => write!(f, "en"),
20            Language::Zh => write!(f, "zh"),
21        }
22    }
23}
24
25#[derive(Clone, Debug)]
26pub struct RetryConfig {
27    pub max_retries: u32,
28    pub initial_backoff_ms: u64,
29    pub max_backoff_ms: u64,
30    pub backoff_multiplier: f64,
31    pub jitter: bool,
32}
33
34impl Default for RetryConfig {
35    fn default() -> Self {
36        Self {
37            max_retries: 3,
38            initial_backoff_ms: 500,
39            max_backoff_ms: 10_000,
40            backoff_multiplier: 2.0,
41            jitter: true,
42        }
43    }
44}
45
46impl RetryConfig {
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    pub fn max_retries(mut self, n: u32) -> Self {
52        self.max_retries = n;
53        self
54    }
55
56    pub fn initial_backoff_ms(mut self, ms: u64) -> Self {
57        self.initial_backoff_ms = ms;
58        self
59    }
60
61    pub fn max_backoff_ms(mut self, ms: u64) -> Self {
62        self.max_backoff_ms = ms;
63        self
64    }
65
66    pub fn no_jitter(mut self) -> Self {
67        self.jitter = false;
68        self
69    }
70}
71
72#[derive(Clone, Debug)]
73pub enum ResponseFormat {
74    JsonObject,
75    JsonSchema {
76        name: String,
77        schema: Value,
78    },
79}
80
81impl ResponseFormat {
82    pub fn to_api_value(&self) -> Value {
83        match self {
84            ResponseFormat::JsonObject => {
85                serde_json::json!({ "type": "json_object" })
86            }
87            ResponseFormat::JsonSchema { name, schema } => {
88                serde_json::json!({
89                    "type": "json_schema",
90                    "json_schema": {
91                        "name": name,
92                        "schema": schema,
93                    }
94                })
95            }
96        }
97    }
98}
99
100use crate::llm::ReasoningConfig;
101
102/// Safety configuration for agent runtime guardrails.
103///
104/// These limits are hard constraints enforced by code, not prompt —
105/// the model cannot bypass them regardless of its capability.
106#[derive(Clone, Debug)]
107pub struct SafetyConfig {
108    /// Maximum number of tool calls allowed per turn.
109    /// When exceeded, tool calls are discarded and the LLM is forced to summarize.
110    /// Default: 128.
111    pub max_tool_calls_per_turn: usize,
112
113    /// Maximum consecutive failures for the same tool before stopping retries.
114    /// Default: 3.
115    pub max_consecutive_failures: usize,
116}
117
118impl Default for SafetyConfig {
119    fn default() -> Self {
120        Self {
121            max_tool_calls_per_turn: 128,
122            max_consecutive_failures: 3,
123        }
124    }
125}
126
127#[derive(Clone, Debug)]
128pub struct AgentConfig {
129    pub system_prompt: Option<String>,
130    /// Controls whether to include the reasoning content in LLM responses.
131    ///
132    /// Distinction from `reasoning.enabled`:
133    /// - `enable_thought`: controls whether the `reasoning_content` field is forwarded
134    ///   to consumers (i.e., "show the thinking process")
135    /// - `reasoning.enabled`: controls whether the model's extended thinking / reasoning
136    ///   mode is enabled (i.e., "let the model think deeply")
137    ///
138    /// Both are usually kept in sync, but can be controlled independently. For example,
139    /// to enable deep thinking without showing the process, set `enable_thought = false`
140    /// and `reasoning.enabled = true`.
141    pub enable_thought: bool,
142    /// Reasoning/thinking configuration that controls LLM reasoning behavior.
143    ///
144    /// - `enabled`: whether to enable extended thinking mode (equivalent to builder's `enable_thinking`)
145    /// - `budget_tokens`: thinking token budget cap
146    /// - `effort`: reasoning intensity/depth (semantics vary by provider)
147    pub reasoning: Option<ReasoningConfig>,
148    pub language: Language,
149    pub execution: ExecutionConfig,
150    pub llm: LlmConfig,
151    pub tool: ToolConfig,
152    pub session: SessionConfig,
153    pub safety: SafetyConfig,
154}
155
156impl Default for AgentConfig {
157    fn default() -> Self {
158        Self {
159            system_prompt: None,
160            enable_thought: false,
161            reasoning: None,
162            language: Language::default(),
163            execution: ExecutionConfig::default(),
164            llm: LlmConfig::default(),
165            tool: ToolConfig::default(),
166            session: SessionConfig::default(),
167            safety: SafetyConfig::default(),
168        }
169    }
170}
171
172impl AgentConfig {
173
174    /// Validate the configuration, returning an error for invalid values.
175    pub fn validate(&self) -> crate::types::AgentResult<()> {
176        use crate::types::AgentError;
177
178        if let Some(max_turns) = self.execution.max_turns {
179            if max_turns == 0 {
180                return Err(AgentError::config_error(
181                    "execution.max_turns must be > 0".to_string(),
182                ));
183            }
184        }
185
186        if let Some(max_sessions) = self.session.max_sessions {
187            if max_sessions == 0 {
188                return Err(AgentError::config_error(
189                    "session.max_sessions must be > 0".to_string(),
190                ));
191            }
192        }
193
194        if let Some(tool_timeout_ms) = self.tool.tool_timeout_ms {
195            if tool_timeout_ms == 0 {
196                return Err(AgentError::config_error(
197                    "tool.tool_timeout_ms must be > 0".to_string(),
198                ));
199            }
200        }
201
202        if self.safety.max_tool_calls_per_turn == 0 {
203            return Err(AgentError::config_error(
204                "safety.max_tool_calls_per_turn must be > 0".to_string(),
205            ));
206        }
207
208        Ok(())
209    }
210}
211
212#[derive(Clone, Debug)]
213pub struct ExecutionConfig {
214    pub max_turns: Option<u32>,
215    pub approval_timeout_ms: Option<u64>,
216    pub fail_on_persist_error: bool,
217}
218
219impl Default for ExecutionConfig {
220    fn default() -> Self {
221        Self {
222            max_turns: None,
223            approval_timeout_ms: None,
224            fail_on_persist_error: false,
225        }
226    }
227}
228
229#[derive(Clone, Debug)]
230pub struct LlmConfig {
231    pub response_format: Option<ResponseFormat>,
232    pub llm_retry: Option<RetryConfig>,
233}
234
235impl Default for LlmConfig {
236    fn default() -> Self {
237        Self {
238            response_format: None,
239            llm_retry: None,
240        }
241    }
242}
243
244#[derive(Clone, Debug)]
245pub struct ToolConfig {
246    pub tool_timeout_ms: Option<u64>,
247    pub max_tool_output_chars: Option<usize>,
248    pub tool_error_retry_prompt: Option<String>,
249}
250
251impl Default for ToolConfig {
252    fn default() -> Self {
253        Self {
254            tool_timeout_ms: None,
255            max_tool_output_chars: None,
256            tool_error_retry_prompt: None,
257        }
258    }
259}
260
261#[derive(Clone, Debug)]
262pub struct SessionConfig {
263    /// 最大 session 数量,超过时 LRU 逐出整个 session(从内存卸载,数据保留)
264    /// None = 不限制
265    pub max_sessions: Option<usize>,
266
267    /// 单个 session 最大保留轮数,超过从前面截掉最旧轮次
268    /// None = 不限制
269    pub max_turns_per_session: Option<usize>,
270
271    /// 单条消息 token 上限(安全阀),超过不存入 session 历史
272    /// 阈值应设很高(如 100k),只拦异常情况
273    /// None = 不限制
274    pub max_message_tokens: Option<usize>,
275}
276
277impl Default for SessionConfig {
278    fn default() -> Self {
279        Self {
280            max_sessions: None,
281            max_turns_per_session: None,
282            max_message_tokens: None,
283        }
284    }
285}