一键导入
ai-agents
Building AI agents with tools, streaming, conversation memory, approval flows, and middleware in Rudder
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Building AI agents with tools, streaming, conversation memory, approval flows, and middleware in Rudder
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | ai-agents |
| description | Building AI agents with tools, streaming, conversation memory, approval flows, and middleware in Rudder |
| license | MIT |
| appliesTo | ["@rudderjs/ai"] |
| trigger | building an `Agent` class, calling `Agent.prompt()`/`.stream()`, defining `tools()` or `middleware()`, or wiring conversations / failover |
| skip | writing a tool definition by itself — load `ai-tools` instead |
| metadata | {"author":"rudderjs"} |
Load this skill when you need to build an AI agent, run prompts with tool loops, stream responses, persist conversations, use approval gates, or queue agent work for background execution.
Agent and implement instructions(). Optionally override model(), tools(), maxSteps(), stopWhen(), temperature(), middleware().agent() function for inline, one-off agents without a class.agent.stream() returns { stream: AsyncIterable<StreamChunk>, response: Promise<AgentResponse> }.agent.forUser(id).prompt() or agent.continue(conversationId).prompt() for persistent memory.'provider/model' (e.g. 'anthropic/claude-sonnet-4-5', 'openai/gpt-4o').'stop', 'tool_calls', 'length', 'client_tool_calls', 'tool_approval_required'.// app/Agents/ResearchAgent.ts
import { Agent } from '@rudderjs/ai'
import type { HasTools, AnyTool } from '@rudderjs/ai'
export class ResearchAgent extends Agent implements HasTools {
instructions(): string {
return `You are a research assistant. Use the search tool to find
information and summarize your findings clearly.`
}
model(): string {
return 'anthropic/claude-sonnet-4-5'
}
tools(): AnyTool[] {
return [searchTool, summarizeTool]
}
maxSteps(): number {
return 10
}
}
const agent = new ResearchAgent()
const response = await agent.prompt('What is Rudder?')
console.log(response.text) // final text output
console.log(response.steps) // array of AgentStep
console.log(response.usage) // { promptTokens, completionTokens, totalTokens }
const { stream, response } = agent.stream('Explain TypeScript decorators')
for await (const chunk of stream) {
switch (chunk.type) {
case 'text-delta':
process.stdout.write(chunk.text ?? '')
break
case 'tool-call':
console.log(`Calling tool: ${chunk.toolCall?.name}`)
break
case 'tool-result':
console.log(`Tool result:`, chunk.result)
break
case 'tool-update':
console.log(`Progress:`, chunk.update)
break
case 'finish':
console.log(`Done: ${chunk.finishReason}`)
break
}
}
const finalResponse = await response
import { agent } from '@rudderjs/ai'
// Simple string instructions
const response = await agent('You are a helpful assistant.').prompt('Hello')
// With tools and model
const response = await agent({
instructions: 'You are a search assistant.',
tools: [searchTool],
model: 'anthropic/claude-sonnet-4-5',
}).prompt('Find users named John')
const myAgent = new ResearchAgent()
// Start a new conversation for a user
const response1 = await myAgent.forUser('user-123').prompt('What is TypeScript?')
const convId = response1.conversationId!
// Continue the same conversation
const response2 = await myAgent.continue(convId).prompt('Tell me more about generics')
// The agent sees the full conversation history
// Streaming with conversations
const { stream, response } = myAgent.forUser('user-123').stream('Explain async/await')
A ConversationStore must be registered. The built-in MemoryConversationStore works for dev; implement the ConversationStore interface for production (database-backed).
import { Agent, stepCountIs, hasToolCall } from '@rudderjs/ai'
class MyAgent extends Agent {
instructions() { return 'You are helpful.' }
stopWhen() {
return [
stepCountIs(5), // stop after 5 iterations
hasToolCall('final_answer'), // stop when this tool is called
]
// Multiple conditions use OR logic -- stops when any is true
}
}
class AdaptiveAgent extends Agent {
instructions() { return 'You are helpful.' }
prepareStep(ctx: { stepNumber: number; steps: AgentStep[]; messages: AiMessage[] }) {
if (ctx.stepNumber > 3) {
return { model: 'anthropic/claude-haiku-3' } // cheaper model for later steps
}
return {}
}
}
import type { AiMiddleware } from '@rudderjs/ai'
const loggingMiddleware: AiMiddleware = {
name: 'logging',
onStart(ctx) { console.log(`Agent started, model: ${ctx.model}`) },
onChunk(ctx, chunk) {
if (chunk.type === 'text-delta') process.stdout.write(chunk.text ?? '')
return chunk // return null to suppress the chunk
},
onBeforeToolCall(ctx, toolName, args) {
console.log(`Calling ${toolName}`, args)
// Return { type: 'skip', result: 'mocked' } to skip execution
// Return { type: 'abort', reason: 'blocked' } to abort the loop
},
onAfterToolCall(ctx, toolName, args, result) {
console.log(`${toolName} returned`, result)
},
onUsage(ctx, usage) {
console.log(`Tokens: ${usage.totalTokens}`)
},
onError(ctx, error) {
console.error('Agent error:', error)
},
}
class MyAgent extends Agent implements HasMiddleware {
instructions() { return 'You are helpful.' }
middleware() { return [loggingMiddleware] }
}
const myAgent = new ResearchAgent()
// Queue for async processing (requires @rudderjs/queue)
myAgent.queue('Analyze this dataset').dispatch()
class ResilientAgent extends Agent {
instructions() { return 'You are helpful.' }
model() { return 'anthropic/claude-sonnet-4-5' }
failover() { return ['openai/gpt-4o', 'google/gemini-2.0-flash'] }
}
const response = await agent('Describe this image.').prompt('What do you see?', {
attachments: [
{ type: 'image', data: base64String, mimeType: 'image/png' },
{ type: 'document', data: pdfBase64, mimeType: 'application/pdf', name: 'report.pdf' },
],
})
See playground/app/Agents/ResearchAgent.ts for a working agent class.
@anthropic-ai/sdk, openai, @google/genai.model() returns undefined, the agent uses the registry default from config/ai.ts. Make sure one is configured.forUser() / continue() throw if no ConversationStore is registered. Register one via setConversationStore() or through the AI service provider.maxSteps, it stops with whatever text it has. Override maxSteps() for agents that need more iterations.yield from an async function* tool execute emits tool-update chunks during streaming. In non-streaming prompt(), yields are silently drained.execute function are client tools. The loop pauses with finishReason: 'client_tool_calls' and returns pendingClientToolCalls for browser-side execution.Setting up authentication with guards, sessions, registration, password reset, gates/policies, and vendor views in Rudder
Building MCP servers with tools, resources, prompts, decorators, and HTTP/stdio transports in Rudder
Creating Eloquent-style models, queries, relationships, casts, factories, and API resources in Rudder
Creating controller-returned views with route overrides, multi-framework support, and the html tagged template in Rudder
Defining server and client tools with Zod schemas, approval gates, streaming yields, and modelOutput for Rudder AI agents
Setting up authentication with guards, sessions, registration, password reset, gates/policies, and vendor views in Rudder