Structured agent outputs: Agent.run_structured(prompt, schema=...) returns parsed JSON for JSON schema dictionaries, or a validated instance when a Pydantic model class is passed. Pydantic remains optional and is not a core Orchflow dependency.
Typed provider configuration: new AgentConfig dataclass carries model, temperature, max_tokens, api_base, api_key, timeout, and an extra dict for provider-specific kwargs. Pass it via Agent(config=AgentConfig(...)); existing direct fields on Agent still work and take priority if both are set.
New error type: StructuredOutputError is raised for invalid JSON, schema/validation failures, unsupported schema types, or empty model output from run_structured(...).
New example: examples/structured_agent.py demonstrates a JSON-schema extraction flow using AgentConfig and run_structured(...) inside a Flow step.
Docs and roadmap refresh: quickstart, API reference, concepts, and AGENTS.md now document AgentConfig and structured outputs; roadmap adds a 0.6.0 target to evaluate one-turn tool execution.
Clarified tool-calling error: calling an Agent configured with tools now raises NotImplementedError mentioning "outside Orchflow v0.5" (previously referenced v0.1), pointing users to call tools inside normal steps.
Merge behavior: direct Agent fields (model, temperature, max_tokens) take precedence over config when both are set. config.extra is passed through directly as additional keyword arguments to the LiteLLM acompletion call (e.g. drop_params).
Agent.run_structured(prompt, schema=...)
New method returns parsed structured output instead of plain text.
parsed = await agent.run_structured(
"Extract name and company from: Ada at OpenAI",
schema={
"type": "object",
"properties": {
"name": {"type": "string"},
"company": {"type": "string"},
},
"required": ["name", "company"],
},
)
Behavior:
JSON schema dictionaries are converted into a response_format payload ({"type": "json_schema", "json_schema": {"name": ..., "schema": ...}}, using schema["title"] or falling back to "orchflow_schema") and the LiteLLM response content is parsed with json.loads.
Pydantic model classes (any class exposing model_validate_json) are passed directly as response_format and parsed via schema.model_validate_json(content). Pydantic is not a core Orchflow dependency; it only needs to be installed by the caller.
Empty response content, invalid JSON, unsupported schema types, and Pydantic validation failures all raise the new StructuredOutputError.
New StructuredOutputError exception
Exported from orchflow alongside AgentConfig:
from orchflow import Agent, AgentConfig, StructuredOutputError
Raised for: invalid JSON, Pydantic validation failures, unsupported schema types (not a dict or Pydantic model class), and empty structured content from the model.
New example: examples/structured_agent.py
Demonstrates AgentConfig plus run_structured(...) inside a Flow step, extracting a {"name", "company"} object from free text.
uv run python examples/structured_agent.py
Documentation and roadmap updates
docs/api-reference.md, docs/quickstart.md, docs/concepts.md, docs/examples.md, and README.md all add AgentConfig / run_structured(...) usage and snippets.
docs/roadmap.md marks AgentConfig, run_structured(...), and JSON/Pydantic schema support as shipped in 0.5.0, and adds a new 0.6.0 - Tool Execution milestone (small tool schema helper, one model-requested tool call followed by a final response; long-running tool loops and MCP remain out of scope).
README.md roadmap section now reads 0.5.x: structured agent polish and docs improvements / 0.6.0: evaluate one-turn tool execution.
Fixes
Tool-calling NotImplementedError message updated from "outside Orchflow v0.1" to "outside Orchflow v0.5" and now explicitly points users to normal steps: "Agent tool execution is outside Orchflow v0.5. Call tools inside normal steps or create an Agent without tools."
Agent.run(...) internals were refactored into _complete, _resolved_model, and _completion_kwargs helpers; behavior for existing direct-field usage (model, temperature, max_tokens) is unchanged and remains backward compatible, now covered by tests/test_agent.py::test_agent_run_uses_fake_litellm_and_returns_text.
Breaking changes
Agent.model is now optional
Who is affected: Code that relies on Agent.model always being a populated string field (e.g. via introspection, serialization, or type-checking against a required str).
A model must now come from either the direct model field or config.model. If neither is set, calling run() or run_structured() raises ValueError("Agent requires a model or AgentConfig model") at call time rather than failing at construction time.
Migration: No change required for existing Agent(model="...") call sites, they still work identically. If you were relying on static typing that Agent.model is always a non-Nonestr, update type checks to handle str | None, or pass config=AgentConfig(model=...) instead.
Verification:agent = Agent(name="x", role="y") followed by await agent.run("prompt") without a model or config now raises ValueError instead of proceeding; passing model= or config=AgentConfig(model=...) continues to work.