Skip to content

fix(judges): a judge config's outputFormat must not reach the provider - #86

Merged
apucacao merged 1 commit into
mainfrom
alexis/judge-output-format-ignored
Oct 23, 2026
Merged

apucacao merged 1 commit into
mainfrom
alexis/judge-output-format-ignored

Conversation

@apucacao

@apucacao apucacao commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Why

A judge is an ordinary AI Config, so it can carry outputFormat (a JSON Schema). Two code paths read that one field with opposite intent:

  • judge_scoring.py ignores it. It appends its formatting instructions and parses the verdict as { score: number 0-1, reasoning: string }. The verdict contract belongs to the SDK.
  • The provider handler honors it. Every judge goes through execute_and_track, which hands the whole judge config to the handler. openai-messages sets text.format = json_schema from config.outputFormat; claude and langchain handlers read it too.

So the model is told to produce two different shapes, obeys the provider-level schema, and the judge parser then rejects the verdict. The judge produces no score and tracks no metric, silently.

This is live. A read-only sweep of 215 AI Config variations across four LaunchDarkly connections found 30 with mode: judge, and one of those sets outputFormat to { message: string, script: string }. Nothing about a score. That judge can never return a valid verdict, and nothing warns its author.

Fix

Strip outputFormat from a judge config before it can reach a handler or be stored on a JudgeTask. One helper, without_output_format, lives in judge_scoring.py next to the rest of the verdict contract, and is applied at the four places a judge config is handed off:

  • run_judges, before execute_and_track
  • build_judge_tasks, before the config is persisted on the task
  • run_judge, defensively, so an older serialized task still behaves
  • the offline evaluations runner (evaluations/runner.py), when it resolves each judge, so evals.run(...) judges are covered too

When the field was actually present, say so once per judge. The helper returns the same reference when the field is absent and never mutates the input, which may be a cached extract_variation result.

Not in scope, deliberately: the score contract, the parser, the formatting instructions, and handler selection are untouched. Making the SDK honor a judge's schema, or substituting the SDK's own { score, reasoning } schema to get structured output for judges, are both follow-ups.

🤖 Generated with Claude Code


Note

Overview
Fixes judges that silently produced no score when their AI Config defined outputFormat: provider handlers enforced that schema while the SDK still parsed {score, reasoning}, so verdicts could never validate.

Adds without_output_format in judge_scoring.py to drop outputFormat from judge configs (non-mutating, one warning per judge when present) and wires it through inline run_judges, deferred build_judge_tasks / run_judge, and offline evaluations judge resolution so handlers never see a judge-owned schema.

AGENTS.md now states that judge outputFormat is ignored and belongs on the config being evaluated, not the judge. Tests cover handler visibility, task serialization, defensive run_judge, and the evaluations path.

Reviewed by Cursor Bugbot for commit 2eb0534. Bugbot is set up for automated code reviews on this repo. Configure here.

@apucacao

apucacao commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@apucacao
apucacao force-pushed the alexis/judge-output-format-ignored branch from 95c088d to 627f1fa Compare October 2, 2026 15:07
@apucacao

apucacao commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 627f1fa. Configure here.

@apucacao
apucacao force-pushed the alexis/judge-output-format-ignored branch from 627f1fa to 7b252b5 Compare October 21, 2026 22:08
A judge is an ordinary AI Config, so it can carry its own outputFormat (a JSON
Schema). Two readers disagreed about what to do with it: judge_scoring.py owns
the verdict contract and parses the reply as {score, reasoning}, while every
provider handler (openai/claude/langchain) honors config["outputFormat"] and
hard-constrains generation to the author's schema. The model ends up obeying a
shape the judge parser then rejects, so the judge silently produces no score
and tracks no metric.

This is live: a read-only sweep of 215 AI Config variations across four
LaunchDarkly connections (2026-09-11) found 30 with mode: judge, and one of
those, some-judge-config in staging project test-ai-config-project, has
outputFormat set to a schema with no score field at all. That judge can never
return a valid verdict.

Adds without_output_format() to judge_scoring.py, next to the rest of the
verdict contract. It returns a shallow copy of the config with outputFormat
removed (or the input itself when absent, never mutated) and logs a warning
naming the judge key when it actually removed something. Applied on every
path that hands a judge config to a handler: run_judges before
execute_and_track, build_judge_tasks before the config is stored on a
JudgeTask, run_judge defensively for older serialized tasks, and the offline
evaluations runner when it resolves each judge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@apucacao
apucacao force-pushed the alexis/judge-output-format-ignored branch from 7b252b5 to 2eb0534 Compare October 22, 2026 20:54
@apucacao
apucacao marked this pull request as ready for review October 22, 2026 20:58
@apucacao
apucacao requested a review from jeffdupont October 22, 2026 20:58

@jeffdupont jeffdupont left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks right to me, and it covers all four judge paths, including offline evaluations (evaluations/runner.py:373).

I'm holding off on approving only because the spec has no rule for this yet. §8.8.3 says a judge config has no tools but says nothing about outputFormat, and §1.9 says handlers enforce outputFormat whenever it is present. A monorepo PR adding the rule to §3.13 and §8.8.3, with a test sentence, would give js#56 (which misses the offline path) something to test against. I'll approve once that's up.

@apucacao

Copy link
Copy Markdown
Contributor Author

@jeffdupont the spec is up in ai-sdks-monorepo#82: §3.13 has the rule and required tests for the inline and deferred paths, §8.8.3 has the offline rule (one warning per judge per run) and its test, and §1.9 notes judges as the exception to handlers enforcing outputFormat. js#56 now covers the offline path as well.

@apucacao
apucacao requested a review from jeffdupont October 22, 2026 23:35
@apucacao
apucacao merged commit d8b893d into main Oct 23, 2026
7 checks passed
@apucacao
apucacao deleted the alexis/judge-output-format-ignored branch October 23, 2026 16:13
@github-actions github-actions Bot mentioned this pull request Oct 23, 2026
XieX added a commit that referenced this pull request Oct 26, 2026
…e root (#217)

#208 added `create_claude_agents_skills_handler` at
`launchdarkly_ai_claude_agents.experimental.skills`. The spec now
promotes it with Agent Skills (launchdarkly/ai-sdks-monorepo#59,
following #86), and no release has shipped it, so it moves to the
package root with no deprecated alias:

- `create_claude_agents_skills_handler` is exported from
`launchdarkly_ai_claude_agents`, from a new `skills` module (moved from
`experimental/skills.py`). The `experimental` subpackage and its API
report are deleted.
- It reports `claude-agents.createClaudeAgentsSkillsHandler` instead of
`experimental.createClaudeAgentsSkillsHandler`.
- The README section loses its experimental notice, and the example
imports from the package root. agents.md follows.
- Tests: the fresh-interpreter test that the package root never loaded
the experimental module is replaced by one that the root exports the
handler. The usage test, and
`tests/test_never_forwarded_parameters.py`'s module map, use the new
module and ID.

**Phantom usage events (review follow-up).** The handler called the
public `skill_refs` and `write_skills`, so every run with skills also
reported `client.skillRefs` and `client.writeSkills`, calls the customer
never made. This was already on main from #208/#210; this PR makes the
handler a core export, so it fixes it here:

- Core gains a non-reporting `_skill_refs` in
`launchdarkly_ai_server.skills`; `skill_refs` reports and then delegates
to it. Not a public name, so no API report changes.
- The handler uses `_skill_refs` and the existing `_write_skills` from
`launchdarkly_ai_server.skills_fs`, as `skills_watch.py` already does.
- `packages/claude-agents/tests/test_sdk_usage.py` gains a run with
skills, over invoke and stream, that sends only
`claude-agents.createClaudeAgentsSkillsHandler`. The test fails against
the public helpers.

The TypeScript twin is launchdarkly/js-ai-sdk#136, which makes the same
move before it merges and likely has the same usage pattern to fix.

**Tests:** rebased onto `main` after #210 squash-merged, so the branch
carries only its own commits. `make api-report-check` reports every
report current. `make lint`, `make format-check` and `make typecheck`
pass, and the full suite passes locally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **`create_claude_agents_skills_handler`** graduates from
`launchdarkly_ai_claude_agents.experimental.skills` to a top-level
**`skills`** module and is re-exported from
**`launchdarkly_ai_claude_agents`**. The experimental subpackage and its
API report are removed; docs and examples import from the package root,
and SDK usage is reported as
**`claude-agents.createClaudeAgentsSkillsHandler`** instead of the
experimental helper id.
> 
> Runs that materialize variation skills no longer emit phantom
**`client.skillRefs`** / **`client.writeSkills`** events. The handler
calls internal **`_skill_refs`** and **`_write_skills`** (core adds
**`_skill_refs`** as the non-reporting body behind public
**`skill_refs`**). New usage tests assert invoke/stream with skills only
records the factory helper.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
d288473. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
apucacao pushed a commit that referenced this pull request Oct 27, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>launchdarkly-ai-server: 0.5.0</summary>

##
[0.5.0](launchdarkly-ai-server-0.4.0...launchdarkly-ai-server-0.5.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* **skills:** promote Agent Skills to core
([#210](#210))
* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Experimental

* **evaluations:** export each offline row as one trace
([#170](#170))
([fc241df](fc241df))
* **evaluations:** normalize text behaviors
([7cee8c8](7cee8c8))


### Features

* **skills:** export the Claude Agents skills handler from the package
root ([#217](#217))
([5d46dbb](5d46dbb))
* **skills:** promote Agent Skills to core
([#210](#210))
([715815f](715815f))


### Bug Fixes

* **client:** show a judge an output of 0 or false, and render
non-string values as JSON
([7cee8c8](7cee8c8))
* **client:** strip whitespace from LD_BASE_URI, LD_STREAM_URI and
LD_EVENTS_URI in init_client
([1629123](1629123))
* **judges:** a judge config's outputFormat must not reach the provider
([#86](#86))
([d8b893d](d8b893d))
* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
* **skills:** coalesce a watcher's debounce window into one reconcile
([#215](#215))
([b214bbd](b214bbd))
* **skills:** default FDv2SkillStore endpoints from LD_BASE_URI and
LD_STREAM_URI
([1629123](1629123))
</details>

<details><summary>launchdarkly-ai-python: 0.4.0</summary>

##
[0.4.0](launchdarkly-ai-python-0.3.0...launchdarkly-ai-python-0.4.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* **skills:** promote Agent Skills to core
([#210](#210))

### Features

* **skills:** promote Agent Skills to core
([#210](#210))
([715815f](715815f))
</details>

<details><summary>launchdarkly-ai-claude-agents: 0.3.0</summary>

##
[0.3.0](launchdarkly-ai-claude-agents-0.2.6...launchdarkly-ai-claude-agents-0.3.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* **skills:** promote Agent Skills to core
([#210](#210))
* **claude-agents:** switch the built-ins off by default and load no
host settings
([#201](#201))
* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Features

* **skills:** export the Claude Agents skills handler from the package
root ([#217](#217))
([5d46dbb](5d46dbb))
* **skills:** promote Agent Skills to core
([#210](#210))
([715815f](715815f))


### Bug Fixes

* **claude-agents:** switch the built-ins off by default and load no
host settings
([#201](#201))
([f99d538](f99d538))
* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-claude-messages: 0.3.0</summary>

##
[0.3.0](launchdarkly-ai-claude-messages-0.2.5...launchdarkly-ai-claude-messages-0.3.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-openai-agents: 0.3.0</summary>

##
[0.3.0](launchdarkly-ai-openai-agents-0.2.6...launchdarkly-ai-openai-agents-0.3.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-vercel-agents: 0.2.0</summary>

##
[0.2.0](launchdarkly-ai-vercel-agents-0.1.1...launchdarkly-ai-vercel-agents-0.2.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-litellm-agents: 0.2.0</summary>

##
[0.2.0](launchdarkly-ai-litellm-agents-0.1.1...launchdarkly-ai-litellm-agents-0.2.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-openai-messages: 0.3.0</summary>

##
[0.3.0](launchdarkly-ai-openai-messages-0.2.5...launchdarkly-ai-openai-messages-0.3.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-vercel-messages: 0.2.0</summary>

##
[0.2.0](launchdarkly-ai-vercel-messages-0.1.1...launchdarkly-ai-vercel-messages-0.2.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-litellm-messages: 0.2.0</summary>

##
[0.2.0](launchdarkly-ai-litellm-messages-0.1.1...launchdarkly-ai-litellm-messages-0.2.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-langchain-agents: 0.3.0</summary>

##
[0.3.0](launchdarkly-ai-langchain-agents-0.2.6...launchdarkly-ai-langchain-agents-0.3.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-langchain-messages: 0.3.0</summary>

##
[0.3.0](launchdarkly-ai-langchain-messages-0.2.5...launchdarkly-ai-langchain-messages-0.3.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-opencode-agents: 0.3.0</summary>

##
[0.3.0](launchdarkly-ai-opencode-agents-0.2.0...launchdarkly-ai-opencode-agents-0.3.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* **opencode-agents:** honor AI Config tools
([#216](#216))
* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Experimental

* **evaluations:** normalize text behaviors
([7cee8c8](7cee8c8))


### Features

* **opencode-agents:** honor AI Config tools
([#216](#216))
([b5a63ec](b5a63ec))


### Bug Fixes

* **client:** show a judge an output of 0 or false, and render
non-string values as JSON
([7cee8c8](7cee8c8))
* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

<details><summary>launchdarkly-ai-typesafe: 0.2.0</summary>

##
[0.2.0](launchdarkly-ai-typesafe-0.1.1...launchdarkly-ai-typesafe-0.2.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* handlers now forward the AI Config's model parameters to the provider,
limited to each handler's list in TESTING.md 1.12. Configs that set
parameters which used to be ignored now take effect.

### Bug Fixes

* pass AI Config model parameters through to every provider handler
([#107](#107))
([e3afb3f](e3afb3f))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> This is a **Release Please** cut that bumps versions across the
monorepo (notably `launchdarkly-ai-server` **0.5.0**,
`launchdarkly-ai-python` **0.4.0**, and matching minor bumps on provider
adapters). The edits are manifest entries, `pyproject.toml` /
`__version__` strings, and new **CHANGELOG** sections for 2026-10-27.
> 
> The release notes capture several **breaking** runtime changes already
on `main`: **Agent Skills** move from experimental to core
(`launchdarkly_ai_server.skills`), **AI Config model parameters** are
now forwarded to every provider handler (configs that previously ignored
them will behave differently), **Claude Agents** defaults built-in tools
off and stops loading host settings, and **OpenCode Agents** now honor
AI Config tools. The core client also documents judge fixes
(`outputFormat` not sent to providers), URI trimming in `init_client`,
skills watcher/store fixes, and experimental evaluation trace export.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
4138892. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants