Skip to content
Draft
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
73 changes: 73 additions & 0 deletions packages/sdk/server-ai/__tests__/RunnerProtocol.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { AgentGraphRunnerResult } from '../src/api/graph/types';
import type { RunnerResult } from '../src/api/model/types';
import type { AgentGraphRunner, Runner } from '../src/api/providers/Runner';

/**
* Verify that the Runner and AgentGraphRunner protocols can be implemented
* by a plain object (no abstract class required).
*/
describe('Runner protocol', () => {
it('can be implemented as a plain object (no class extension required)', async () => {
const runnerResult: RunnerResult = {
content: 'Hello from runner',
metrics: { success: true },
};

const myRunner: Runner = {
run: jest.fn().mockResolvedValue(runnerResult),
};

const result = await myRunner.run([{ role: 'user', content: 'Hello' }]);

expect(result.content).toBe('Hello from runner');
expect(result.metrics.success).toBe(true);
});

it('Runner.run() accepts optional outputType for structured output', async () => {
const runnerResult: RunnerResult = {
content: '',
metrics: { success: true },
parsed: { score: 0.9, reasoning: 'good' },
};

const myRunner: Runner = {
run: jest.fn().mockResolvedValue(runnerResult),
};

const schema = { type: 'object', properties: { score: { type: 'number' } } };
const result = await myRunner.run([{ role: 'user', content: 'Evaluate' }], schema);

expect(result.parsed).toEqual({ score: 0.9, reasoning: 'good' });
expect(myRunner.run).toHaveBeenCalledWith([{ role: 'user', content: 'Evaluate' }], schema);
});

it('AgentGraphRunner can be implemented as a plain object', async () => {
const graphResult: AgentGraphRunnerResult = {
content: 'Graph output',
metrics: {
success: true,
path: ['node-a'],
nodeMetrics: { 'node-a': { success: true } },
},
};

const myGraphRunner: AgentGraphRunner = {
run: jest.fn().mockResolvedValue(graphResult),
};

const result = await myGraphRunner.run('user input');

expect(result.content).toBe('Graph output');
expect(result.metrics.path).toEqual(['node-a']);
});

it('RunnerResult does NOT include evaluations field', () => {
const result: RunnerResult = {
content: 'test',
metrics: { success: true },
};

// TypeScript would catch this at compile time, but verify at runtime shape too
expect('evaluations' in result).toBe(false);
});
});
4 changes: 4 additions & 0 deletions packages/sdk/server-ai/src/api/providers/AIProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { StructuredResponse } from '../judge/types';
*
* Following the AICHAT spec recommendation to use base classes with non-abstract methods
* for better extensibility and backwards compatibility.
*
* @deprecated Use the `Runner` interface instead. Provider implementations should
* implement `Runner` (and optionally `AgentGraphRunner`) rather than extending this
* abstract class. This class will be removed in a future major version.
*/
export abstract class AIProvider {
protected readonly logger?: LDLogger;
Expand Down
41 changes: 41 additions & 0 deletions packages/sdk/server-ai/src/api/providers/Runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { LDMessage } from '../config/types';
import { AgentGraphRunnerResult } from '../graph/types';
import { RunnerResult } from '../model/types';

/**
* Runner protocol for AI model providers.
*
* Providers implementing the Runner interface can be used with ManagedModel
* and ManagedAgent without extending the deprecated AIProvider abstract class.
*
* A single Runner interface covers both chat (completion) and agent use cases.
* For structured output (e.g., judge evaluation), pass an `outputType` schema
* and access the parsed result via `RunnerResult.parsed`.
*/
export interface Runner {
/**
* Invoke the model with an array of messages.
*
* @param input Array of LDMessage objects representing the conversation or prompt.
* @param outputType Optional JSON schema for structured output. When provided,
* the model should return structured data accessible via `RunnerResult.parsed`.
* @returns Promise resolving to a RunnerResult.
*/
run(input: LDMessage[], outputType?: Record<string, unknown>): Promise<RunnerResult>;
}

/**
* Runner protocol for agent graph providers.
*
* Providers implementing AgentGraphRunner can execute an entire agent graph
* and return a structured AgentGraphRunnerResult.
*/
export interface AgentGraphRunner {
/**
* Execute the agent graph with the given input.
*
* @param input The user input to process through the graph.
* @returns Promise resolving to an AgentGraphRunnerResult.
*/
run(input: string): Promise<AgentGraphRunnerResult>;
}
1 change: 1 addition & 0 deletions packages/sdk/server-ai/src/api/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './AIProvider';
export * from './AIProviderFactory';
export type { Runner, AgentGraphRunner } from './Runner';
Loading