Skip to content

chatomon.orbitals.judge

An orbital that asks a language model to judge a message.

Classes:

Name Description
JudgeDecision

The structured answer the judge model must produce.

JudgeOrbital

A small LLM agent that judges messages following your instructions.

JudgeDecision pydantic-model

Bases: BaseModel

The structured answer the judge model must produce.

Fields:

Validators:

action pydantic-field

action: Literal['allow', 'block', 'respond']

The decision.

reply pydantic-field

reply: str | None = None

Text shown to the user, for block or respond.

guidance pydantic-field

guidance: str | None = None

Instructions for the assistant, for allow on input or block.

reason pydantic-field

reason: str

Short label of a few words; never quotes the user.

explanation pydantic-field

explanation: str | None = None

Reasoning behind the decision.

to_verdict

to_verdict() -> Verdict

Convert the decision into a verdict.

The model's reason and explanation are model-written text that may quote the user, so they go into the annotations (treated as content). The verdict's audit reason is the fixed label judge_<action>.

Returns:

Type Description
Verdict

The verdict.

Raises:

Type Description
ValueError

If the fields do not form a valid verdict (for example respond without a reply, or block with both reply and guidance).

Source code in src/chatomon/orbitals/judge.py
 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
def to_verdict(self) -> Verdict:
    """Convert the decision into a verdict.

    The model's `reason` and `explanation` are model-written text that may quote
    the user, so they go into the annotations (treated as content). The
    verdict's audit reason is the fixed label `judge_<action>`.

    Returns:
        The verdict.

    Raises:
        ValueError: If the fields do not form a valid verdict (for example
            `respond` without a reply, or `block` with both reply and guidance).
    """
    reason = f"judge_{self.action}"
    annotations = {"label": self.reason, "explanation": self.explanation}
    if self.action == "allow":
        return Verdict.allow(self.guidance, reason=reason, annotations=annotations)
    if self.action == "block":
        return Verdict.block(
            self.reply, guidance=self.guidance, reason=reason, annotations=annotations
        )
    if not self.reply:
        raise ValueError("the judge chose 'respond' without a reply")
    return Verdict.respond(self.reply, reason=reason, annotations=annotations)

JudgeOrbital

JudgeOrbital(
    *,
    model: str,
    instructions: str,
    context: Knowledge | str | None = None,
    connector: Connector[Any] | None = None,
    history_window: int = 6,
    name: str | None = None,
    reads: Sequence[str] = (),
    timeout: float = 30.0,
    on_error: ErrorPolicy = "block"
)

Bases: Orbital

A small LLM agent that judges messages following your instructions.

It works on both sides. By default it calls the model through the atom's connector, so it uses the same framework as the atom; pass connector to use another one. Its instructions and context are its own: share the atom's knowledge only by passing it explicitly.

Parameters:

Name Type Description Default
model str

Model identifier in the connector's format (e.g. "openai:gpt-5-mini").

required
instructions str

The judge's own definition.

required
context Knowledge | str | None

Reference material for the judge.

None
connector Connector[Any] | None

Connector used to call the model; the atom's when None.

None
history_window int

Number of history messages shown to the judge.

6
name str | None

Unique name inside the atom; defaults to "judge".

None
reads Sequence[str]

Earlier orbitals whose annotations are shown to the judge.

()
timeout float

Seconds before the check is treated as failed.

30.0
on_error ErrorPolicy

"block" (default) or "allow".

'block'

Raises:

Type Description
ChatomonConfigError

If a setting is invalid.

Examples:

from chatomon import Knowledge
from chatomon.orbitals import JudgeOrbital

faq = Knowledge("Opening hours: Monday to Friday, 9 to 18.")
fact_check = JudgeOrbital(
    name="fact_check",
    model="openai:gpt-5-mini",
    instructions="Block replies that state facts missing from the reference.",
    context=faq,
)
assert "Monday to Friday" in fact_check.system_prompt("output")

Methods:

Name Description
bind

Remember the atom's connector, used when no connector was given.

system_prompt

Build the judge's system prompt for one side.

user_prompt

Build the judge's user prompt.

check

Ask the model for a decision.

config

Return the configuration, including model, instructions and context.

Attributes:

Name Type Description
calls_model

Every check is a call to the judge's model.

connector Connector[Any]

The connector used to call the model.

Source code in src/chatomon/orbitals/judge.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def __init__(
    self,
    *,
    model: str,
    instructions: str,
    context: Knowledge | str | None = None,
    connector: Connector[Any] | None = None,
    history_window: int = 6,
    name: str | None = None,
    reads: Sequence[str] = (),
    timeout: float = 30.0,
    on_error: ErrorPolicy = "block",
) -> None:
    super().__init__(name=name, reads=reads, timeout=timeout, on_error=on_error)
    if not model.strip():
        raise ChatomonConfigError("model must not be empty")
    if not instructions.strip():
        raise ChatomonConfigError("instructions must not be empty")
    if history_window < 0:
        raise ChatomonConfigError(f"history_window must be >= 0, got {history_window!r}")
    self.model = model
    self.instructions = instructions
    self.context = Knowledge(context) if isinstance(context, str) else context
    self.history_window = history_window
    self._connector = connector
    self._bound: Connector[Any] | None = None

calls_model class-attribute instance-attribute

calls_model = True

Every check is a call to the judge's model.

connector property

connector: Connector[Any]

The connector used to call the model.

Raises:

Type Description
ChatomonConfigError

If the judge was neither given a connector nor built inside an atom.

bind

bind(connector: Connector[Any]) -> None

Remember the atom's connector, used when no connector was given.

A judge can be shared by several atoms only if they use the same connector (or if the judge has its own connector).

Parameters:

Name Type Description Default
connector Connector[Any]

The atom's connector.

required

Raises:

Type Description
ChatomonConfigError

If the judge is already bound to a different connector.

Source code in src/chatomon/orbitals/judge.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def bind(self, connector: Connector[Any]) -> None:
    """Remember the atom's connector, used when no connector was given.

    A judge can be shared by several atoms only if they use the same connector
    (or if the judge has its own `connector`).

    Args:
        connector: The atom's connector.

    Raises:
        ChatomonConfigError: If the judge is already bound to a different connector.
    """
    if self._connector is None and self._bound is not None and self._bound is not connector:
        raise ChatomonConfigError(
            f"judge {self.name!r} is already bound to another atom's connector; "
            "create one judge per atom or pass connector= explicitly"
        )
    self._bound = connector

system_prompt

system_prompt(side: Side) -> str

Build the judge's system prompt for one side.

Parameters:

Name Type Description Default
side Side

The side being judged.

required

Returns:

Type Description
str

The fixed frame, the instructions and the context.

Source code in src/chatomon/orbitals/judge.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def system_prompt(self, side: Side) -> str:
    """Build the judge's system prompt for one side.

    Args:
        side: The side being judged.

    Returns:
        The fixed frame, the instructions and the context.
    """
    prompt = _FRAME.format(
        side_description=_SIDE_DESCRIPTIONS[side],
        allow_guidance=_ALLOW_GUIDANCE[side],
        instructions=self.instructions.strip(),
    )
    if self.context is not None:
        prompt += "\n\nReference material:\n" + self.context.render()
    return prompt

user_prompt

user_prompt(ctx: OrbitalContext) -> str

Build the judge's user prompt.

Parameters:

Name Type Description Default
ctx OrbitalContext

The orbital context.

required

Returns:

Type Description
str

The history window, the annotations in reads and the text under review,

str

each in its own tag. Inserted text is XML-escaped, so it cannot close a tag.

Source code in src/chatomon/orbitals/judge.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def user_prompt(self, ctx: OrbitalContext) -> str:
    """Build the judge's user prompt.

    Args:
        ctx: The orbital context.

    Returns:
        The history window, the annotations in `reads` and the text under review,
        each in its own tag. Inserted text is XML-escaped, so it cannot close a tag.
    """
    parts: list[str] = []
    history = ctx.history[-self.history_window :] if self.history_window else ()
    if history:
        lines = "\n".join(f"{message.role}: {_escape(message.content)}" for message in history)
        parts.append(f"<history>\n{lines}\n</history>")
    for name in self.reads:
        data = _escape(stable_json(ctx.annotations.get(name)))
        parts.append(f'<annotations from="{name}">\n{data}\n</annotations>')
    parts.append(f"<text_under_review>\n{_escape(ctx.message)}\n</text_under_review>")
    return "\n\n".join(parts)

check async

check(ctx: OrbitalContext) -> Verdict

Ask the model for a decision.

Parameters:

Name Type Description Default
ctx OrbitalContext

The orbital context.

required

Returns:

Type Description
Verdict

The verdict built from the model's decision.

Raises:

Type Description
ChatomonConfigError

If the judge has no connector.

ValueError

If the model's decision is not a valid verdict. The engine treats both as an orbital failure and applies on_error.

Source code in src/chatomon/orbitals/judge.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
async def check(self, ctx: OrbitalContext) -> Verdict:
    """Ask the model for a decision.

    Args:
        ctx: The orbital context.

    Returns:
        The verdict built from the model's decision.

    Raises:
        ChatomonConfigError: If the judge has no connector.
        ValueError: If the model's decision is not a valid verdict. The engine
            treats both as an orbital failure and applies `on_error`.
    """  # noqa: DOC502 - raised by the connector property and to_verdict()
    decision = await self.connector.generate_structured(
        self.model,
        system=self.system_prompt(ctx.side),
        prompt=self.user_prompt(ctx),
        schema=JudgeDecision,
    )
    trace.get_current_span().add_event(
        "gen_ai.evaluation.result",
        attributes={
            "gen_ai.evaluation.name": self.name,
            "gen_ai.evaluation.score.label": decision.action,
            "gen_ai.request.model": self.model,
        },
    )
    return decision.to_verdict()

config

config() -> dict[str, Any]

Return the configuration, including model, instructions and context.

Returns:

Type Description
dict[str, Any]

A JSON-serializable dictionary.

Source code in src/chatomon/orbitals/judge.py
283
284
285
286
287
288
289
290
291
292
293
294
295
def config(self) -> dict[str, Any]:
    """Return the configuration, including model, instructions and context.

    Returns:
        A JSON-serializable dictionary.
    """
    return {
        **super().config(),
        "model": self.model,
        "instructions": self.instructions,
        "context": self.context.render() if self.context is not None else None,
        "history_window": self.history_window,
    }