spec_ai_core/agent/
output.rs

1//! Shared agent output data types used by the core loop and CLI
2
3use crate::agent::model::TokenUsage;
4use crate::tools::ToolResult;
5use crate::types::MessageRole;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// Output from an agent execution step
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AgentOutput {
12    /// The response text
13    pub response: String,
14    /// Message identifier for the persisted assistant response
15    pub response_message_id: Option<i64>,
16    /// Token usage information
17    pub token_usage: Option<TokenUsage>,
18    /// Detailed tool invocations performed during this turn
19    pub tool_invocations: Vec<ToolInvocation>,
20    /// Finish reason
21    pub finish_reason: Option<String>,
22    /// Semantic memory recall statistics for this turn (if embeddings enabled)
23    pub recall_stats: Option<MemoryRecallStats>,
24    /// Unique identifier for correlating this run with logs/telemetry
25    pub run_id: String,
26    /// Optional recommendation produced by graph steering
27    pub next_action: Option<String>,
28    /// Model's internal reasoning/thinking process (extracted from <think> tags)
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub reasoning: Option<String>,
31    /// Human-readable summary of the reasoning (if reasoning was present)
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub reasoning_summary: Option<String>,
34    /// Snapshot of graph state for debugging purposes
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub graph_debug: Option<GraphDebugInfo>,
37}
38
39/// Minimal snapshot of a recent graph node for debugging output
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct GraphDebugNode {
42    pub id: i64,
43    pub node_type: String,
44    pub label: String,
45}
46
47/// Debug information about the graph state captured for run stats
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct GraphDebugInfo {
50    pub enabled: bool,
51    pub graph_memory_enabled: bool,
52    pub auto_graph_enabled: bool,
53    pub graph_steering_enabled: bool,
54    pub node_count: usize,
55    pub edge_count: usize,
56    pub recent_nodes: Vec<GraphDebugNode>,
57}
58
59/// A single tool invocation, including arguments and outcome metadata
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ToolInvocation {
62    pub name: String,
63    pub arguments: Value,
64    pub success: bool,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub output: Option<String>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub error: Option<String>,
69}
70
71impl ToolInvocation {
72    pub fn from_result(name: &str, arguments: Value, result: &ToolResult) -> Self {
73        let output = if result.output.trim().is_empty() {
74            None
75        } else {
76            Some(result.output.clone())
77        };
78
79        Self {
80            name: name.to_string(),
81            arguments,
82            success: result.success,
83            output,
84            error: result.error.clone(),
85        }
86    }
87}
88
89/// Telemetry about memory recall for a single turn
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct MemoryRecallStats {
92    pub strategy: MemoryRecallStrategy,
93    pub matches: Vec<MemoryRecallMatch>,
94}
95
96/// Strategy used for memory recall
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub enum MemoryRecallStrategy {
99    Semantic { requested: usize, returned: usize },
100    RecentContext { limit: usize },
101}
102
103/// Summary of an individual recalled memory
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct MemoryRecallMatch {
106    pub message_id: Option<i64>,
107    pub score: f32,
108    pub role: MessageRole,
109    pub preview: String,
110}