Orchflow v0.5.0: AgentConfig and Structured Outputs
Orchflow v0.5.0 adds typed provider configuration via AgentConfig and a new Agent.run_structured(prompt, schema=...) method that returns parsed JSON or Pydantic model instances, raising the new StructuredOutputError on invalid or unparseable output.
Features
AgentConfig for typed provider configuration
Agent now accepts an optional config: AgentConfig field for provider settings, instead of only direct constructor fields.
from orchflow import Agent, AgentConfig
agent = Agent(
name="extractor",
role="Extract structured data.",
config=AgentConfig(
model="openai/gpt-5-mini",
temperature=0,
),
)
AgentConfig fields: model (required), temperature, max_tokens, api_base, api_key, timeout, extra: dict[str, Any].
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_formatpayload ({"type": "json_schema", "json_schema": {"name": ..., "schema": ...}}, usingschema["title"]or falling back to"orchflow_schema") and the LiteLLM response content is parsed withjson.loads. - Pydantic model classes (any class exposing
model_validate_json) are passed directly asresponse_formatand parsed viaschema.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, andREADME.mdall addAgentConfig/run_structured(...)usage and snippets.docs/roadmap.mdmarksAgentConfig,run_structured(...), and JSON/Pydantic schema support as shipped in 0.5.0, and adds a new0.6.0 - Tool Executionmilestone (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.mdroadmap section now reads0.5.x: structured agent polish and docs improvements/0.6.0: evaluate one-turn tool execution.
Fixes
- Tool-calling
NotImplementedErrormessage 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_kwargshelpers; behavior for existing direct-field usage (model,temperature,max_tokens) is unchanged and remains backward compatible, now covered bytests/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).
Before:
model: str
After:
model: str | None = None
config: AgentConfig | None = None
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-None str, 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.