feat: remove LLM extract fallback, require YAML frontmatter
Agent output must contain valid YAML frontmatter matching the role schema. If frontmatter parsing fails, the step fails immediately with a clear error instead of falling back to an LLM extraction that can fabricate values. The extract module remains as a public API export but is no longer used in the agent run loop. Breaking change: agents that relied on LLM extraction to produce valid output will now fail. They must output proper frontmatter.
This commit is contained in:
@@ -3,10 +3,9 @@ import type { CasRef, StepNodePayload, ThreadId } from "@uncaged/workflow-protoc
|
|||||||
import { config as loadDotenv } from "dotenv";
|
import { config as loadDotenv } from "dotenv";
|
||||||
import { buildOutputFormatInstruction } from "./build-output-format-instruction.js";
|
import { buildOutputFormatInstruction } from "./build-output-format-instruction.js";
|
||||||
import { buildContextWithMeta } from "./context.js";
|
import { buildContextWithMeta } from "./context.js";
|
||||||
import { extract } from "./extract.js";
|
|
||||||
import { tryFrontmatterFastPath } from "./frontmatter.js";
|
import { tryFrontmatterFastPath } from "./frontmatter.js";
|
||||||
import type { AgentStore } from "./storage.js";
|
import type { AgentStore } from "./storage.js";
|
||||||
import { getEnvPath, loadWorkflowConfig, resolveStorageRoot } from "./storage.js";
|
import { getEnvPath, resolveStorageRoot } from "./storage.js";
|
||||||
import type { AgentContext, AgentOptions, AgentRunResult } from "./types.js";
|
import type { AgentContext, AgentOptions, AgentRunResult } from "./types.js";
|
||||||
|
|
||||||
function fail(message: string): never {
|
function fail(message: string): never {
|
||||||
@@ -73,24 +72,19 @@ async function runAgent(options: AgentOptions, ctx: AgentContext): Promise<Agent
|
|||||||
async function extractOutput(
|
async function extractOutput(
|
||||||
rawOutput: string,
|
rawOutput: string,
|
||||||
outputSchema: CasRef,
|
outputSchema: CasRef,
|
||||||
storageRoot: string,
|
|
||||||
ctx: Awaited<ReturnType<typeof buildContextWithMeta>>,
|
ctx: Awaited<ReturnType<typeof buildContextWithMeta>>,
|
||||||
): Promise<CasRef> {
|
): Promise<CasRef> {
|
||||||
const fastPath = await runWithMessage("frontmatter fast path", () =>
|
const fastPath = await tryFrontmatterFastPath(rawOutput, outputSchema, ctx.meta.store);
|
||||||
tryFrontmatterFastPath(rawOutput, outputSchema, ctx.meta.store),
|
|
||||||
).catch(() => null);
|
|
||||||
|
|
||||||
if (fastPath !== null) {
|
if (fastPath !== null) {
|
||||||
return fastPath.outputHash;
|
return fastPath.outputHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await runWithMessage("failed to load config", () =>
|
fail(
|
||||||
loadWorkflowConfig(storageRoot),
|
"Agent output does not contain valid YAML frontmatter matching the role schema.\n" +
|
||||||
|
"The agent must output a YAML frontmatter block (--- delimited) as the first thing in its response.\n" +
|
||||||
|
`Raw output (first 500 chars): ${rawOutput.slice(0, 500)}`,
|
||||||
);
|
);
|
||||||
const extracted = await runWithMessage("extract failed", () =>
|
|
||||||
extract(rawOutput, outputSchema, config),
|
|
||||||
);
|
|
||||||
return extracted.hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function persistStep(options: {
|
async function persistStep(options: {
|
||||||
@@ -136,12 +130,7 @@ export function createAgent(options: AgentOptions): () => Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const agentResult = await runAgent(options, ctx);
|
const agentResult = await runAgent(options, ctx);
|
||||||
const outputHash = await extractOutput(
|
const outputHash = await extractOutput(agentResult.output, roleDef.frontmatter, ctx);
|
||||||
agentResult.output,
|
|
||||||
roleDef.frontmatter,
|
|
||||||
storageRoot,
|
|
||||||
ctx,
|
|
||||||
);
|
|
||||||
const stepHash = await persistStep({
|
const stepHash = await persistStep({
|
||||||
ctx,
|
ctx,
|
||||||
outputHash,
|
outputHash,
|
||||||
|
|||||||
Reference in New Issue
Block a user