mirror of
https://github.com/JackChen-me/open-multi-agent.git
synced 2026-10-19 01:29:57 -05:00
fix(release-bot): request DeepSeek JSON output mode for every role (#601)
Two roles failed in 2026-09 with STRUCTURED_OUTPUT_VALIDATION_FAILED on bracket mismatches deep inside long nested output, and the in-run correction failed the same way. #599 moved the nested object to the end of each schema so the model has no boundary to hold, which works around the symptom without changing what the provider guarantees. Set `response_format: { type: 'json_object' }` on the shared role config through `extraBody`. DeepSeek then guarantees that the answer parses as JSON, so the syntax failure class goes away at the source. A direct API probe with the pre-#599 planner schema, thinking at max effort, and a tool turn confirmed that DeepSeek accepts the mode in that combination and returned strict JSON on every run. OMA still validates the answer against the role's schema, so the in-run correction keeps handling schema mismatches. The scripted adapter test asserts that every role carries the mode, and the README documents it next to the shared output ceiling.
This commit is contained in:
1 parent
f9d3ca4606
commit
d52380a0a7
3 files changed
+25
-2
No files matched your search
@@ -27,7 +27,11 @@ as the truncation it is. The evidence roles have five turns and the planner and
|
||||
reviewer three. Every DAG task has `maxRetries: 0`, so a failed role is not
|
||||
silently rerun as a whole new analysis (OMA's one in-run structured-output
|
||||
correction still applies). The complete planning DAG has a thirty-minute
|
||||
wall-clock deadline.
|
||||
wall-clock deadline. Every request also sets DeepSeek's JSON output mode
|
||||
(`response_format: json_object`), so the provider guarantees that the answer
|
||||
parses as JSON. OMA still validates the answer against the role's schema, so
|
||||
the in-run correction only has to handle a schema mismatch, not a bracket
|
||||
error in several thousand characters of nested output.
|
||||
|
||||
Repository diffs are untrusted evidence. The analyst and compatibility auditor
|
||||
receive only three custom read-only tools: immutable release evidence, a
|
||||
|
||||
@@ -74,7 +74,7 @@ export async function generateReleaseDecision(
|
||||
const model = options.model ?? DEFAULT_MODEL
|
||||
const shared: Pick<AgentConfig,
|
||||
'model' | 'provider' | 'adapter' | 'apiKey' | 'temperature' | 'thinking' | 'maxTokens' |
|
||||
'parallelToolCalls' | 'maxToolOutputChars' | 'compressToolResults'> = {
|
||||
'parallelToolCalls' | 'extraBody' | 'maxToolOutputChars' | 'compressToolResults'> = {
|
||||
model,
|
||||
provider: options.adapter ? undefined : 'deepseek',
|
||||
adapter: options.adapter,
|
||||
@@ -90,6 +90,15 @@ export async function generateReleaseDecision(
|
||||
// replace it because it is only checked after a call returns.
|
||||
maxTokens: 64_000,
|
||||
parallelToolCalls: false,
|
||||
// DeepSeek's JSON output mode makes the provider guarantee that the
|
||||
// answer parses. Two roles failed in 2026-09 on bracket mismatches deep
|
||||
// inside long nested output, and the in-run correction failed the same
|
||||
// way; #599 moved the nested object last as a workaround. The mode is
|
||||
// accepted together with thinking and tool calls, and the structured
|
||||
// output instruction already carries the word "json" that DeepSeek
|
||||
// requires in the prompt. Schema conformance is still validated by OMA;
|
||||
// this only removes the syntax failure class.
|
||||
extraBody: { response_format: { type: 'json_object' } },
|
||||
maxToolOutputChars: 75_000,
|
||||
compressToolResults: { minChars: 2_000 },
|
||||
}
|
||||
|
||||
@@ -93,6 +93,14 @@ describe('OMA release orchestration', () => {
|
||||
['release-planner', { enabled: true, effort: 'max' }],
|
||||
['release-reviewer', { enabled: true, effort: 'max' }],
|
||||
]))
|
||||
// Every role asks DeepSeek for JSON output mode so the provider, not the
|
||||
// in-run correction, guarantees the answer parses.
|
||||
expect(adapter.extraBodyByRole).toEqual(new Map([
|
||||
['change-analyst', { response_format: { type: 'json_object' } }],
|
||||
['compatibility-auditor', { response_format: { type: 'json_object' } }],
|
||||
['release-planner', { response_format: { type: 'json_object' } }],
|
||||
['release-reviewer', { response_format: { type: 'json_object' } }],
|
||||
]))
|
||||
expect(run.tokenUsage).toEqual({ input_tokens: 40, output_tokens: 20 })
|
||||
})
|
||||
|
||||
@@ -193,6 +201,7 @@ class ReleaseScriptAdapter implements LLMAdapter {
|
||||
readonly toolSets: string[][] = []
|
||||
readonly maxTokensByRole = new Map<string, number | undefined>()
|
||||
readonly thinkingByRole = new Map<string, ThinkingConfig | undefined>()
|
||||
readonly extraBodyByRole = new Map<string, Record<string, unknown> | undefined>()
|
||||
plannerMessages = ''
|
||||
reviewerMessages = ''
|
||||
private sequence = 0
|
||||
@@ -203,6 +212,7 @@ class ReleaseScriptAdapter implements LLMAdapter {
|
||||
this.toolSets.push((options.tools ?? []).map(tool => tool.name))
|
||||
this.maxTokensByRole.set(role, options.maxTokens)
|
||||
this.thinkingByRole.set(role, options.thinking)
|
||||
this.extraBodyByRole.set(role, options.extraBody)
|
||||
const messageText = JSON.stringify(messages)
|
||||
if (role === 'release-planner') this.plannerMessages = messageText
|
||||
if (role === 'release-reviewer') this.reviewerMessages = messageText
|
||||
|
||||
Reference in new issue
Block a user