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
4 changes: 4 additions & 0 deletions src/ax/dsp/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ export class AxGen<IN = any, OUT extends AxGenOut = any>
this.promptTemplate.setInstruction(instruction);
}

public getInstruction(): string | undefined {
return this.promptTemplate.getInstruction();
}

private getSignatureName(): string {
return this.signature.getDescription() || 'unknown_signature';
}
Expand Down
62 changes: 62 additions & 0 deletions src/ax/dsp/optimizers/gepa.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, expect, it } from 'vitest';
import { ax } from '../template.js';
import type { AxAIService } from '../../ai/types.js';
import { AxGEPA } from './gepa.js';

describe('AxGEPA Optimizer', () => {
describe('getBaseInstruction', () => {
it('should use the description from the signature when available', async () => {
// Create a program with a signature that has a description
const program = ax(
'"This is my custom task description" question:string -> answer:string'
);

// Access the private method via cast
const optimizer = new AxGEPA({
studentAI: {} as AxAIService,
teacherAI: {} as AxAIService,
});

// Call getBaseInstruction
const instruction = await (optimizer as any).getBaseInstruction(program);

// It should return the description from the signature, not the default
expect(instruction).toBe('This is my custom task description');
expect(instruction).not.toBe(
'Follow the task precisely. Be concise, correct, and consistent.'
);
});

it('should fall back to default when signature has no description', async () => {
// Create a program without a description
const program = ax('question:string -> answer:string');

const optimizer = new AxGEPA({
studentAI: {} as AxAIService,
teacherAI: {} as AxAIService,
});

const instruction = await (optimizer as any).getBaseInstruction(program);

// Should use the default fallback
expect(instruction).toBe(
'Follow the task precisely. Be concise, correct, and consistent.'
);
});

it('should use custom instruction when set via setInstruction', async () => {
const program = ax('question:string -> answer:string');
program.setInstruction('My explicitly set custom instruction');

const optimizer = new AxGEPA({
studentAI: {} as AxAIService,
teacherAI: {} as AxAIService,
});

const instruction = await (optimizer as any).getBaseInstruction(program);

// Should return the custom instruction
expect(instruction).toBe('My explicitly set custom instruction');
});
});
});
24 changes: 13 additions & 11 deletions src/ax/dsp/optimizers/gepa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -836,17 +836,19 @@ export class AxGEPA extends AxBaseOptimizer {
private async getBaseInstruction<IN, OUT extends AxGenOut>(
program: Readonly<AxGen<IN, OUT>>
): Promise<string> {
try {
// If program exposes instruction via signature, prefer it
const sig: any = program.getSignature?.();
if (
sig &&
typeof sig.instruction === 'string' &&
sig.instruction.length > 0
) {
return sig.instruction as string;
}
} catch {}
// First check for custom instruction set via setInstruction()
const customInstruction = program.getInstruction?.();
if (customInstruction && customInstruction.length > 0) {
return customInstruction;
}

// Fall back to signature description
const sig = program.getSignature?.();
const description = sig?.getDescription?.();
if (description && description.length > 0) {
return description;
}

return 'Follow the task precisely. Be concise, correct, and consistent.';
}

Expand Down
22 changes: 13 additions & 9 deletions src/ax/dsp/optimizers/gepaFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,15 +830,19 @@ export class AxGEPAFlow extends AxBaseOptimizer {

// === Helpers ===
private async getBaseInstruction(program: any): Promise<string> {
try {
const sig = program?.getSignature?.();
if (
sig &&
typeof sig.instruction === 'string' &&
sig.instruction.length > 0
)
return sig.instruction as string;
} catch {}
// First check for custom instruction set via setInstruction()
const customInstruction = program?.getInstruction?.();
if (customInstruction && customInstruction.length > 0) {
return customInstruction;
}

// Fall back to signature description
const sig = program?.getSignature?.();
const description = sig?.getDescription?.();
if (description && description.length > 0) {
return description;
}

return 'Follow the task precisely. Be concise, correct, and consistent.';
}

Expand Down
6 changes: 6 additions & 0 deletions src/ax/dsp/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,16 @@ export class AxPromptTemplate {
private sig: Readonly<AxSignature>;
private fieldTemplates?: Record<string, AxFieldTemplateFn>;
private task: { type: 'text'; text: string };
private customInstruction?: string;

public setInstruction(instruction: string): void {
this.customInstruction = instruction;
this.task = { type: 'text', text: instruction };
}

public getInstruction(): string | undefined {
return this.customInstruction;
}
private readonly thoughtFieldName: string;
private readonly functions?: Readonly<AxInputFunctionType>;
private readonly cacheSystemPrompt?: boolean;
Expand Down