Skip to content

Commit d8c5226

Browse files
authored
fix(agent): bound research output size (#160)
## Why The production trace after PR #159 proved that Anthropic native structured output was active, but query passes generated 11-23 exhaustive claims and exhausted the response budget before emitting required fields. Responses were valid JSON prefixes cut off mid-claim or before `summary`. ## What changed - Bound each query pass to 1-6 concise claims and 1-3 exact provider citations per claim. - Prompt the model to produce 4-6 synthesis-ready claims instead of restating the whole evidence pack. - Bound synthesis to 16 consolidated claims and 4 source IDs per claim. - Give the final synthesis an explicit 8,192-token allowance while keeping the report below 2,000 words. - Keep all strict Exa/Firecrawl provenance validation in place. No database, migration, dependency, environment, or deployment-topology changes. ## Verification - `pnpm lint` - `pnpm typecheck` - `pnpm turbo build --force` - `pnpm deadcode` - `pnpm architecture:check` - `pnpm turbo skills:build` After deployment, production QA will repeat natural-language and explicit-tool research flows and validate PDF generation, viewing, download, refresh persistence, and `/` file recall.
1 parent cbe0a91 commit d8c5226

2 files changed

Lines changed: 34 additions & 15 deletions

File tree

packages/agent-core/src/mastra/workflows/deep-research-workflow.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,10 @@ function createSynthesisStep(id: string, config: ResearchWorkflowPrompts) {
168168
execute: async ({ abortSignal, inputData, mastra, requestContext }) => {
169169
const agent = mastra.getAgent("general");
170170
const sources = mergeResearchSources(inputData);
171-
const response = await agent.generate(config.synthesisPrompt(inputData), {
171+
const response = await agent.generate(researchSynthesisPrompt(config, inputData), {
172172
activeTools: [],
173173
abortSignal,
174+
modelSettings: { maxOutputTokens: 8_192 },
174175
providerOptions: RESEARCH_PROVIDER_OPTIONS,
175176
requestContext,
176177
structuredOutput: { schema: ResearchSynthesisDraftSchema },
@@ -289,12 +290,24 @@ function researchPassPrompt(
289290
config.queryPrompt(query),
290291
"Use only the provider evidence below. For Exa citations, copy providerResultId and URL exactly. For Firecrawl citations, copy the URL exactly.",
291292
"Set providerResultId to an empty string for every Firecrawl citation.",
293+
"Return 4-6 distinct, synthesis-ready claims, no more than 3 sources per claim, and a concise summary. Prioritize the strongest guidance instead of exhaustively restating the evidence.",
292294
"Do not cite sourceId directly and do not add sources that are absent from this evidence pack.",
293295
"",
294296
JSON.stringify(evidence, null, 2),
295297
].join("\n");
296298
}
297299

300+
function researchSynthesisPrompt(
301+
config: ResearchWorkflowPrompts,
302+
findings: z.output<typeof ResearchFindingSchema>[],
303+
): string {
304+
return [
305+
config.synthesisPrompt(findings),
306+
"Consolidate overlapping evidence into at most 16 distinct claims with no more than 4 source IDs per claim.",
307+
"Keep the report focused and complete within 2,000 words while retaining actionable findings and citations.",
308+
].join("\n");
309+
}
310+
298311
function parseResearchPassDraft(value: unknown) {
299312
const parsed = ResearchPassDraftSchema.safeParse(value);
300313
if (parsed.success) {

packages/agent-core/src/mastra/workflows/research-provenance.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,29 @@ const SourceReferenceDraftSchema = z.strictObject({
3131
});
3232

3333
export const ResearchPassDraftSchema = z.strictObject({
34-
claims: z.array(
35-
z.strictObject({
36-
claim: z.string().trim().min(1),
37-
sources: z.array(SourceReferenceDraftSchema).min(1),
38-
}),
39-
),
40-
summary: z.string().trim().min(1),
34+
claims: z
35+
.array(
36+
z.strictObject({
37+
claim: z.string().trim().min(1).max(800),
38+
sources: z.array(SourceReferenceDraftSchema).min(1).max(3),
39+
}),
40+
)
41+
.min(1)
42+
.max(6),
43+
summary: z.string().trim().min(1).max(2_000),
4144
});
4245

4346
export const ResearchSynthesisDraftSchema = z.strictObject({
44-
claims: z.array(
45-
z.strictObject({
46-
claim: z.string().trim().min(1),
47-
sourceIds: z.array(z.string().trim().min(1).max(4_096)).min(1),
48-
}),
49-
),
50-
report: z.string().trim().min(1),
47+
claims: z
48+
.array(
49+
z.strictObject({
50+
claim: z.string().trim().min(1).max(800),
51+
sourceIds: z.array(z.string().trim().min(1).max(4_096)).min(1).max(4),
52+
}),
53+
)
54+
.min(1)
55+
.max(16),
56+
report: z.string().trim().min(1).max(12_000),
5157
});
5258

5359
type SourceReference = z.infer<typeof SourceReferenceSchema>;

0 commit comments

Comments
 (0)