Skip to main content

lc_agents/
types.rs

1// lc-agents/src/types.rs
2//! Agent related type definitions
3
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Agent action
8///
9/// Represents an action that the Agent decides to execute (usually a tool call).
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AgentAction {
12    /// Tool name
13    pub tool: String,
14
15    /// Tool input (string or JSON object)
16    pub tool_input: ToolInput,
17
18    /// Log information (contains the full LLM output)
19    pub log: String,
20}
21
22/// Tool input type
23///
24/// Uses internally tagged serialization to avoid `untagged` ambiguity:
25/// - String inputs are tagged with `"type": "string"`
26/// - Object inputs are tagged with `"type": "object"`
27///
28/// A `TryFrom<serde_json::Value>` implementation validates incoming untagged
29/// JSON and dispatches to the correct variant.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(tag = "type", rename_all = "snake_case")]
32pub enum ToolInput {
33    /// String input
34    String { value: String },
35
36    /// JSON object input
37    Object { value: serde_json::Value },
38}
39
40impl Default for ToolInput {
41    fn default() -> Self {
42        ToolInput::String {
43            value: String::new(),
44        }
45    }
46}
47
48impl std::fmt::Display for ToolInput {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            ToolInput::String { value } => write!(f, "{}", value),
52            ToolInput::Object { value } => write!(
53                f,
54                "{}",
55                serde_json::to_string(value).unwrap_or_else(|_| "unknown".to_string())
56            ),
57        }
58    }
59}
60
61/// Validates untagged JSON into a `ToolInput`.
62///
63/// This handles the case where external systems send JSON without the
64/// `type` tag. A JSON string becomes `ToolInput::String`, a JSON object
65/// becomes `ToolInput::Object`, and anything else is an error.
66impl TryFrom<serde_json::Value> for ToolInput {
67    type Error = String;
68
69    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
70        match value {
71            serde_json::Value::String(s) => Ok(ToolInput::String { value: s }),
72            serde_json::Value::Object(_) => Ok(ToolInput::Object { value }),
73            other => Err(format!(
74                "ToolInput must be a string or object, got: {}",
75                other
76            )),
77        }
78    }
79}
80
81/// Agent finish state
82///
83/// Represents that the Agent has reached a final answer.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct AgentFinish {
86    /// Return values (key-value pairs)
87    pub return_values: HashMap<String, serde_json::Value>,
88
89    /// Log information (contains the full LLM output)
90    pub log: String,
91}
92
93impl AgentFinish {
94    /// Create a new AgentFinish
95    pub fn new(output: String, log: String) -> Self {
96        let mut return_values = HashMap::new();
97        return_values.insert("output".to_string(), serde_json::Value::String(output));
98        Self { return_values, log }
99    }
100
101    /// Get the output value
102    pub fn output(&self) -> Option<&str> {
103        self.return_values.get("output").and_then(|v| v.as_str())
104    }
105}
106
107/// Agent execution step
108///
109/// Represents an executed action and its observation result.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct AgentStep {
112    /// The executed action
113    pub action: AgentAction,
114
115    /// Observation result (tool output)
116    pub observation: String,
117}
118
119impl AgentStep {
120    /// Create a new AgentStep
121    pub fn new(action: AgentAction, observation: String) -> Self {
122        Self {
123            action,
124            observation,
125        }
126    }
127}
128
129/// Agent output
130///
131/// The plan method of the Agent may return an action or a final answer.
132#[derive(Debug, Clone)]
133pub enum AgentOutput {
134    /// Execute a single action
135    Action(AgentAction),
136
137    /// Execute multiple actions in parallel
138    Actions(Vec<AgentAction>),
139
140    /// Finish (return final answer)
141    Finish(AgentFinish),
142}
143
144impl AgentOutput {
145    /// Whether this is a final answer
146    pub fn is_finish(&self) -> bool {
147        matches!(self, AgentOutput::Finish(_))
148    }
149
150    /// Whether this is an action (single or multiple)
151    pub fn is_action(&self) -> bool {
152        matches!(self, AgentOutput::Action(_) | AgentOutput::Actions(_))
153    }
154
155    /// Get a single action (if any)
156    pub fn action(&self) -> Option<&AgentAction> {
157        match self {
158            AgentOutput::Action(action) => Some(action),
159            _ => None,
160        }
161    }
162
163    /// Get all actions (single or multiple)
164    pub fn actions(&self) -> Vec<&AgentAction> {
165        match self {
166            AgentOutput::Action(action) => vec![action],
167            AgentOutput::Actions(actions) => actions.iter().collect(),
168            _ => vec![],
169        }
170    }
171
172    /// Get the finish state (if any)
173    pub fn finish(&self) -> Option<&AgentFinish> {
174        match self {
175            AgentOutput::Finish(finish) => Some(finish),
176            _ => None,
177        }
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    fn create_action(tool: &str, input: &str) -> AgentAction {
186        AgentAction {
187            tool: tool.to_string(),
188            tool_input: ToolInput::String {
189                value: input.to_string(),
190            },
191            log: "test".to_string(),
192        }
193    }
194
195    #[test]
196    fn test_agent_output_single_action() {
197        let action = create_action("calculator", "1+2");
198        let output = AgentOutput::Action(action);
199
200        assert!(output.is_action());
201        assert!(!output.is_finish());
202        assert_eq!(output.actions().len(), 1);
203    }
204
205    #[test]
206    fn test_agent_output_multiple_actions() {
207        let actions = vec![
208            create_action("calculator", "1+2"),
209            create_action("datetime", "now"),
210        ];
211        let output = AgentOutput::Actions(actions);
212
213        assert!(output.is_action());
214        assert!(!output.is_finish());
215        assert_eq!(output.actions().len(), 2);
216        assert!(output.action().is_none());
217    }
218
219    #[test]
220    fn test_agent_output_finish() {
221        let finish = AgentFinish::new("answer".to_string(), "log".to_string());
222        let output = AgentOutput::Finish(finish);
223
224        assert!(!output.is_action());
225        assert!(output.is_finish());
226        assert_eq!(output.actions().len(), 0);
227        assert!(output.finish().is_some());
228    }
229
230    #[test]
231    fn test_agent_finish_output() {
232        let finish = AgentFinish::new("the answer is 42".to_string(), String::new());
233        assert_eq!(finish.output(), Some("the answer is 42"));
234    }
235}