Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/structured-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@smooai/smooth-operator-core-monorepo': minor
---

Add structured output (schema-constrained JSON responses) to the LLM client (SMOODEV-1472).

New public API:

- `ResponseFormat` enum (`JsonSchema { name, schema, strict }`) with a `ResponseFormat::json_schema(name, schema)` constructor (defaults `strict = true`).
- `LlmClient::chat_structured(messages, &ResponseFormat)` and the lower-level `LlmClient::chat_with_format(messages, tools, Option<&ResponseFormat>)`. `chat` now delegates to `chat_with_format(.., None)`.
- `LlmResponse::structured_json() -> serde_json::Value` and `LlmResponse::deserialize_json::<T>()` — both surface a clear error (never a silent empty value) when the model returned empty or non-JSON content.
- `LlmProvider::chat_structured` trait method; `MockLlmClient` records the requested `ResponseFormat` on `RecordedCall.response_format` for assertions.

Provider handling:

- **OpenAI-compatible** (LiteLLM gateway, etc.): serialized on `/chat/completions` as `response_format: { type: "json_schema", json_schema: { name, schema, strict } }`.
- **Anthropic-native** (`/v1/messages`): achieved via a forced single tool call — a synthetic tool whose `input_schema` is the requested schema, forced with `tool_choice: { type: "tool", name }`; the tool's `input` is surfaced back as the JSON content string.

Streaming structured output and agent-level (`Agent::run`) wiring are deliberately deferred as follow-ups.
2 changes: 1 addition & 1 deletion rust/smooth-operator-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub use conversation::{CompactionResult, CompactionStrategy, Conversation, Messa
pub use cost::{BudgetExceeded, CostBudget, CostEntry, CostTracker, ModelPricing};
pub use human::{human_channel, ConfirmationHook, HumanChannelPair, HumanRequest, HumanResponse};
pub use knowledge::{Document, DocumentType, InMemoryKnowledge, KnowledgeBase, KnowledgeResult};
pub use llm::{accumulate_stream_events, LlmClient, LlmConfig, LlmResponse, StreamEvent};
pub use llm::{accumulate_stream_events, LlmClient, LlmConfig, LlmResponse, ResponseFormat, StreamEvent};
pub use memory::{InMemoryMemory, Memory, MemoryEntry, MemoryType};
pub use providers::{Activity, ModelRouting, ModelSlot, ProviderConfig, ProviderRegistry};
pub use tool::{Tool, ToolCall, ToolRegistry, ToolResult, ToolSchema};
Expand Down
Loading
Loading