operonx.core¶
Engine, op decorators, graph composition, state markers, and middleware. This page is the primary public surface — everything you need to build and run a workflow without touching providers or telemetry.
Engine¶
Operon
¶
Operon(
graph: Union[GraphOp, Callable[..., GraphOp]],
*,
params: Optional[Dict[str, Any]] = None,
trace: Optional[Union[str, Consumer, List[Union[str, Consumer]]]] = None,
)
Workflow execution engine.
Operon takes a GraphOp and provides execution capabilities: - Builds and validates the graph structure - Creates state schema for data flow - Executes workflows with fresh state per run - Integrates with tracers for observability
Attributes:
| Name | Type | Description |
|---|---|---|
graph |
The GraphOp to execute |
|
name |
Workflow name (from graph) |
|
schema |
StateSchema
|
State schema for the workflow |
Example
# Define graph
with GraphOp(name="chatbot") as graph:
llm = LLMOp(name="llm", resource="gpt-4o", inputs={"prompt": ...})
START >> llm >> END
# Create engine (builds automatically)
engine = Operon(graph)
# Run multiple times with fresh state
result = await engine.run(inputs={"query": "Hello!"})
print(result["response"]) # workflow output
print(result["$state"]) # MemoryState for debugging
# Or use callable syntax
result = await engine({"query": "Goodbye!"})
Initialize Operon engine with a GraphOp or a graph factory.
Pure orchestrator — does not load .env or resources.yaml.
Call :func:operonx.bootstrap (or :meth:ResourceHub.from_yaml directly)
before constructing the engine if your graph uses provider ops.
Pure-compute graphs need no setup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
Union[GraphOp, Callable[..., GraphOp]]
|
A GraphOp workflow, or a callable that returns one.
When a callable is passed, it is invoked with |
required |
params
|
Optional[Dict[str, Any]]
|
Keyword arguments passed to the graph factory. Ignored
when graph is already a GraphOp. Defaults to |
None
|
trace
|
Optional[Union[str, Consumer, List[Union[str, Consumer]]]]
|
V3 tracing. Accepts a ResourceHub key (str),
a :class: |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If a provider op needs the hub but none has been
installed. The message points at |
TypeError
|
If a |
Source code in operonx/core/engine.py
Attributes¶
Methods:¶
start
¶
start(
inputs: Dict[str, Any],
*,
user_id: Optional[str] = None,
session_id: Optional[str] = None,
request_id: Optional[str] = None,
trace_id: Optional[str] = None,
scratch: Optional[Dict[str, Any]] = None,
checkpointer=None,
) -> ExecutionHandle
Start workflow execution and return a streaming handle immediately.
Does not block — the graph runs in the background. Use the handle to stream frames, await specific outputs, or collect the final result.
V3 trace consumers (declared via Operon(..., trace=...)) fire
automatically once the scheduler completes — no explicit finalize
needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Dict[str, Any]
|
Input data for the workflow |
required |
user_id
|
Optional[str]
|
Optional user identifier (auto-generated if not provided) |
None
|
session_id
|
Optional[str]
|
Optional session identifier (auto-generated if not provided) |
None
|
request_id
|
Optional[str]
|
Optional request identifier (auto-generated if not provided) |
None
|
scratch
|
Optional[Dict[str, Any]]
|
Optional initial values for per-call scratch space. Applied
synchronously before the scheduler task is created — race-free.
Equivalent to writing |
None
|
Returns:
| Type | Description |
|---|---|
ExecutionHandle
|
ExecutionHandle — async-iterable, supports |
ExecutionHandle
|
and |
Source code in operonx/core/engine.py
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 | |
run
async
¶
run(
inputs: Dict[str, Any],
*,
user_id: Optional[str] = None,
session_id: Optional[str] = None,
request_id: Optional[str] = None,
trace_id: Optional[str] = None,
scratch: Optional[Dict[str, Any]] = None,
checkpointer=None,
) -> Dict[str, Any]
Execute the workflow with given inputs.
Each call creates a fresh state, so the same engine can be used for multiple independent executions. Equivalent to::
handle = engine.start(inputs, ...)
result = await handle.collect(unwrap=True)
V3 trace consumers (declared via Operon(..., trace=...))
fire automatically inside start() when the scheduler
completes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Dict[str, Any]
|
Input data for the workflow |
required |
user_id
|
Optional[str]
|
Optional user identifier (auto-generated if not provided) |
None
|
session_id
|
Optional[str]
|
Optional session identifier (auto-generated if not provided) |
None
|
request_id
|
Optional[str]
|
Optional request identifier (auto-generated if not provided) |
None
|
scratch
|
Optional[Dict[str, Any]]
|
Optional initial values for per-call scratch space. |
None
|
Returns:
| Type | Description |
|---|---|
Dict[str, Any]
|
Dictionary containing workflow outputs plus "$state" key |
Dict[str, Any]
|
with the MemoryState for debugging/tracing access. |
Source code in operonx/core/engine.py
invoke
async
¶
stream
async
¶
stream(
inputs: Dict[str, Any],
*,
mode: str = "updates",
channels: Optional[List[str]] = None,
checkpointer: Optional[Any] = None,
**kwargs: Any,
) -> asyncio.AsyncGenerator[Any, None]
LangGraph-familiar streaming iterator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
Dict[str, Any]
|
workflow inputs (same as |
required |
mode
|
str
|
one of
- |
'updates'
|
channels
|
Optional[List[str]]
|
for |
None
|
checkpointer
|
Optional[Any]
|
for |
None
|
**kwargs
|
Any
|
forwarded to |
{}
|
Yields:
| Type | Description |
|---|---|
AsyncGenerator[Any, None]
|
mode-specific chunks (see above). |
Source code in operonx/core/engine.py
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 | |
serve
¶
serve(
*,
path: str = "/",
host: str = "0.0.0.0",
port: int = 8000,
stream: Optional[bool] = None,
websocket: bool = False,
backend: str = "python",
**kwargs: Any,
) -> None
Serve this workflow as an HTTP API.
Convenience wrapper around operonx.serve.OperonApp. Requires operonx-serve
to be installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
URL path for the endpoint (default: "/"). |
'/'
|
host
|
str
|
Bind address. |
'0.0.0.0'
|
port
|
int
|
Bind port. |
8000
|
stream
|
Optional[bool]
|
Enable SSE streaming endpoint. None = auto-detect. |
None
|
websocket
|
bool
|
Enable WebSocket endpoint. |
False
|
backend
|
str
|
"python" (FastAPI/uvicorn) or "rust" (Axum). |
'python'
|
**kwargs
|
Any
|
Extra arguments forwarded to |
{}
|
Source code in operonx/core/engine.py
batch
async
¶
batch(
inputs_list: List[Dict[str, Any]], *, concurrency: int = 10, **kwargs: Any
) -> List[Dict[str, Any]]
Run the workflow concurrently on multiple inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs_list
|
List[Dict[str, Any]]
|
List of input dicts to process. |
required |
concurrency
|
int
|
Max concurrent executions (default: 10). |
10
|
**kwargs
|
Any
|
Extra arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
List[Dict[str, Any]]
|
List of result dicts in the same order as inputs. |
Source code in operonx/core/engine.py
cli
¶
Interactive CLI mode — read JSON from stdin, print result to stdout.
Source code in operonx/core/engine.py
input_schema
¶
output_schema
¶
Decorators¶
The two decorators that turn ordinary Python into Operonx ops:
op
¶
op(
func: Optional[Callable] = None,
*,
bound: Optional[str] = None,
cache: Optional[Any] = None,
delay: float = 0,
exclude: Optional[Any] = None,
include: Optional[Any] = None,
observe_max: Optional[int] = None,
) -> Any
Decorator that turns a plain function into a FuncOp factory.
Can be used bare or with keyword arguments::
@op
def double(x: int):
return {"result": x * 2}
@op(bound="cpu")
def heavy_compute(data: list):
return {"result": process(data)}
@op(bound="io")
async def call_api(url: str):
return {"data": await fetch(url)}
# Phase 2 observability filter (Checkpointer + Tracer both respect these):
@op(exclude=["tokens"]) # skip these vars in both observers
@op(include=[]) # silence entire op (allowlist of nothing)
@op(exclude={"trace": ["pii"]}) # per-observer split (dict form)
@op(observe_max=10_000) # circuit breaker: raise if op emits > N events
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bound
|
Optional[str]
|
Execution bound hint for the scheduler.
|
None
|
exclude
|
Optional[Any]
|
Vars to hide from observers. |
None
|
include
|
Optional[Any]
|
Vars to expose to observers (allowlist). Same shapes as |
None
|
observe_max
|
Optional[int]
|
Per-op circuit breaker. If the op emits more than this many
events in a single run, :class: |
None
|
Source code in operonx/core/ops/transform/func_op.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
graph
¶
GraphOp — container op that manages a graph of child ops.
Classes¶
GraphOp
¶
Bases: BaseOp
Container op that holds and executes a directed graph of child ops.
Lifecycle::
1. DEFINE with GraphOp(name="wf") as g:
a = double(x=PARENT["x"])
b = add(a=a["result"], b=PARENT["y"])
START >> a >> b >> END
Ops auto-register via context manager. Edges via >> operator.
Inputs/outputs auto-discovered from PARENT refs.
2. BUILD g.build() (or auto on first run)
_setup_schema scan PARENT refs → graph inputs/outputs
_setup_endpoints find entry/exit ops from topology
_build() adj list + ready counts + stream ready counts
validate branch targets, cycles, reachability, refs
3. EXECUTE g.run(state, context_id) — async generator
→ run_task_scheduler() drives ops via Frame/EOF events
→ yields (ctx, outputs) per batch or per stream frame
→ loop iteration handled inside scheduler EOF handler
4. EXPORT serialize() config dict for Rust backend
validate() graph structure validation
show() debug display
Source code in operonx/core/ops/graph/graph_op.py
Methods:¶
get_current_graph
staticmethod
¶
add_op
¶
Add an op to the graph.
Source code in operonx/core/ops/graph/graph_op.py
add_edge
¶
add_edge(
source: str,
target: str,
type: EdgeType = "normal",
soft: bool = False,
hard: bool = False,
)
Add an edge between two ops.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
Source op name. |
required |
target
|
str
|
Target op name. |
required |
type
|
EdgeType
|
Edge type (normal, lookback, condition). |
'normal'
|
soft
|
bool
|
If True, edge does not count toward ready_count. Used for branch outputs when only one branch executes. |
False
|
hard
|
bool
|
If True, opt this edge out of auto-softening at build time. Use for the rare case where a branch-descended pred must still be waited on as a hard dependency. |
False
|
Source code in operonx/core/ops/graph/graph_op.py
build
¶
Build graph: cycle-rewrite → children → schema → endpoints → validation → topology.
The cycle-rewrite pass (Phase 3 Level-2) runs FIRST so that any
synthetic hidden GraphOp.loop children it creates are then built
alongside the user's own children. Ordering matters — validate() and
auto_soft assume a DAG and would produce wrong results on cyclic input.
Source code in operonx/core/ops/graph/graph_op.py
run
async
¶
run(
state: MemoryState, context_id: Optional[tuple] = None
) -> AsyncGenerator[Tuple[tuple, Dict[str, Any]], None]
Execute graph: get inputs → schedule ops → loop if needed → store results.
Source code in operonx/core/ops/graph/graph_op.py
serialize
¶
Serialize full graph to config dict for the Rust backend.
Note: the key "initial_ready_count" is kept as-is for Rust backend
compatibility even though the internal Python attribute was renamed to
_initial_ready during the scheduler rewrite.
Source code in operonx/core/ops/graph/graph_op.py
validate
¶
Run all validations and return result.
Source code in operonx/core/ops/graph/graph_op.py
show
¶
Display graph structure (debug).
Source code in operonx/core/ops/graph/graph_op.py
GraphValidationError
¶
Bases: Exception
Exception raised when graph validation fails.
Source code in operonx/core/ops/graph/validation.py
ValidationIssue
dataclass
¶
ValidationIssue(
level: ValidationLevel,
category: str,
message: str,
op_name: Optional[str] = None,
target_name: Optional[str] = None,
available_nodes: List[str] = list(),
suggestions: List[str] = list(),
)
A single validation issue found in the graph.
ValidationLevel
¶
Bases: Enum
Severity level for validation issues.
ValidationResult
dataclass
¶
Result of graph validation.
Methods:¶
raise_if_errors
¶
Raise exception if there are any errors.
Source code in operonx/core/ops/graph/validation.py
Op types¶
The base classes that compose into a workflow. Most users only touch
GraphOp directly (via with GraphOp(...) as g:) — the others are
constructed by decorators or factory helpers.
GraphOp
¶
Bases: BaseOp
Container op that holds and executes a directed graph of child ops.
Lifecycle::
1. DEFINE with GraphOp(name="wf") as g:
a = double(x=PARENT["x"])
b = add(a=a["result"], b=PARENT["y"])
START >> a >> b >> END
Ops auto-register via context manager. Edges via >> operator.
Inputs/outputs auto-discovered from PARENT refs.
2. BUILD g.build() (or auto on first run)
_setup_schema scan PARENT refs → graph inputs/outputs
_setup_endpoints find entry/exit ops from topology
_build() adj list + ready counts + stream ready counts
validate branch targets, cycles, reachability, refs
3. EXECUTE g.run(state, context_id) — async generator
→ run_task_scheduler() drives ops via Frame/EOF events
→ yields (ctx, outputs) per batch or per stream frame
→ loop iteration handled inside scheduler EOF handler
4. EXPORT serialize() config dict for Rust backend
validate() graph structure validation
show() debug display
Source code in operonx/core/ops/graph/graph_op.py
Methods:¶
get_current_graph
staticmethod
¶
add_op
¶
Add an op to the graph.
Source code in operonx/core/ops/graph/graph_op.py
add_edge
¶
add_edge(
source: str,
target: str,
type: EdgeType = "normal",
soft: bool = False,
hard: bool = False,
)
Add an edge between two ops.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
Source op name. |
required |
target
|
str
|
Target op name. |
required |
type
|
EdgeType
|
Edge type (normal, lookback, condition). |
'normal'
|
soft
|
bool
|
If True, edge does not count toward ready_count. Used for branch outputs when only one branch executes. |
False
|
hard
|
bool
|
If True, opt this edge out of auto-softening at build time. Use for the rare case where a branch-descended pred must still be waited on as a hard dependency. |
False
|
Source code in operonx/core/ops/graph/graph_op.py
build
¶
Build graph: cycle-rewrite → children → schema → endpoints → validation → topology.
The cycle-rewrite pass (Phase 3 Level-2) runs FIRST so that any
synthetic hidden GraphOp.loop children it creates are then built
alongside the user's own children. Ordering matters — validate() and
auto_soft assume a DAG and would produce wrong results on cyclic input.
Source code in operonx/core/ops/graph/graph_op.py
run
async
¶
run(
state: MemoryState, context_id: Optional[tuple] = None
) -> AsyncGenerator[Tuple[tuple, Dict[str, Any]], None]
Execute graph: get inputs → schedule ops → loop if needed → store results.
Source code in operonx/core/ops/graph/graph_op.py
serialize
¶
Serialize full graph to config dict for the Rust backend.
Note: the key "initial_ready_count" is kept as-is for Rust backend
compatibility even though the internal Python attribute was renamed to
_initial_ready during the scheduler rewrite.
Source code in operonx/core/ops/graph/graph_op.py
validate
¶
Run all validations and return result.
Source code in operonx/core/ops/graph/graph_op.py
show
¶
Display graph structure (debug).
Source code in operonx/core/ops/graph/graph_op.py
BranchOp
¶
BranchOp(
cases: Optional[List[Tuple[Ref, str]]] = None,
candidates: Optional[List[str]] = None,
default: Optional[str] = None,
inputs: Dict[str, Any] = None,
outputs: Dict[str, Any] = None,
**kwargs,
)
Bases: BaseOp
Op that evaluates conditions and routes execution to different targets.
Conditions are Ref objects with comparison operators. The first matching
condition determines the target. An optional anchor input overrides all
conditions. Use soft edges (>>~) to connect branch targets to a merge op.
Inputs
anchor (str, optional): Hard-coded target name that overrides conditions. (any): Variables referenced in condition Refs (auto-extracted).
Outputs
target (str): Name of the selected target op. matched (str): Description of which condition matched.
Example::
router = if_(PARENT["score"] >= 90, "excellent").else_("fail")
START >> router >> ~excellent >> merge >> END
router >> ~fail >> merge
Source code in operonx/core/ops/flow/branch_op.py
Attributes¶
Methods:¶
get_target
¶
serialize
¶
Serialize branch op with conditions for Rust backend.
Source code in operonx/core/ops/flow/branch_op.py
FuncOp
¶
FuncOp(
code_fn: Optional[Callable] = None,
return_keys: Optional[List[str]] = None,
inputs: Dict[str, Any] = None,
outputs: Dict[str, Any] = None,
_mappings: Dict[str, Any] = None,
**kwargs,
)
Bases: BaseOp
Op that executes a Python function.
Inputs and outputs are auto-extracted from the function's signature and
return-statement AST. Both sync and async functions are supported.
Prefer the @op decorator over instantiating FuncOp directly.
Inputs
Auto-parsed from the function's parameter list.
Outputs
Auto-parsed from return {"key": ...} via AST, or from
explicit return_keys.
Example::
@op
def add(a: int, b: int):
return {"sum": a + b}
with GraphOp(name="main") as graph:
result = add(a=PARENT["x"], b=PARENT["y"])
START >> result >> END
Source code in operonx/core/ops/transform/func_op.py
Attributes¶
Methods:¶
run
async
¶
run(
state: MemoryState, context_id: Optional[str] = None
) -> AsyncGenerator[Tuple[Optional[str], Dict[str, Any]], None]
Execute FuncOp with CodeError wrapping.
Delegates to BaseOp.run() (async generator) and re-raises any
exception as a CodeError with full op context attached.
Source code in operonx/core/ops/transform/func_op.py
Note (1.0.0): the standalone ParserOp was removed. Text parsing lives
inline in LLMOp(fields=..., parser=..., validators=...); pure text
helpers (no LLM) are in operonx.providers.parsing.
Branch helpers¶
Branch
¶
Fluent builder for creating a BranchOp.
Two usage flavors — both supported, both idiomatic:
Inline form (recommended for common if/else) — pass op instances as
targets. The Branch auto-wires branch >> target edges at build time
so you never write them yourself, and the branch can drop right into a
>> chain::
START >> source >> if_(source["kind"] == "audio", asr).else_(skip_stt)
asr >> denoise >> picker
skip_stt >> picker
Auto-name resolves to the LHS if there is one (stt_route = if_(...))
or falls back to a semantic "branch_<target>_or_<default>" name.
Named form (for forward refs or a shared branch node) — pass op
names as strings. No auto-wiring; you wire branch >> target
yourself as before::
router = (if_(PARENT["score"] >= 90, "excellent")
.if_(PARENT["score"] >= 70, "good")
.else_("fail"))
# ...
router >> excellent >> merge
router >> good >> merge
router >> fail >> merge
Mixed is allowed — string targets skip auto-wiring, op-instance targets get auto-wired.
Initialise the builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
Optional[str]
|
Op name. If None, auto-inferred from the variable name. |
None
|
Source code in operonx/core/ops/flow/branch_op.py
Methods:¶
if_
¶
Add a condition–target case.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
condition
|
Ref
|
Ref with comparison (e.g., |
required |
target
|
Union[str, BaseOp]
|
Target op instance (enables auto-wiring) or op name string. |
required |
Returns:
| Type | Description |
|---|---|
Branch
|
self for chaining. |
Source code in operonx/core/ops/flow/branch_op.py
else_
¶
if_
¶
Start a branch declaration with the first condition.
Example (inline, auto-wired)::
START >> source >> if_(cond, asr).else_(skip_stt)
Example (named, string targets, wire manually)::
router = if_(PARENT["score"] >= 90, "excellent").else_("fail")
router >> excellent >> merge
router >> fail >> merge
Source code in operonx/core/ops/flow/branch_op.py
State markers¶
Constants used inside with GraphOp(...) blocks to wire edges and
references. None of these are real instances you'd construct — they're
sentinels the graph builder recognises.
| Marker | Meaning |
|---|---|
START |
Entry node. Every graph's first hard edge goes from START. |
END |
Exit node. op >> END auto-forwards op's outputs as the graph result. |
PARENT |
Reference root for inputs from engine.run(inputs={...}) or the parent graph in nested contexts. Used as PARENT["key"]. |
PENDING |
Sentinel returned by ops that absorb input without producing output. |
Top-level convenience¶
bootstrap
¶
bootstrap(
*, resources: Optional[Union[str, Path]] = None, env: bool = True
) -> Optional[ResourceHub]
One-line setup for .env and :class:ResourceHub.
- When
envisTrue(default), load./.envfrom CWD usingpython-dotenv(non-override; existing env wins). The path is recorded inBOOTSTRAP_ENV_PATHSfor later diagnostic messages. - When
resourcesis a path, install the hub via :meth:ResourceHub.from_yaml. - When
resourcesisNone, call :meth:ResourceHub.auto— which checks./resources.yamland warns on miss. - Idempotent: if a hub is already installed, return it unchanged.
Returns the installed hub, or None if no resources.yaml was
found and none was provided. Pure-compute graphs that don't need a
hub can ignore the return value.
Source code in operonx/__init__.py
Provider-neutral types¶
The v0.7 LLMOp converter layer will translate provider-specific types to/from these at the provider boundary.
ChatMessage
¶
Bases: TypedDict
A single message in a chat conversation.
Provider-neutral shape; backends translate to / from this at the LLMOp boundary.
Required fields
role: One of "system", "user", "assistant",
"tool".
content: The message body. str for plain text;
providers may accept richer structured shapes (tool calls,
multi-modal parts) via opt-in fields below.
Optional fields
name: Speaker identifier (for tool replies and named system
prompts).
tool_call_id: When role == "tool", the id of the tool call
this message responds to.
tool_calls: When role == "assistant", the list of tool
calls the model is requesting. Shape is
provider-specific; converter layers normalise this in v0.7.