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
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ uv add langwatch-scenario pytest
pnpm install @langwatch/scenario vitest
```

The TypeScript package targets [AI SDK](https://ai-sdk.dev) 7. If your project is still on AI SDK 6, stay on `@langwatch/scenario` 1.x until you upgrade.

Now create your first scenario, copy the full working example below.

<details>
Expand Down Expand Up @@ -198,13 +200,8 @@ describe("Vegetarian Recipe Agent", () => {
call: async (input) => {
const response = await generateText({
model: openai("gpt-4.1-mini"),
messages: [
{
role: "system",
content: `You are a vegetarian recipe agent.\nGiven the user request, ask AT MOST ONE follow-up question, then provide a complete recipe. Keep your responses concise and focused.`,
},
...input.messages,
],
instructions: `You are a vegetarian recipe agent.\nGiven the user request, ask AT MOST ONE follow-up question, then provide a complete recipe. Keep your responses concise and focused.`,
messages: input.messages,
});
return response.text;
},
Expand Down
27 changes: 16 additions & 11 deletions docs/docs/pages/advanced/remote-traces.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,23 @@ async def test_remote_trace_propagation():
```typescript [typescript]
import { createServer, Server } from "http";
import { openai } from "@ai-sdk/openai";
import { OpenTelemetry } from "@ai-sdk/otel";
import scenario, { AgentRole, type AgentAdapter } from "@langwatch/scenario";
import { context, propagation } from "@opentelemetry/api";
import { W3CTraceContextPropagator } from "@opentelemetry/core";
import { generateText, ModelMessage, stepCountIs, tool } from "ai";
import {
generateText,
ModelMessage,
isStepCount,
registerTelemetry,
tool,
} from "ai";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { z } from "zod/v4";

// AI SDK 7 emits no spans until a telemetry integration is registered.
registerTelemetry(new OpenTelemetry());

// Standard OTel HTTP instrumentation registers a propagator on its own;
// this hand-rolled demo server registers the W3C propagator explicitly.
propagation.setGlobalPropagator(new W3CTraceContextPropagator());
Expand Down Expand Up @@ -277,17 +287,12 @@ beforeAll(async () => {
const result = await context.with(extracted, () =>
generateText({
model: openai("gpt-5-mini"),
messages: [
{
role: "system",
content:
"You are an order support assistant. Use the lookup_order_status tool to check orders before answering.",
},
...messages,
],
instructions:
"You are an order support assistant. Use the lookup_order_status tool to check orders before answering.",
messages,
tools: { lookup_order_status: lookupOrderStatus },
stopWhen: stepCountIs(3),
experimental_telemetry: { isEnabled: true },
stopWhen: isStepCount(3),
telemetry: { isEnabled: true },
})
);

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/pages/agent-integration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ import { z } from "zod";

const getCurrentWeather = tool({
description: "Get the current weather in a given city.",
parameters: z.object({
inputSchema: z.object({
city: z.string().describe("The city to get the weather for."),
}),
execute: async ({ city }) => `The weather in ${city} is sunny!`,
Expand Down
7 changes: 2 additions & 5 deletions docs/docs/pages/agent-integration/vercel-ai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ import { generateText } from 'ai';
async function myAgent(prompt: string) {
const result = await generateText({
model: openai('gpt-4-turbo'),
instructions: 'You are a helpful AI assistant.',
messages: [
{
role: 'system',
content: 'You are a helpful AI assistant.',
},
{
role: 'user',
content: prompt,
Expand All @@ -46,7 +43,7 @@ const createMyAgentAdapter = (): AgentAdapter => {
name: "VercelAIAgent",
call: async (input) => {
const result = await myAgent(input.messages.at(-1)?.content);
return result.response.messages;
return result.responseMessages;
},
};
};
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/pages/basics/scripted-simulations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ const script = [
],
}),

// System message
// System message (with AI SDK 7, an agent that forwards these messages to
// generateText needs `allowSystemInMessages: true`)
message({
role: "system",
content: "The user is now in a hurry and needs quick responses",
Expand Down
9 changes: 2 additions & 7 deletions docs/docs/pages/examples/multimodal/multimodal-images.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,8 @@ const imageAgent: AgentAdapter = {
async call(input) {
const response = await generateText({
model: openai("gpt-4o"),
messages: [
{
role: "system",
content: `You are a helpful assistant that can describe images.`,
},
...input.messages,
],
instructions: `You are a helpful assistant that can describe images.`,
messages: input.messages,
});
return response.text;
},
Expand Down
40 changes: 36 additions & 4 deletions docs/docs/pages/introduction/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,40 @@ pnpm install @langwatch/scenario vitest

:::

<Callout type="info">
The TypeScript SDK targets [AI SDK](https://ai-sdk.dev) 7 and Node.js 22.12 or later. If your project is still on AI SDK 6, stay on `@langwatch/scenario` 1.x until you upgrade.
</Callout>

### AI SDK telemetry (TypeScript)

AI SDK 7 emits no spans until a telemetry integration is registered, so the judge and LangWatch cannot see your agent's LLM calls by default. Register the OpenTelemetry integration once at start-up, for example in a Vitest setup file:

```bash
pnpm install @ai-sdk/otel
```

```typescript
// vitest.setup.ts
import { OpenTelemetry } from "@ai-sdk/otel";
import { registerTelemetry } from "ai";

registerTelemetry(new OpenTelemetry());
```

To attach per-call metadata to the spans (a user id, a tenant, a feature flag), pass it as `runtimeContext` and opt each key in with `telemetry.includeRuntimeContext`. Turn on `runtimeContext` on the integration too; the values are emitted as `ai.settings.context.<key>`:

```typescript
registerTelemetry(new OpenTelemetry({ runtimeContext: true }));

await generateText({
model: openai("gpt-5-mini"),
instructions: "You are a vegetarian recipe agent.",
messages: input.messages,
runtimeContext: { userId: "user-123" },
telemetry: { functionId: "recipe-agent", includeRuntimeContext: { userId: true } },
});
```

## Quick Start

Create your first Scenario test. This example demonstrates testing a vegetarian recipe agent:
Expand Down Expand Up @@ -138,10 +172,8 @@ const vegetarianRecipeAgent: AgentAdapter = {
call: async (input) => {
const response = await generateText({
model: openai("gpt-4.1-mini"),
messages: [
{ role: "system", content: "You are a vegetarian recipe agent." },
...input.messages,
],
instructions: "You are a vegetarian recipe agent.",
messages: input.messages,
});
return response.text;
},
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/pages/testing-guides/mocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ const mockWeatherTool = vi.fn().mockResolvedValue("Sunny, 75°F");

const weatherTool = tool({
description: "Get weather data",
parameters: z.object({ city: z.string() }),
inputSchema: z.object({ city: z.string() }),
execute: mockWeatherTool,
});

Expand Down
9 changes: 2 additions & 7 deletions docs/docs/pages/testing-guides/tool-calling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -309,16 +309,11 @@ const agent = (): AgentAdapter => ({
call: async (input) => {
const response = await generateText({
model: openai("gpt-4.1-nano"),
messages: [
{
role: "system",
content: `
instructions: `
You are a helpful assistant that may help the user with weather information.
Do not guess the city if they don't provide it.
`,
},
...input.messages,
],
messages: input.messages,
tools: { get_current_weather: getCurrentWeather },
toolChoice: "auto",
});
Expand Down
16 changes: 14 additions & 2 deletions javascript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ npm install @langwatch/scenario
yarn add @langwatch/scenario
```

Scenario targets [AI SDK](https://ai-sdk.dev) 7 (`ai@^7`) and Node.js 22.12 or later. If your project is still on AI SDK 6, stay on `@langwatch/scenario` 1.x until you upgrade.

AI SDK 7 emits no spans until a telemetry integration is registered. To let the judge and LangWatch see your agent's LLM calls, register one once at start-up (for example in a Vitest setup file):

```typescript
import { OpenTelemetry } from "@ai-sdk/otel";
import { registerTelemetry } from "ai";

registerTelemetry(new OpenTelemetry());
```

## Quick Start

Create your first scenario test in under a minute.
Expand Down Expand Up @@ -86,7 +97,7 @@ describe("Weather Agent", () => {
// 1. Define the tools your agent can use
const getCurrentWeather = tool({
description: "Get the current weather in a given city.",
parameters: z.object({
inputSchema: z.object({
city: z.string().describe("The city to get the weather for."),
}),
execute: async ({ city }) =>
Expand All @@ -100,7 +111,7 @@ describe("Weather Agent", () => {
call: async (input) => {
const response = await generateText({
model: openai("gpt-4.1-mini"),
system: `You are a helpful assistant that may help the user with weather information.`,
instructions: `You are a helpful assistant that may help the user with weather information.`,
messages: input.messages,
tools: { get_current_weather: getCurrentWeather },
});
Expand All @@ -113,6 +124,7 @@ describe("Weather Agent", () => {
{
toolCallId: toolCall.toolCallId,
messages: input.messages,
context: {},
}
);
return [
Expand Down
7 changes: 4 additions & 3 deletions javascript/examples/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
"vitest-mock-extended": "5.1.1"
},
"dependencies": {
"@ai-sdk/anthropic": "3.0.41",
"@ai-sdk/xai": "3.0.50",
"@ai-sdk/anthropic": "4.0.79",
"@ai-sdk/otel": "^1.0.130",
"@ai-sdk/xai": "5.0.21",
"@langwatch/scenario": "workspace:*",
"@openai/agents": "^0.16.0",
"ai": "^6.0.0",
"ai": "^7.0.130",
"express": "^5.1.0",
"openai": "^6.9.1",
"vite": "^8.2.1",
Expand Down
13 changes: 4 additions & 9 deletions javascript/examples/vitest/tests/00-demo-light.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,10 @@ describe("Demo: Lightweight Scenarios", () => {
call: async (input) => {
const response = await generateText({
model: openai("gpt-5-mini"),
messages: [
{
role: "system",
content:
"You can analyze images and respond briefly and helpfully.",
},
...input.messages,
],
experimental_telemetry: { isEnabled: true },
instructions:
"You can analyze images and respond briefly and helpfully.",
messages: input.messages,
telemetry: { isEnabled: true },
});
return response.text;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const userDataAgent: AgentAdapter = {
messages: input.messages,
tools: { fetch_user_data: fetchUserDataTool },
toolChoice: "auto",
experimental_telemetry: { isEnabled: true },
telemetry: { isEnabled: true },
});
return response.text;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const databaseAgent: AgentAdapter = {
find_user: findUserTool,
},
toolChoice: "auto",
experimental_telemetry: { isEnabled: true },
telemetry: { isEnabled: true },
});
return response.text;
},
Expand Down
8 changes: 3 additions & 5 deletions javascript/examples/vitest/tests/false-assumptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ describe("False Assumptions", () => {
call: async (input) => {
const response = await generateText({
model: openai("gpt-5-mini"),
messages: [
{ role: "system", content: "You are a helpful assistant" },
...input.messages,
],
experimental_telemetry: { isEnabled: true },
instructions: "You are a helpful assistant",
messages: input.messages,
telemetry: { isEnabled: true },
});

return response.text;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* AI SDK 7 emits no spans until a telemetry integration is registered.
* Loaded once per worker as a vitest setup file.
*/
import { OpenTelemetry } from "@ai-sdk/otel";
import { registerTelemetry } from "ai";

registerTelemetry(new OpenTelemetry());
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const weatherAgent: AgentAdapter = {
messages: input.messages,
tools: { get_weather: getWeatherTool },
toolChoice: "auto",
experimental_telemetry: { isEnabled: true },
telemetry: { isEnabled: true },
});

if (response.toolCalls && response.toolCalls.length > 0) {
Expand Down
6 changes: 3 additions & 3 deletions javascript/examples/vitest/tests/llm-provider-mocking.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { openai } from "@ai-sdk/openai";
import scenario, { type AgentAdapter, AgentRole } from "@langwatch/scenario";
import { generateText, ToolSet, GenerateTextResult } from "ai";
import { generateText } from "ai";
import { describe, it, expect, vi } from "vitest";

/**
Expand Down Expand Up @@ -39,7 +39,7 @@ const chatAgent: AgentAdapter = {
const response = await generateText({
model: openai("gpt-5-mini"),
messages: input.messages,
experimental_telemetry: { isEnabled: true },
telemetry: { isEnabled: true },
});
return response.text;
},
Expand All @@ -51,7 +51,7 @@ describe("LLM Provider Mocking", () => {
// No actual LLM call will be made
mockGenerateText.mockResolvedValue({
text: "I can help you with that request.",
} as GenerateTextResult<ToolSet, never>);
} as Awaited<ReturnType<typeof generateText>>);

const result = await scenario.run({
name: "llm mock test",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,14 @@ describe("Mocked Weather Agent Tool", () => {
call: async (input) => {
const response = await generateText({
model: openai("gpt-5-mini"),
messages: [
{
role: "system",
content: `
instructions: `
You are a helpful assistant that may help the user with weather information.
Do not guess the city if they don't provide it.
`,
},
...input.messages,
],
messages: input.messages,
tools: { get_current_weather: getCurrentWeather },
toolChoice: "auto",
experimental_telemetry: { isEnabled: true },
telemetry: { isEnabled: true },
});

return response.text;
Expand Down
Loading
Loading