Providers¶
convopack keeps its own provider-agnostic Message type internally and offers from_X / to_X adapters for each major provider's message shape. Use the convenience methods on Packer to round-trip in a single call.
OpenAI Chat Completions¶
from convopack.providers import from_openai, to_openai
# raw is list[dict] in OpenAI Chat shape
msgs = from_openai(raw)
out = to_openai(msgs)
Supported shapes:
{"role": "system" | "user" | "assistant", "content": str}{"role": "user", "content": [{"type": "text", ...}, {"type": "image_url", ...}]}{"role": "assistant", "tool_calls": [{"id", "type", "function": {"name", "arguments"}}]}{"role": "tool", "tool_call_id": "...", "content": str}namefield is round-tripped.
Convenience on Packer:
Anthropic Messages¶
from convopack.providers import from_anthropic, to_anthropic
msgs = from_anthropic(raw, system="Be concise.")
payload = to_anthropic(msgs)
# payload.system: str
# payload.messages: list[dict] (no system message inside)
Supported content blocks:
textimagewithbase64orurlsourcestool_usewith id/name/inputtool_resultwithtool_use_id, string or list content,is_errorflag
Convenience on Packer:
packed = Packer(budget=4000, tokenizer="anthropic:claude-sonnet-4-6").pack_anthropic(
history, system="Be concise."
)
Google Gemini¶
Gemini differs in two ways:
- The assistant role is named
"model"instead of"assistant". - Tool calls (
function_call) and tool results (function_response) match by name, not by ID.
convopack generates synthetic IDs on the way in so the rest of the library keeps its uniform tool-pair invariant, and drops them on the way out.
from convopack.providers import from_gemini, to_gemini
msgs = from_gemini(contents, system_instruction="Be concise.")
payload = to_gemini(msgs)
# payload.system_instruction: str
# payload.contents: list[dict]
Convenience on Packer:
packed = Packer(budget=4000, tokenizer="approx").pack_gemini(
contents, system_instruction="Be concise."
)
Cross-provider conversions¶
Because everything goes through the internal Message type, you can read in one shape and write out another:
from convopack.providers import from_openai, to_anthropic
anthropic_payload = to_anthropic(from_openai(openai_history))
This is useful for migrating an agent loop from one provider to another, or for libraries that want to test parity across providers without rewriting their fixtures.
Tool-pair atomicity, across providers¶
Every provider models tool exchanges slightly differently — OpenAI uses tool_calls/tool messages with shared id, Anthropic uses content blocks with tool_use_id, Gemini uses parts with no ID. convopack reconciles these into one internal model: a ToolUseBlock always pairs with a matching ToolResultBlock. Strategies see only that pairing, so the invariant — "never split a pair" — holds regardless of which provider you came from.