-
Notifications
You must be signed in to change notification settings - Fork 142
Integration with AI SDK #1792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Integration with AI SDK #1792
Conversation
chris-olszewski
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for slow review. My biggest concern is usage of any for some of the user facing types where ideally we would want to rely on the type system to prevent mismatching args.
| "@opentelemetry/api": "^1.7.0", | ||
| "@opentelemetry/core": "^1.19.0", | ||
| "@opentelemetry/sdk-node": "^0.46.0", | ||
| "@opentelemetry/api": "1.9.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we need these to have pinned versions instead of version ranges?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chris said the same, I thought I updated it. Will check again
| }; | ||
|
|
||
| const compiler = webpack(this.webpackConfigHook(options)); | ||
| const compiler = webpack(this.webpackConfigHook(options))!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any reason for the non-null assertion operator on this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have local issues with this for some reason, meant to remove it for PR.
| @@ -0,0 +1,5648 @@ | |||
| lockfileVersion: '9.0' | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be no lockfile in this directory. It's probably the result of a mistake when you rebased your branch over Chris' works on switching the repo to PNPM.
Just delete the file, and the sibling node_modules directory if you have one on your local machine, then rerun pnpm i from the root of the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
| "docs": "cd packages/docs && pnpm run maybe-install-deps-and-build-docs" | ||
| }, | ||
| "dependencies": { | ||
| "@temporalio/ai-sdk": "workspace:*", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You also need to add to the meta package's pacakge.json, tsconfig.json and index.ts files. These control what's included in generated API docs.
| "@opentelemetry/sdk-trace-base": "^1.19.0", | ||
| "@opentelemetry/semantic-conventions": "^1.19.0", | ||
| "@modelcontextprotocol/sdk": "^1.10.2", | ||
| "@opentelemetry/api": "1.9.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid pinned dependencies here?
| import { Worker } from './helpers'; | ||
| import EventType = temporal.api.enums.v1.EventType; | ||
|
|
||
| const remoteTests = !!process.env.AI_SDK_REMOTE_TESTS; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know that !!"false" is true?
Please use the following idiom instead (or look at isSet in worker-options.ts).
That will ensure that this variable follows the same thruthness conventions as our other env variables.
| const remoteTests = !!process.env.AI_SDK_REMOTE_TESTS; | |
| const remoteTests = ['1', 't', 'true'].includes((process.env.AI_SDK_REMOTE_TESTS ?? 'false').toLowerCase()), |
| }); | ||
|
|
||
| test('Hello world agent responds in haikus', async (t) => { | ||
| t.timeout(120 * 1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit worried about setting individual test timeout to 2 minutes. That means that eventual bugs that would prevent a worker from shutting down will cause the CI job to time out while running this test (which runs very early because ai starts with an a), with almost no context on what failed.
I suppose that's only pertinent when remoteTests is true, right?
And remoteTests would be false in CI, right?
If so, can we only bump up the test timeout to 2 minutes when remote tests are enabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we could do that. I don't currently have it set to run the remoteTests at all in CI.
| async invokeModel( | ||
| modelId: string, | ||
| options: LanguageModelV2CallOptions | ||
| ): Promise<{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this inline type is duplicated in doGenerate, and possibly other places. Can we extract that to a single name ddefinition?
| * | ||
| * @experimental The AI SDK integration is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export const createActivities = (provider: ProviderV2, mcpClientFactory?: (_: any) => Promise<MCPClient>): object => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few things:
- Prefer the function syntax rather than the const lambda form unless there's a reason for the later;
- Make a named type definition for
McpClientFactory. - Make the arg's type
unknownrather thanany.
| export const createActivities = (provider: ProviderV2, mcpClientFactory?: (_: any) => Promise<MCPClient>): object => { | |
| // In mcp.ts | |
| type McpClientFactory = (args: unknown) => Promise<MCPClient>; | |
| export function createActivities(provider: ProviderV2, mcpClientFactory?: McpClientFactory): object { |
| */ | ||
| export const createActivities = (provider: ProviderV2, mcpClientFactory?: (_: any) => Promise<MCPClient>): object => { | ||
| const activities = { | ||
| async invokeModel( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we not namespace those activities somehow, to avoid eventual conflicts with user defined activities, or those from other plugins? Could be something like e.g. __ai_temporal_invokeModel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't in Python, and it does come with the downside of being UI clutter since the name is shown there.
| return await model.doGenerate(options); | ||
| }, | ||
| }; | ||
| if (mcpClientFactory === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should still register the listTools and callTool activities even if there is no actual tool provided. The workflow side has no way to figure this out. listTools could return an empty array, and callTool could would throw the Tool xxcxc not found error, or even No tool registered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That brings some reminders up for me. I need to reevaluate this to allow multiple MCP servers, that will change some things.
| ) {} | ||
|
|
||
| async tools(): Promise<ToolSet> { | ||
| const tools: Record<string, ListToolResult> = await workflow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it the way you did this in Python? I mean, listTools being an activity call, which is call everytime tools() is called?
A few things that can be considered to make this more efficient:
- Cache
toolsafter the first call - Make
listToolsa local activity call instead of a regular activity; - Extract the list of tools at bundle time and inject it as a static value into the workflow bundle (so no activity call needed to list tools).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is one of the implementations we provided. Caching is a potential addition, but tools() is generally called once.
Extract the list of tools at bundle time and inject it as a static value into the workflow bundle (so no activity call needed to list tools).
I don't believe this is valid as the tool list is not known to be static.
| import { SimplePlugin } from '@temporalio/plugin'; | ||
| import { createActivities } from './activities'; | ||
|
|
||
| export interface AiSDKPluginOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need an experimental annotation here. And also some docs.
| * | ||
| * @experimental The AI SDK plugin is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export class AiSDKPlugin extends SimplePlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nil: This is inconsistent. AI and SDK are both acronyms, and so both shoujld either be written with only the first letter in capital, or both entirely in uppercase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point
| warnings: Array<LanguageModelV2CallWarning>; | ||
| }> { | ||
| const result = await workflow | ||
| .proxyActivities(this.options ?? { startToCloseTimeout: '10 minutes' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ?? here will not guarantee that startToCloseTimeout is set, as this.options could be an empty object or similar.
Might be better to do { startToCloseTimeout: '10 minutes' , ...this.options } instead.
I'd also suggest doing that in the class constructor instead of here, but no strong strong opinion on that one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think that's what I was thinking but not what I did, good call.
| @@ -0,0 +1,88 @@ | |||
| import { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| import { | |
| import type { |
| @@ -0,0 +1,92 @@ | |||
| import { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| import { | |
| import type { |
| @@ -0,0 +1,99 @@ | |||
| import { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| import { | |
| import type { |
| "author": "Temporal Technologies Inc. <sdk@temporal.io>", | ||
| "license": "MIT", | ||
| "dependencies": { | ||
| "@ai-sdk/provider": "^2.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to build the project with only type imports to the @ai/* packages, so it should be possible to lower these two dependencies to dev + peer dependencies instead of regular ones.
| * | ||
| * @experimental The AI SDK integration is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export class TestProvider implements ProviderV2 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not clear why it is pertinent for this mock to be part of our public API. That would be used in a user's tests on the worker side, not in workflow, so there's no concern regarding eventual import of non-workflow-safe code.
Doesn't the ai package itself provide this kind of test provider? Or some other external package that we could recommend instead of taking responsibility for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They have something kind of similar, although it doesn't do exactly what I needed. In Python we did expose it because OpenAI had basically no unit test support, but I could see an argument here for keeping it in test at least until there's a strong need.
mjameswh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my previous comments.
What was changed
Added a new
AiSDKPluginto support running AI SDK in workflows. The plugin registers new activities for model invocation and optionallyMCPtools. Workflows can then usetemporalProviderto create models in workflows.Why?
Checklist
Closes
How was this tested:
Added a new test suite with local model coverage.
AI_SDK_REMOTE_TESTSenv variable is available to run against a real endpoint.Any docs updates needed?