fix(core): Restore the system prompt in Instance AI LLM step debug (no-changelog) (#38887)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
José Braulio González Valido 2026-09-17 13:26:23 +00:00 committed by GitHub
parent 33def0e800
commit e15b3fc922
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 67 additions and 7 deletions

View File

@ -677,6 +677,7 @@ export type { AgentRunState } from './schemas/agent-run-reducer';
export {
formatDebugJson,
summarizeJsonValue,
stepInstructions,
parseSystemPromptForDisplay,
parseMessageBlocks,
parseUsageSummary,

View File

@ -12,6 +12,7 @@ import {
parseUsageSummary,
summarizeJsonValue,
extractObservationsBlock,
stepInstructions,
} from '../llm-step-display';
describe('llm-step-display', () => {
@ -287,4 +288,36 @@ describe('llm-step-display', () => {
{ type: 'text', text: 'Skill loading protocol' },
]);
});
describe('stepInstructions', () => {
it('reads the v7 `instructions` field', () => {
expect(stepInstructions({ instructions: 'You are helpful.' })).toBe('You are helpful.');
});
it('falls back to the pre-v7 `system` field for older snapshots', () => {
expect(stepInstructions({ system: 'You are helpful.' })).toBe('You are helpful.');
});
it('prefers `instructions` when a snapshot somehow carries both', () => {
expect(stepInstructions({ instructions: 'new', system: 'old' })).toBe('new');
});
it('returns undefined for a missing input', () => {
expect(stepInstructions(undefined)).toBeUndefined();
});
// The shape real captures carry: `{ role, content, providerOptions }`.
it('lets parseStepSummary size a v7 instructions object', () => {
const summary = parseStepSummary(
{ instructions: { role: 'system', content: 'x'.repeat(120) } },
{ finishReason: 'stop' },
);
expect(summary.systemCharCount).toBe(120);
});
it('still sizes a pre-v7 string system prompt', () => {
const summary = parseStepSummary({ system: 'x'.repeat(42) }, { finishReason: 'stop' });
expect(summary.systemCharCount).toBe(42);
});
});
});

View File

@ -431,6 +431,16 @@ function extractObservationsFromBlock(block: ReadableContentBlock): {
};
}
/**
* The step's system prompt, wherever the SDK put it. AI SDK v7 standardizes it as
* `instructions` (`StandardizedPrompt`); `system` is the pre-v7 name, kept as a
* fallback so snapshots captured before the rename still render. Without this the
* System section — and the `<observations>` block inside it — reads as empty.
*/
export function stepInstructions(input: Record<string, unknown> | undefined): unknown {
return input?.instructions ?? input?.system;
}
export function parseSystemPromptForDisplay(system: unknown): ParsedSystemPromptDisplay {
const blocks = parseSystemBlocks(system);
const observationsParts: string[] = [];
@ -588,7 +598,15 @@ export function parseInputExtras(input: Record<string, unknown> | undefined): un
if (!input) return undefined;
const extras: Record<string, unknown> = {};
const primaryKeys = new Set(['system', 'messages', 'stepNumber', 'sdkStepNumber']);
// `instructions` is the v7 name for `system` — both are rendered as the System
// section, so neither belongs in the extras dump.
const primaryKeys = new Set([
'system',
'instructions',
'messages',
'stepNumber',
'sdkStepNumber',
]);
for (const [key, value] of Object.entries(input)) {
if (!primaryKeys.has(key)) {
@ -753,11 +771,15 @@ export function parseStepSummary(
}
}
// Same field move as the System section: read through `stepInstructions`, or
// this silently stays undefined for every v7 snapshot. `parseSystemBlocks`
// handles the string, array and single-object forms alike.
const instructions = stepInstructions(input);
let systemCharCount: number | undefined;
if (typeof input?.system === 'string') {
systemCharCount = input.system.length;
} else if (Array.isArray(input?.system)) {
systemCharCount = parseSystemBlocks(input.system).reduce(
if (typeof instructions === 'string') {
systemCharCount = instructions.length;
} else if (instructions !== undefined && instructions !== null) {
systemCharCount = parseSystemBlocks(instructions).reduce(
(total, block) => total + block.content.length,
0,
);

View File

@ -21,6 +21,7 @@ import {
parseStepSummary,
parseSystemPromptForDisplay,
parseUsageSummary,
stepInstructions,
} from '@n8n/api-types';
import fs from 'fs';
import path from 'path';
@ -116,7 +117,7 @@ function renderStepDetail(
step: InstanceAiRunDebugStep,
workflowCode: InstanceAiRunDebugWorkflowCodeSnapshot[],
): string {
const parsedSystem = parseSystemPromptForDisplay(step.input?.system);
const parsedSystem = parseSystemPromptForDisplay(stepInstructions(step.input));
const messageBlocks = parseMessageBlocks(step.input?.messages);
const inputExtras = parseInputExtras(step.input);
const outputBlocks = parseOutputDisplayBlocks(step.output);

View File

@ -8,6 +8,7 @@ import {
parseOutputExtras,
parseSystemPromptForDisplay,
parseUsageSummary,
stepInstructions,
} from '@n8n/api-types';
import { N8nText } from '@n8n/design-system';
import { useI18n } from '@n8n/i18n';
@ -26,7 +27,9 @@ const props = defineProps<{
const i18n = useI18n();
const outputSectionRef = ref<HTMLElement | null>(null);
const parsedSystemPrompt = computed(() => parseSystemPromptForDisplay(props.input?.system));
const parsedSystemPrompt = computed(() =>
parseSystemPromptForDisplay(stepInstructions(props.input)),
);
const systemBlocks = computed(() => parsedSystemPrompt.value.systemBlocks);
const systemObservations = computed(() => parsedSystemPrompt.value.observations);
const messageBlocks = computed(() => parseMessageBlocks(props.input?.messages));