Skip to content

Commit bd21dae

Browse files
committed
fix: add agent tests and fix review
1 parent 2738797 commit bd21dae

File tree

7 files changed

+601
-36
lines changed

7 files changed

+601
-36
lines changed

src/agents/adapters.test.ts

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { WorkflowContext } from "../context";
3+
import { Client } from "@upstash/qstash";
4+
import { MOCK_QSTASH_SERVER_URL, mockQStashServer, WORKFLOW_ENDPOINT } from "../test-utils";
5+
import { getWorkflowRunId, nanoid } from "../utils";
6+
import { wrapTools } from "./adapters";
7+
import { tool } from "ai";
8+
import { z } from "zod";
9+
import { LangchainTool } from "./types";
10+
11+
describe("wrapTools", () => {
12+
const token = getWorkflowRunId();
13+
const workflowRunId = nanoid();
14+
const createContext = () =>
15+
new WorkflowContext({
16+
headers: new Headers({}) as Headers,
17+
initialPayload: "mock",
18+
qstashClient: new Client({ baseUrl: MOCK_QSTASH_SERVER_URL, token }),
19+
steps: [],
20+
url: WORKFLOW_ENDPOINT,
21+
workflowRunId,
22+
});
23+
24+
const aiSDKToolDescription = "ai sdk tool";
25+
const langChainToolDescription = "langchain sdk tool";
26+
const parameters = z.object({ expression: z.string() });
27+
const execute = async ({ expression }: { expression: string }) => expression;
28+
29+
const aiSDKTool = tool({
30+
description: aiSDKToolDescription,
31+
parameters,
32+
execute,
33+
});
34+
35+
const langChainTool: LangchainTool = {
36+
description: langChainToolDescription,
37+
schema: parameters,
38+
invoke: execute,
39+
};
40+
41+
test("should wrap AI SDK tool with execute", async () => {
42+
const context = createContext();
43+
const wrappedTools = wrapTools({ context, tools: { aiSDKTool } });
44+
45+
expect(Object.entries(wrappedTools).length).toBe(1);
46+
const wrappedTool = wrappedTools["aiSDKTool"];
47+
// @ts-expect-error description exists but can't resolve the type
48+
expect(wrappedTool.description === aiSDKToolDescription).toBeTrue();
49+
50+
await mockQStashServer({
51+
execute: () => {
52+
const execute = wrappedTool.execute;
53+
if (!execute) {
54+
throw new Error("execute is missing.");
55+
} else {
56+
const throws = () => execute({ expression: "hello" }, { messages: [], toolCallId: "id" });
57+
expect(throws).toThrowError(
58+
`Aborting workflow after executing step 'Run tool aiSDKTool'`
59+
);
60+
}
61+
},
62+
responseFields: {
63+
status: 200,
64+
body: "msgId",
65+
},
66+
receivesRequest: {
67+
method: "POST",
68+
url: `${MOCK_QSTASH_SERVER_URL}/v2/batch`,
69+
token,
70+
body: [
71+
{
72+
body: '{"stepId":1,"stepName":"Run tool aiSDKTool","stepType":"Run","out":"\\"hello\\"","concurrent":1}',
73+
destination: WORKFLOW_ENDPOINT,
74+
headers: {
75+
"content-type": "application/json",
76+
"upstash-failure-callback-retries": "3",
77+
"upstash-feature-set": "LazyFetch,InitialBody",
78+
"upstash-forward-upstash-workflow-sdk-version": "1",
79+
"upstash-method": "POST",
80+
"upstash-retries": "3",
81+
"upstash-workflow-init": "false",
82+
"upstash-workflow-runid": workflowRunId,
83+
"upstash-workflow-url": "https://requestcatcher.com/api",
84+
},
85+
},
86+
],
87+
},
88+
});
89+
});
90+
test("should wrap LangChain tool with execute", async () => {
91+
const context = createContext();
92+
const wrappedTools = wrapTools({ context, tools: { langChainTool } });
93+
94+
expect(Object.entries(wrappedTools).length).toBe(1);
95+
const wrappedTool = wrappedTools["langChainTool"];
96+
// @ts-expect-error description exists but can't resolve the type
97+
expect(wrappedTool.description === langChainToolDescription).toBeTrue();
98+
99+
await mockQStashServer({
100+
execute: () => {
101+
const execute = wrappedTool.execute;
102+
if (!execute) {
103+
throw new Error("execute is missing.");
104+
} else {
105+
const throws = () => execute({ expression: "hello" }, { messages: [], toolCallId: "id" });
106+
expect(throws).toThrowError(
107+
`Aborting workflow after executing step 'Run tool langChainTool'`
108+
);
109+
}
110+
},
111+
responseFields: {
112+
status: 200,
113+
body: "msgId",
114+
},
115+
receivesRequest: {
116+
method: "POST",
117+
url: `${MOCK_QSTASH_SERVER_URL}/v2/batch`,
118+
token,
119+
body: [
120+
{
121+
body: '{"stepId":1,"stepName":"Run tool langChainTool","stepType":"Run","out":"\\"hello\\"","concurrent":1}',
122+
destination: WORKFLOW_ENDPOINT,
123+
headers: {
124+
"content-type": "application/json",
125+
"upstash-failure-callback-retries": "3",
126+
"upstash-feature-set": "LazyFetch,InitialBody",
127+
"upstash-forward-upstash-workflow-sdk-version": "1",
128+
"upstash-method": "POST",
129+
"upstash-retries": "3",
130+
"upstash-workflow-init": "false",
131+
"upstash-workflow-runid": workflowRunId,
132+
"upstash-workflow-url": "https://requestcatcher.com/api",
133+
},
134+
},
135+
],
136+
},
137+
});
138+
});
139+
test("should wrap multiple tools", async () => {
140+
const context = createContext();
141+
const wrappedTools = wrapTools({ context, tools: { langChainTool, aiSDKTool } });
142+
143+
expect(Object.entries(wrappedTools).length).toBe(2);
144+
const wrappedLangChainTool = wrappedTools["langChainTool"];
145+
// @ts-expect-error description exists but can't resolve the type
146+
expect(wrappedLangChainTool.description === langChainToolDescription).toBeTrue();
147+
148+
const wrappedAiSDKTool = wrappedTools["aiSDKTool"];
149+
// @ts-expect-error description exists but can't resolve the type
150+
expect(wrappedAiSDKTool.description === aiSDKToolDescription).toBeTrue();
151+
});
152+
});

src/agents/adapters.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,7 @@ import { HTTPMethods } from "@upstash/qstash";
77
import { WorkflowContext } from "../context";
88
import { tool } from "ai";
99
import { AISDKTool, LangchainTool } from "./types";
10-
11-
export type ToolParams = Parameters<typeof tool>[0];
12-
13-
/**
14-
* header we pass to generateText to designate the agent name
15-
*
16-
* this allows us to access the agent name when naming the context.call step,
17-
* inside fetch implementation
18-
*/
19-
export const AGENT_NAME_HEADER = "upstash-agent-name";
10+
import { AGENT_NAME_HEADER } from "./constants";
2011

2112
/**
2213
* creates an AI SDK openai client with a custom

0 commit comments

Comments
 (0)