Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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
48 changes: 48 additions & 0 deletions docs/content/docs/api-reference/workflow-ai/durable-agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ async function myAgent() {
const agent = new DurableAgent({
model: 'anthropic/claude-haiku-4.5',
system: 'You are a helpful weather assistant.',
temperature: 0.7, // Control output randomness
maxOutputTokens: 1000, // Limit response length
tools: {
getWeather: {
description: 'Get weather for a city',
Expand Down Expand Up @@ -177,6 +179,52 @@ async function multiToolAgentWorkflow(userQuery: string) {
}
```

### Advanced Configuration

```typescript
import { DurableAgent } from '@workflow/ai/agent';
import { z } from 'zod';

async function calculateResult({ formula }: { formula: string }) {
"use step";
// Perform calculation
return "42";
}

async function advancedAgentWorkflow(userQuery: string) {
'use workflow';

const agent = new DurableAgent({
model: 'anthropic/claude-haiku-4.5',
system: 'You are a precise calculator assistant.',
// Model behavior controls
temperature: 0.3, // Lower temperature for more deterministic responses
maxOutputTokens: 500, // Limit response length
topP: 0.9, // Nucleus sampling for response variety
presencePenalty: 0.2, // Reduce repetition
frequencyPenalty: 0.2, // Reduce word repetition
seed: 12345, // For reproducible results
stopSequences: ['END'], // Stop generation at specific sequences
tools: {
calculateResult: {
description: 'Calculate a mathematical result',
inputSchema: z.object({ formula: z.string() }),
execute: calculateResult,
},
},
});

await agent.stream({
messages: [
{
role: 'user',
content: userQuery,
},
],
});
}
```

### Tools with Workflow Library Features

```typescript
Expand Down
20 changes: 19 additions & 1 deletion packages/ai/src/agent/do-stream-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,32 @@ export async function doStreamStep(
conversationPrompt: LanguageModelV2Prompt,
modelId: string,
writable: WritableStream<UIMessageChunk>,
tools?: LanguageModelV2CallOptions['tools']
tools?: LanguageModelV2CallOptions['tools'],
callOptions?: {
temperature?: number;
maxOutputTokens?: number;
topP?: number;
topK?: number;
presencePenalty?: number;
frequencyPenalty?: number;
stopSequences?: string[];
seed?: number;
}
) {
'use step';

const model = gateway(modelId);
const result = await model.doStream({
prompt: conversationPrompt,
tools,
temperature: callOptions?.temperature,
maxOutputTokens: callOptions?.maxOutputTokens,
topP: callOptions?.topP,
topK: callOptions?.topK,
presencePenalty: callOptions?.presencePenalty,
frequencyPenalty: callOptions?.frequencyPenalty,
stopSequences: callOptions?.stopSequences,
seed: callOptions?.seed,
});

let finish: FinishPart | undefined;
Expand Down
169 changes: 169 additions & 0 deletions packages/ai/src/agent/durable-agent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* Tests for DurableAgent
*
* These tests verify that the DurableAgent constructor properly accepts
* and stores configuration options from the AI SDK Agent class.
*/
import { describe, expect, it } from 'vitest';
import { DurableAgent } from './durable-agent.js';

describe('DurableAgent', () => {
describe('constructor', () => {
it('should accept basic required options', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
});

expect(agent).toBeDefined();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This (and all similar ones in the rest of the tests) is a weak assertion. Can we test that the relevant property actually exists on the class instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in cb5acbd. Tests now verify that each property value is correctly stored on the class instance using type assertions (as any) to access the private properties.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Instead of casting to any, how about we just drop the private statements on the class definition itself. private in TS is kind of dumb anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 528f3ed. Removed private modifiers from all DurableAgent properties and updated tests to access properties directly without type assertions.

});

it('should accept system prompt', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
system: 'You are a helpful assistant.',
});

expect(agent).toBeDefined();
});

it('should accept temperature option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
temperature: 0.7,
});

expect(agent).toBeDefined();
});

it('should accept maxOutputTokens option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
maxOutputTokens: 1000,
});

expect(agent).toBeDefined();
});

it('should accept topP option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
topP: 0.9,
});

expect(agent).toBeDefined();
});

it('should accept topK option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
topK: 40,
});

expect(agent).toBeDefined();
});

it('should accept presencePenalty option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
presencePenalty: 0.5,
});

expect(agent).toBeDefined();
});

it('should accept frequencyPenalty option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
frequencyPenalty: 0.5,
});

expect(agent).toBeDefined();
});

it('should accept stopSequences option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
stopSequences: ['STOP', 'END'],
});

expect(agent).toBeDefined();
});

it('should accept seed option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
seed: 12345,
});

expect(agent).toBeDefined();
});

it('should accept all options together', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
system: 'You are a helpful assistant.',
temperature: 0.7,
maxOutputTokens: 1000,
topP: 0.9,
topK: 40,
presencePenalty: 0.5,
frequencyPenalty: 0.3,
stopSequences: ['STOP', 'END'],
seed: 12345,
});

expect(agent).toBeDefined();
});

it('should accept tools with proper structure', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {
testTool: {
description: 'A test tool',
inputSchema: {
type: 'object',
properties: {},
},
execute: async () => 'result',
},
},
});

expect(agent).toBeDefined();
});
});

describe('methods', () => {
it('should have generate method', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
});

expect(agent.generate).toBeDefined();
expect(typeof agent.generate).toBe('function');
});

it('should have stream method', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
});

expect(agent.stream).toBeDefined();
expect(typeof agent.stream).toBe('function');
});
});
});
85 changes: 85 additions & 0 deletions packages/ai/src/agent/durable-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,67 @@ export interface DurableAgentOptions {
* Optional system prompt to guide the agent's behavior.
*/
system?: string;

/**
* Temperature setting. The range depends on the provider and model.
*
* It is recommended to set either `temperature` or `topP`, but not both.
*/
temperature?: number;

/**
* Maximum number of tokens to generate.
*/
maxOutputTokens?: number;

/**
* Nucleus sampling. This is a number between 0 and 1.
*
* E.g. 0.1 would mean that only tokens with the top 10% probability mass
* are considered.
*
* It is recommended to set either `temperature` or `topP`, but not both.
*/
topP?: number;

/**
* Only sample from the top K options for each subsequent token.
*
* Used to remove "long tail" low probability responses.
* Recommended for advanced use cases only. You usually only need to use temperature.
*/
topK?: number;

/**
* Presence penalty setting. It affects the likelihood of the model to
* repeat information that is already in the prompt.
*
* The presence penalty is a number between -1 (increase repetition)
* and 1 (maximum penalty, decrease repetition). 0 means no penalty.
*/
presencePenalty?: number;

/**
* Frequency penalty setting. It affects the likelihood of the model
* to repeatedly use the same words or phrases.
*
* The frequency penalty is a number between -1 (increase repetition)
* and 1 (maximum penalty, decrease repetition). 0 means no penalty.
*/
frequencyPenalty?: number;

/**
* Stop sequences.
* If set, the model will stop generating text when one of the stop sequences is generated.
* Providers may have limits on the number of stop sequences.
*/
stopSequences?: string[];

/**
* The seed (integer) to use for random sampling. If set and supported
* by the model, calls will generate deterministic results.
*/
seed?: number;
}

/**
Expand Down Expand Up @@ -92,11 +153,27 @@ export class DurableAgent {
private model: string;
private tools: ToolSet;
private system?: string;
private temperature?: number;
private maxOutputTokens?: number;
private topP?: number;
private topK?: number;
private presencePenalty?: number;
private frequencyPenalty?: number;
private stopSequences?: string[];
private seed?: number;

constructor(options: DurableAgentOptions) {
this.model = options.model;
this.tools = options.tools;
this.system = options.system;
this.temperature = options.temperature;
this.maxOutputTokens = options.maxOutputTokens;
this.topP = options.topP;
this.topK = options.topK;
this.presencePenalty = options.presencePenalty;
this.frequencyPenalty = options.frequencyPenalty;
this.stopSequences = options.stopSequences;
this.seed = options.seed;
}

generate() {
Expand Down Expand Up @@ -124,6 +201,14 @@ export class DurableAgent {
tools: this.tools,
writable,
prompt: modelPrompt,
temperature: this.temperature,
maxOutputTokens: this.maxOutputTokens,
topP: this.topP,
topK: this.topK,
presencePenalty: this.presencePenalty,
frequencyPenalty: this.frequencyPenalty,
stopSequences: this.stopSequences,
seed: this.seed,
});

let result = await iterator.next();
Expand Down
Loading
Loading