phi_agent/agent/
builder.rs1use std::sync::Arc;
8
9use agent_base::{AgentBuilder, ConsecutiveFailureRecovery, Language, ReasoningConfig, ReasoningEffort};
10
11use crate::agent::compression::SummarizingMiddleware;
12
13pub fn base_agent_builder(llm_client: Arc<dyn agent_base::LlmClient>) -> AgentBuilder {
25 let max_tool_output_chars = match std::env::var("PHI_MAX_TOOL_OUTPUT_CHARS") {
29 Ok(value) => match value.trim().parse::<usize>() {
30 Ok(n) => n,
31 Err(_) => {
32 tracing::warn!(
33 value = %value,
34 "PHI_MAX_TOOL_OUTPUT_CHARS is not a valid integer; falling back to default 4000"
35 );
36 4000
37 },
38 },
39 Err(_) => 4000,
40 };
41
42 AgentBuilder::new(llm_client.clone())
43 .language(Language::En)
44 .reasoning(ReasoningConfig { effort: Some(ReasoningEffort::Medium), ..Default::default() })
45 .enable_thought(true)
46 .enable_thinking(true)
47 .max_sessions(50)
48 .max_turns_per_session(100)
49 .execution_max_turns(200)
50 .max_message_tokens(50_000)
51 .max_tool_output_chars(max_tool_output_chars)
52 .error_recovery(Arc::new(ConsecutiveFailureRecovery::new(3)))
53 .middleware(SummarizingMiddleware::new(llm_client))
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use async_trait::async_trait;
63 use futures_core::Stream;
64 use std::pin::Pin;
65 use std::task::{Context, Poll};
66
67 struct StubClient;
68 struct EmptyStream;
69
70 impl Stream for EmptyStream {
71 type Item = agent_base::AgentResult<agent_base::StreamChunk>;
72 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
73 Poll::Ready(None)
74 }
75 }
76
77 #[async_trait]
78 impl agent_base::LlmClient for StubClient {
79 async fn chat(
80 &self,
81 _messages: &[agent_base::ChatMessage],
82 _tools: &[serde_json::Value],
83 _reasoning: Option<&agent_base::ReasoningConfig>,
84 _response_format: Option<&agent_base::ResponseFormat>,
85 ) -> agent_base::AgentResult<serde_json::Value> {
86 Ok(serde_json::json!({"choices":[{"message":{"content":"stub"}}]}))
87 }
88 async fn chat_stream(
89 &self,
90 _messages: &[agent_base::ChatMessage],
91 _tools: &[serde_json::Value],
92 _reasoning: Option<&agent_base::ReasoningConfig>,
93 _response_format: Option<&agent_base::ResponseFormat>,
94 ) -> agent_base::AgentResult<Pin<Box<dyn Stream<Item = agent_base::AgentResult<agent_base::StreamChunk>> + Send>>>
95 {
96 Ok(Box::pin(EmptyStream))
97 }
98 fn capabilities(&self) -> agent_base::LlmCapabilities {
99 agent_base::LlmCapabilities {
100 supports_streaming: true,
101 supports_tools: true,
102 supports_vision: false,
103 supports_thinking: true,
104 max_context_tokens: Some(128_000),
105 max_output_tokens: Some(16_384),
106 }
107 }
108 }
109
110 #[test]
111 fn test_max_tool_output_chars_default() {
112 unsafe { std::env::remove_var("PHI_MAX_TOOL_OUTPUT_CHARS") };
113 let builder = base_agent_builder(Arc::new(StubClient));
114 let _ = builder;
115 }
116
117 #[test]
118 fn test_max_tool_output_chars_custom() {
119 unsafe { std::env::set_var("PHI_MAX_TOOL_OUTPUT_CHARS", "8000") };
120 let builder = base_agent_builder(Arc::new(StubClient));
121 let _ = builder;
122 unsafe { std::env::remove_var("PHI_MAX_TOOL_OUTPUT_CHARS") };
123 }
124
125 #[test]
126 fn test_max_tool_output_chars_invalid_fallback() {
127 unsafe { std::env::set_var("PHI_MAX_TOOL_OUTPUT_CHARS", "not-a-number") };
128 let builder = base_agent_builder(Arc::new(StubClient));
129 let _ = builder;
130 unsafe { std::env::remove_var("PHI_MAX_TOOL_OUTPUT_CHARS") };
131 }
132}