ChatModel protocol. Four message dataclasses carry the content, and one protocol defines the six methods every model implements. This page covers both.
The message types
infy has four message types, defined as plain dataclasses ininfy/messages.py. There is no deep class hierarchy: each type is a small dataclass with a type property and a text property.
The union
Message = HumanMessage | AIMessage | SystemMessage | ToolMessage is what every model method accepts as a list.
HumanMessage
HumanMessage holds user input. Its content is either a string or a list of content blocks (TextBlock, ImageBlock, ToolCallBlock, ToolResultBlock). The name and id fields are optional.
text property always returns a plain string. If content is a list of blocks, it concatenates the text of each block, so you can read .text regardless of how the message was built.
AIMessage
AIMessage is what a model returns. It carries the generated content, plus tool calls, usage metadata, and response metadata.
has_tool_calls is a convenience property: it returns True when tool_calls is non-empty. Use it to decide whether to run tools before continuing the loop.tool_calls is a ToolCall with name, args (a dict), and id. Usage is a UsageMetadata with input_tokens, output_tokens, and total_tokens (the total is computed automatically when not supplied).
SystemMessage
SystemMessage holds a system instruction. It has content and an optional name, plus the same text property.
ToolMessage
ToolMessage reports the result of running a tool back to the model. Unlike the other three, its content is always a string, and it must reference the tool_call_id it answers.
artifact field lets you attach a richer object (for example a dataframe) alongside the string content, and status records whether the tool succeeded.
The ChatModel protocol
ChatModel in infy/models.py is a runtime_checkable Protocol. It is the entire contract for a chat model: one model_name attribute and six methods. Every provider (OpenAI, Anthropic, Gemini/Vertex, Ollama) implements it, so models are interchangeable in chains, agents, and graphs.
generate and agenerate
generate runs one completion and returns an AIMessage. agenerate is the async equivalent, awaitable and identical in signature.
tools, tool_choice, temperature, max_tokens, and any extra provider kwargs.
stream and astream
stream yields AIMessageChunk objects as tokens arrive. astream is the async generator form, iterated with async for.
AIMessageChunk is additive: you can fold a stream into a single chunk with +, then call materialize() to get a complete AIMessage (with parsed tool calls and summed usage).
astream is declared as a plain def that returns an AsyncIterator, not as an async def. You iterate its return value with async for; you do not await the call itself.bind_tools
bind_tools returns a BoundChatModel with tools pre-attached, so you do not pass tools= on every call. Tools are ToolSchema objects (name, description, parameters).
BoundChatModel exposes the same generate, agenerate, stream, and astream methods, forwarding the bound tools on each call. You can call bind_tools again to replace them, or with_structured_output on top.
with_structured_output
with_structured_output returns a StructuredOutputModel that instructs the model to reply as JSON and parses the reply for you. It accepts two kinds of schema:
- A pydantic model class: the reply is validated with
model_validate_json, and the validated instance is returned. This requires pydantic (pip install infy[pydantic]). - A plain dict (JSON schema): the reply is parsed by the Rust
JsonParserand a dict is returned, with no validation.
StructuredOutputModel also supports agenerate, and its stream/astream yield partial dicts as JSON arrives (pydantic validation applies on generate/agenerate, not mid-stream).
Interchangeable by design
Because every provider satisfies the same protocol, swapping models is a one-line change. The messages you build, the tools you bind, and the structured schemas you attach all stay the same.Providers
The OpenAI, Anthropic, Gemini, and Ollama classes and how to configure them.
Runnables and chains
Compose models, parsers, and functions with the
| operator.Agents
Build tool-calling agents with
create_agent.Governance
Add deny-by-default policy, approval, and audit around tool calls.