Phase 1プロンプトにもステータスルールを注入(Phase 3との併用方式)

buildInstruction()にセクション7を追加し、タグベースのルールがある場合に
判定基準と出力フォーマットをPhase 1のプロンプトに注入する。
ai()/aggregate条件のみの場合はスキップ。
This commit is contained in:
nrslib 2026-01-30 17:38:49 +09:00
parent 9c05b45e1e
commit 213e293c06
2 changed files with 40 additions and 25 deletions

View File

@ -362,8 +362,8 @@ describe('instruction-builder', () => {
});
});
describe('buildInstruction with rules (Phase 1 — no status tags)', () => {
it('should NOT include status rules even when rules exist (phase separation)', () => {
describe('buildInstruction with rules (Phase 1 — status rules injection)', () => {
it('should include status rules when tag-based rules exist', () => {
const step = createMinimalStep('Do work');
step.name = 'plan';
step.rules = [
@ -374,10 +374,9 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
// Phase 1 should NOT contain status header or criteria
expect(result).not.toContain('Status Output Rules');
expect(result).not.toContain('Decision Criteria');
expect(result).not.toContain('[PLAN:');
expect(result).toContain('Decision Criteria');
expect(result).toContain('[PLAN:1]');
expect(result).toContain('[PLAN:2]');
});
it('should not add status rules when rules do not exist', () => {
@ -386,7 +385,6 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).not.toContain('Decision Criteria');
});
@ -397,7 +395,6 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).not.toContain('Decision Criteria');
});
});
@ -859,8 +856,8 @@ describe('instruction-builder', () => {
});
});
describe('phase separation — buildInstruction never includes status rules', () => {
it('should NOT include status rules even with ai() conditions', () => {
describe('status rules injection — skip when all rules are ai()/aggregate', () => {
it('should NOT include status rules when all rules are ai() conditions', () => {
const step = createMinimalStep('Do work');
step.rules = [
{ condition: 'ai("No issues")', next: 'COMPLETE', isAiCondition: true, aiConditionText: 'No issues' },
@ -870,12 +867,13 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).not.toContain('Decision Criteria');
expect(result).not.toContain('[TEST-STEP:');
});
it('should NOT include status rules with mixed regular and ai() conditions', () => {
it('should include status rules with mixed regular and ai() conditions', () => {
const step = createMinimalStep('Do work');
step.name = 'review';
step.rules = [
{ condition: 'Error occurred', next: 'ABORT' },
{ condition: 'ai("Issues found")', next: 'fix', isAiCondition: true, aiConditionText: 'Issues found' },
@ -884,11 +882,13 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).toContain('Decision Criteria');
expect(result).toContain('[REVIEW:1]');
});
it('should NOT include status rules with regular conditions only', () => {
it('should include status rules with regular conditions only', () => {
const step = createMinimalStep('Do work');
step.name = 'plan';
step.rules = [
{ condition: 'Done', next: 'COMPLETE' },
{ condition: 'Blocked', next: 'ABORT' },
@ -897,10 +897,12 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).toContain('Decision Criteria');
expect(result).toContain('[PLAN:1]');
expect(result).toContain('[PLAN:2]');
});
it('should NOT include status rules with aggregate conditions', () => {
it('should NOT include status rules when all rules are aggregate conditions', () => {
const step = createMinimalStep('Do work');
step.rules = [
{ condition: 'all("approved")', next: 'COMPLETE', isAggregateCondition: true, aggregateType: 'all' as const, aggregateConditionText: 'approved' },
@ -910,10 +912,10 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).not.toContain('Decision Criteria');
});
it('should NOT include status rules with mixed ai() and aggregate conditions', () => {
it('should NOT include status rules when all rules are ai() + aggregate', () => {
const step = createMinimalStep('Do work');
step.rules = [
{ condition: 'all("approved")', next: 'COMPLETE', isAggregateCondition: true, aggregateType: 'all' as const, aggregateConditionText: 'approved' },
@ -924,11 +926,12 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).not.toContain('Decision Criteria');
});
it('should NOT include status rules with mixed aggregate and regular conditions', () => {
it('should include status rules with mixed aggregate and regular conditions', () => {
const step = createMinimalStep('Do work');
step.name = 'supervise';
step.rules = [
{ condition: 'all("approved")', next: 'COMPLETE', isAggregateCondition: true, aggregateType: 'all' as const, aggregateConditionText: 'approved' },
{ condition: 'Error occurred', next: 'ABORT' },
@ -937,7 +940,8 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).not.toContain('Status Output Rules');
expect(result).toContain('Decision Criteria');
expect(result).toContain('[SUPERVISE:1]');
});
});

View File

@ -3,10 +3,12 @@
*
* Builds the instruction string for agent execution by:
* 1. Auto-injecting standard sections (Execution Context, Workflow Context,
* User Request, Previous Response, Additional User Inputs, Instructions header)
* User Request, Previous Response, Additional User Inputs, Instructions header,
* Status Output Rules)
* 2. Replacing template placeholders with actual values
*
* Status judgment is handled separately in Phase 3 (buildStatusJudgmentInstruction).
* Status rules are injected into Phase 1 for tag-based detection,
* and also used in Phase 3 (buildStatusJudgmentInstruction) as a dedicated follow-up.
*/
import type { WorkflowStep, WorkflowRule, AgentResponse, Language, ReportConfig, ReportObjectConfig } from '../models/types.js';
@ -406,8 +408,7 @@ function replaceTemplatePlaceholders(
* 4. Previous Response if passPreviousResponse and has content, unless template contains {previous_response}
* 5. Additional User Inputs unless template contains {user_inputs}
* 6. Instructions header + instruction_template content always
*
* Status judgment is handled separately in Phase 3 (buildStatusJudgmentInstruction).
* 7. Status Output Rules when step has tag-based rules (not all ai()/aggregate)
*
* Template placeholders ({task}, {previous_response}, etc.) are still replaced
* within the instruction_template body for backward compatibility.
@ -462,6 +463,16 @@ export function buildInstruction(
);
sections.push(`${s.instructions}\n${processedTemplate}`);
// 7. Status Output Rules (for tag-based detection in Phase 1)
// Skip if all rules are ai() or aggregate conditions (no tags needed)
if (step.rules && step.rules.length > 0) {
const allNonTagConditions = step.rules.every((r) => r.isAiCondition || r.isAggregateCondition);
if (!allNonTagConditions) {
const statusRulesPrompt = generateStatusRulesFromRules(step.name, step.rules, language);
sections.push(statusRulesPrompt);
}
}
return sections.join('\n\n');
}