Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/agent-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ Each concurrent research pass gets an isolated evidence collector populated only
from one bounded Exa discovery call and an optional Firecrawl extraction of its
primary result. A single tool-free model pass structures each byte-bounded provider
evidence pack. Nested model calls use stage-appropriate output bounds, an operational
timeout, and one in-memory retry for transient provider failures; request cancellation
always wins and no secret-bearing state is snapshotted. Claim citations and the final
synthesis are schema-validated against that evidence; prose URL scraping is not an
accepted provenance boundary.
timeout, and one in-memory retry for transient provider or invalid structured-output
failures; request cancellation always wins and no secret-bearing state is snapshotted.
Claim citations and the final synthesis are schema-validated against that evidence;
prose URL scraping is not an accepted provenance boundary.
Successful top-level deep-research and fan-out tools render the validated report's
canonical GitHub-flavored Markdown directly into a PDF artifact. The chat response
and PDF therefore preserve the same headings, prose, lists, tables, links, citations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,21 +148,24 @@ function createQueryStep(id: string, config: ResearchWorkflowPrompts) {
research.requestContext,
abortSignal,
);
const response = await generateResearchOutput(abortSignal, (generationSignal) =>
agent.generate(researchPassPrompt(config, inputData.query, evidence), {
activeTools: [],
abortSignal: generationSignal,
modelSettings: { maxOutputTokens: RESEARCH_PASS_MAX_OUTPUT_TOKENS },
providerOptions: RESEARCH_PROVIDER_OPTIONS,
requestContext: research.requestContext,
structuredOutput: { schema: ResearchPassDraftSchema },
}),
);
return validateResearchPass(
parseResearchPassDraft(response.object),
inputData.query,
research.collector,
);
return generateResearchOutput(abortSignal, async (generationSignal) => {
const response = await agent.generate(
researchPassPrompt(config, inputData.query, evidence),
{
activeTools: [],
abortSignal: generationSignal,
modelSettings: { maxOutputTokens: RESEARCH_PASS_MAX_OUTPUT_TOKENS },
providerOptions: RESEARCH_PROVIDER_OPTIONS,
requestContext: research.requestContext,
structuredOutput: { schema: ResearchPassDraftSchema },
},
);
return validateResearchPass(
parseResearchPassDraft(response.object),
inputData.query,
research.collector,
);
});
},
});
}
Expand All @@ -176,22 +179,22 @@ function createSynthesisStep(id: string, config: ResearchWorkflowPrompts) {
execute: async ({ abortSignal, inputData, mastra, requestContext }) => {
const agent = mastra.getAgent("general");
const sources = mergeResearchSources(inputData);
const response = await generateResearchOutput(abortSignal, (generationSignal) =>
agent.generate(researchSynthesisPrompt(config, inputData), {
return generateResearchOutput(abortSignal, async (generationSignal) => {
const response = await agent.generate(researchSynthesisPrompt(config, inputData), {
activeTools: [],
abortSignal: generationSignal,
modelSettings: { maxOutputTokens: RESEARCH_SYNTHESIS_MAX_OUTPUT_TOKENS },
providerOptions: RESEARCH_PROVIDER_OPTIONS,
requestContext,
structuredOutput: { schema: ResearchSynthesisDraftSchema },
}),
);
const draft = parseResearchSynthesisDraft(response.object);
return ResearchReportSchema.parse({
claims: validateSynthesisClaims(draft.claims, sources),
findings: inputData,
report: draft.report,
sources,
});
const draft = parseResearchSynthesisDraft(response.object);
return ResearchReportSchema.parse({
claims: validateSynthesisClaims(draft.claims, sources),
findings: inputData,
report: draft.report,
sources,
});
});
},
});
Expand Down Expand Up @@ -301,7 +304,8 @@ function researchPassPrompt(
config.queryPrompt(query),
"Use only the provider evidence below. For Exa citations, copy providerResultId and URL exactly. For Firecrawl citations, copy the URL exactly.",
"Set providerResultId to an empty string for every Firecrawl citation.",
"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.",
"Return 3-4 distinct, synthesis-ready claims. Keep each claim under 450 characters, use no more than 2 sources per claim, and keep the summary under 700 characters.",
"Prioritize the strongest guidance instead of exhaustively restating the evidence.",
"Do not cite sourceId directly and do not add sources that are absent from this evidence pack.",
"",
JSON.stringify(evidence, null, 2),
Expand All @@ -315,7 +319,7 @@ function researchSynthesisPrompt(
return [
config.synthesisPrompt(findings),
"Consolidate overlapping evidence into at most 16 distinct claims with no more than 4 source IDs per claim.",
"Keep the report focused and complete within 2,000 words while retaining actionable findings and citations.",
"Keep the report focused and complete within 1,200 words while retaining actionable findings and citations.",
"Write report as polished GitHub-flavored Markdown for direct display and PDF rendering. Preserve a clear heading hierarchy, lists, and comparison tables where useful.",
"Cite factual claims with descriptive Markdown links to the exact source URLs in the findings, and finish with a Sources heading containing only sources used in the report.",
].join("\n");
Expand Down Expand Up @@ -381,8 +385,19 @@ function isRetriableModelError(error: unknown): boolean {
if (error.name === "TimeoutError") {
return true;
}
const record = error as Error & { isRetryable?: unknown; statusCode?: unknown };
if (record.isRetryable === true) {
const record = error as Error & {
code?: unknown;
id?: unknown;
isRetryable?: unknown;
retriable?: unknown;
statusCode?: unknown;
};
if (
record.isRetryable === true ||
record.retriable === true ||
record.id === "STRUCTURED_OUTPUT_SCHEMA_VALIDATION_FAILED" ||
record.code === "STRUCTURED_OUTPUT_SCHEMA_VALIDATION_FAILED"
) {
return true;
}
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,26 @@ export const ResearchPassDraftSchema = z.strictObject({
claims: z
.array(
z.strictObject({
claim: z.string().trim().min(1),
sources: z.array(SourceReferenceDraftSchema).min(1).max(3),
claim: z.string().trim().min(1).max(600),
sources: z.array(SourceReferenceDraftSchema).min(1).max(2),
}),
)
.min(1)
.max(6),
summary: z.string().trim().min(1),
.max(4),
summary: z.string().trim().min(1).max(1_000),
});

export const ResearchSynthesisDraftSchema = z.strictObject({
claims: z
.array(
z.strictObject({
claim: z.string().trim().min(1),
claim: z.string().trim().min(1).max(1_000),
sourceIds: z.array(z.string().trim().min(1).max(4_096)).min(1).max(4),
}),
)
.min(1)
.max(16),
report: z.string().trim().min(1),
report: z.string().trim().min(1).max(20_000),
});

type SourceReference = z.infer<typeof SourceReferenceSchema>;
Expand Down