Skip to content

Commit a5a305b

Browse files
initcronclaude
andcommitted
feat: Add structured I/O foundation (Output/Input Schemas)
Implements structured input/output schema support similar to Agno's Pydantic-based approach, using JSON Schema for Rust. ## Changes ### Core Types (`aof-core/src/schema.rs`) - **OutputSchema**: JSON Schema for validated LLM responses - Schema validation with type checking - LLM tool conversion for structured output - System prompt instructions generation - Format hints (table, list, json, yaml, auto) - **InputSchema**: JSON Schema for input validation - Type-safe input validation - Runtime schema enforcement - **Pre-built schemas** in `schemas::` module: - `container_list()` - Docker/K8s container listings - `resource_stats()` - CPU/memory statistics - `simple_list()` - Generic list outputs - `key_value()` - Key-value data structures ### Integration Points - **AgentContext**: Added `output_schema` and `input_schema` fields - `with_output_schema()` builder method - `with_input_schema()` builder method - **Error handling**: Added `AofError::Validation` variant ### Exports - Exported `OutputSchema`, `InputSchema`, `FormatHint` from `aof-core` ## Design Principles 1. **Runtime flexibility**: Schemas specified per-execution, not per-agent 2. **Type safety**: Rust's compile-time guarantees + runtime validation 3. **LLM-driven**: Uses function calling or response_format for structure 4. **Format hints**: Guides CLI rendering (table vs list vs json) 5. **Graceful degradation**: Validation is strict by default, optional fallback ## Example Usage (Planned) ```rust use aof_core::schema::schemas; // Use pre-built schema let schema = schemas::container_list(); // Execute with schema runtime.execute_with_schema("agent", "list containers", Some(schema)).await?; ``` ```bash # CLI usage (to be implemented) aofctl run agent docker-health.yaml --output-schema container-list aofctl run agent docker-health.yaml --output-schema-file custom.json ``` ## Related - Issue: #74 - Inspired by: Agno's structured I/O - https://docs.agno.com/basics/input-output/agent/usage/output-schema-on-run - https://docs.agno.com/basics/input-output/agent/usage/structured-input ## Next Steps - [ ] Update agent executor to use structured output - [ ] Add CLI support for --output-schema flag - [ ] Implement schema-based rendering in aofctl - [ ] Write internal + user-facing docs - [ ] Comprehensive tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 10dd0be commit a5a305b

4 files changed

Lines changed: 433 additions & 0 deletions

File tree

crates/aof-core/src/agent.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ pub struct AgentContext {
154154

155155
/// Execution metadata
156156
pub metadata: ExecutionMetadata,
157+
158+
/// Optional output schema for structured responses
159+
pub output_schema: Option<crate::schema::OutputSchema>,
160+
161+
/// Optional input schema for validation
162+
pub input_schema: Option<crate::schema::InputSchema>,
157163
}
158164

159165
/// Message in conversation history
@@ -212,9 +218,23 @@ impl AgentContext {
212218
state: HashMap::new(),
213219
tool_results: Vec::new(),
214220
metadata: ExecutionMetadata::default(),
221+
output_schema: None,
222+
input_schema: None,
215223
}
216224
}
217225

226+
/// Set output schema for structured responses
227+
pub fn with_output_schema(mut self, schema: crate::schema::OutputSchema) -> Self {
228+
self.output_schema = Some(schema);
229+
self
230+
}
231+
232+
/// Set input schema for validation
233+
pub fn with_input_schema(mut self, schema: crate::schema::InputSchema) -> Self {
234+
self.input_schema = Some(schema);
235+
self
236+
}
237+
218238
/// Add a message to history
219239
pub fn add_message(&mut self, role: MessageRole, content: impl Into<String>) {
220240
self.messages.push(Message {

crates/aof-core/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ pub enum AofError {
4848
#[error("Runtime error: {0}")]
4949
Runtime(String),
5050

51+
#[error("Validation error: {0}")]
52+
Validation(String),
53+
5154
#[error("Unknown error: {0}")]
5255
Unknown(String),
5356
}
@@ -100,6 +103,11 @@ impl AofError {
100103
pub fn runtime(msg: impl Into<String>) -> Self {
101104
Self::Runtime(msg.into())
102105
}
106+
107+
/// Create a validation error
108+
pub fn validation(msg: impl Into<String>) -> Self {
109+
Self::Validation(msg.into())
110+
}
103111
}
104112

105113
#[cfg(test)]

crates/aof-core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod mcp;
1414
pub mod memory;
1515
pub mod model;
1616
pub mod registry;
17+
pub mod schema;
1718
pub mod tool;
1819
pub mod trigger;
1920
pub mod workflow;
@@ -31,6 +32,7 @@ pub use model::{
3132
Model, ModelConfig, ModelProvider, ModelRequest, ModelResponse, RequestMessage, StopReason,
3233
StreamChunk, ToolDefinition as ModelToolDefinition, Usage,
3334
};
35+
pub use schema::{FormatHint, InputSchema, OutputSchema};
3436
pub use tool::{
3537
Tool, ToolCall, ToolConfig, ToolDefinition, ToolExecutor, ToolInput, ToolResult, ToolType,
3638
};

0 commit comments

Comments
 (0)