Breaking Changes (2026-02-11)¶
This document summarizes the breaking changes introduced in this branch and shows how to migrate with minimal effort.
Summary¶
Error handling is now fully aligned with standard Python style:
- Success paths return plain values.
- Failure paths raise
ErrorPayload. StructuredOutput(value, error)is no longer used as a general return wrapper.
If your code still depends on .value / .error, migrate using the patterns below.
Scope¶
1. StructuredOutput Removed¶
StructuredOutput has been removed from core types and public exports.
src/republic/core/results.pysrc/republic/core/__init__.pysrc/republic/__init__.py
2. Non-Streaming APIs Now Use "Return Value + Exception"¶
The following APIs no longer return StructuredOutput:
LLM.chat(...) -> strLLM.chat_async(...) -> strLLM.tool_calls(...) -> list[dict[str, Any]]LLM.tool_calls_async(...) -> list[dict[str, Any]]LLM.if_(...) -> boolLLM.if_async(...) -> boolLLM.classify(...) -> strLLM.classify_async(...) -> strLLM.embed(...) -> AnyLLM.embed_async(...) -> Any
Tape session shortcuts changed in the same way:
Tape.chat(...) -> strTape.chat_async(...) -> strTape.tool_calls(...) -> list[dict[str, Any]]Tape.tool_calls_async(...) -> list[dict[str, Any]]
3. ToolExecutor.execute Error Semantics Changed¶
ToolExecutor.execute(...) now raises ErrorPayload for invalid input, validation failures, missing context, unknown tools, and similar failures. It no longer returns a result object with an error field for these cases.
4. Tape Return-Type Simplification¶
This branch also finalizes:
ContextSelectionremoved.read_messages(...)now returnslist[dict[str, Any]].QueryResultremoved.TapeQuery.all()now returnslist[TapeEntry], and errors are raised asErrorPayload.read_entries()is deprecated. Usetape.query.all()for full entry reads.
Migration Examples¶
Chat¶
Before:
out = llm.chat("Ping")
if out.error:
handle_error(out.error)
else:
print(out.value)
After:
from republic.core.results import ErrorPayload
try:
text = llm.chat("Ping")
print(text)
except ErrorPayload as exc:
handle_error(exc)
Text Decision / Classify¶
Before:
decision = llm.if_("service down", "should page?")
if decision.error is None and decision.value:
page_oncall()
After:
from republic.core.results import ErrorPayload
try:
decision = llm.if_("service down", "should page?")
if decision:
page_oncall()
except ErrorPayload as exc:
handle_error(exc)
Embedding¶
Before:
out = llm.embed("incident summary")
if out.error:
handle_error(out.error)
else:
vectors = out.value
After:
from republic.core.results import ErrorPayload
try:
vectors = llm.embed("incident summary")
except ErrorPayload as exc:
handle_error(exc)
ToolExecutor¶
Before:
result = executor.execute(calls, tools=tools)
if result.error:
handle_error(result.error)
else:
print(result.tool_results)
After:
from republic.core.results import ErrorPayload
try:
result = executor.execute(calls, tools=tools)
print(result.tool_results)
except ErrorPayload as exc:
handle_error(exc)
Fast Detection¶
Use this command to locate old calling patterns:
rg -n "StructuredOutput|\\.value\\b|\\.error\\b" src tests
Then migrate matches to "return value + try/except ErrorPayload".
Release Guidance¶
- This is a clear API breaking change. Use a major version bump, or at minimum a minor bump with explicit release notes.
- If you maintain downstream SDK consumers, provide a migration note or codemod focused on replacing
.value/.errorbranches.