-
Notifications
You must be signed in to change notification settings - Fork 141
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
Open
tconley1428
wants to merge
30
commits into
main
Choose a base branch
from
ai/initial
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+7,197
−243
Open
Integration with AI SDK #1792
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
bcb181b
Initial WIP on ai-sdk integration
tconley1428 a04854e
Tools and a basic hello workflow now work
tconley1428 925bd9d
Merge remote-tracking branch 'origin/main' into ai/initial
tconley1428 d45be90
Leveraging plugin for AI SDK integration
tconley1428 c7af3f3
Some changes
tconley1428 d334684
Add experimental telemetry validation
tconley1428 a285bcc
Merge remote-tracking branch 'origin/main' into ai/initial
tconley1428 90c745a
Remove AI markdown
tconley1428 fe533f2
Linting and project structure
tconley1428 34fc1ca
Update dependencies
tconley1428 c01f8bb
Linting and project structure
tconley1428 a330d99
Fix build
tconley1428 21bc8d5
Linting
tconley1428 181727c
Docstrings
tconley1428 e7faeed
Merge branch 'main' into ai/initial
tconley1428 844be62
Clean up
tconley1428 843b112
Fix core version
tconley1428 839dabb
Revert lock changes to minimum
tconley1428 89948bd
Adding MCP and activity config support
tconley1428 fd9fbbc
Linting
tconley1428 bd5d9be
Fix error suppression location after lint
tconley1428 613f32e
Test fix of the fetch-esm CI issue
mjameswh 022d814
Bump GHA mac runners to macos15
mjameswh a83c35d
Try using latest mcp server
tconley1428 bae6997
Skip MCP test for now
tconley1428 5a89bd4
Merge remote-tracking branch 'origin/main' into ai/initial
tconley1428 5396f36
:wqMerge remote-tracking branch 'origin/main' into ai/initial
tconley1428 fafb2ac
Fix pnpm rebase
tconley1428 4a66eb1
PR Feedback
tconley1428 e3f4d70
Update lockfile
tconley1428 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ | |
| "docs": "cd packages/docs && pnpm run maybe-install-deps-and-build-docs" | ||
| }, | ||
| "dependencies": { | ||
| "@temporalio/ai-sdk": "workspace:*", | ||
| "@temporalio/client": "workspace:*", | ||
| "@temporalio/cloud": "workspace:*", | ||
| "@temporalio/common": "workspace:*", | ||
|
|
@@ -56,9 +57,9 @@ | |
| "temporalio": "file:packages/meta" | ||
| }, | ||
| "devDependencies": { | ||
| "@opentelemetry/api": "^1.7.0", | ||
| "@opentelemetry/core": "^1.19.0", | ||
| "@opentelemetry/sdk-node": "^0.46.0", | ||
| "@opentelemetry/api": "1.9.0", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| "@opentelemetry/core": "1.25.1", | ||
| "@opentelemetry/sdk-node": "0.52.1", | ||
| "@tsconfig/node18": "^18.2.4", | ||
| "@types/fs-extra": "^11.0.4", | ||
| "@types/ms": "^0.7.34", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| { | ||
| "name": "@temporalio/ai-sdk", | ||
| "version": "1.13.2", | ||
| "description": "Temporal AI SDK integration package", | ||
| "main": "lib/index.js", | ||
| "types": "./lib/index.d.ts", | ||
| "keywords": [ | ||
| "temporal", | ||
| "workflow", | ||
| "ai", | ||
| "ai-sdk", | ||
| "llm" | ||
| ], | ||
| "author": "Temporal Technologies Inc. <sdk@temporal.io>", | ||
| "license": "MIT", | ||
| "dependencies": { | ||
| "@ai-sdk/provider": "^2.0.0", | ||
| "@ai-sdk/mcp": "^0.0.8", | ||
| "@temporalio/plugin": "workspace:*", | ||
| "@temporalio/workflow": "workspace:*", | ||
| "@ungap/structured-clone": "^1.3.0", | ||
| "headers-polyfill": "^4.0.3", | ||
| "web-streams-polyfill": "^4.2.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "ai": "^5.0.91" | ||
| }, | ||
| "engines": { | ||
| "node": ">= 18.0.0" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/temporalio/sdk-typescript/issues" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/temporalio/sdk-typescript.git", | ||
| "directory": "packages/ai-sdk" | ||
| }, | ||
| "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/ai-sdk", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "files": [ | ||
| "src", | ||
| "lib" | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import { | ||
| LanguageModelV2CallOptions, | ||
| LanguageModelV2CallWarning, | ||
| LanguageModelV2Content, | ||
| LanguageModelV2FinishReason, | ||
| LanguageModelV2ResponseMetadata, | ||
| LanguageModelV2Usage, | ||
| ProviderV2, | ||
| SharedV2Headers, | ||
| SharedV2ProviderMetadata, | ||
| } from '@ai-sdk/provider'; | ||
| import type { experimental_MCPClient as MCPClient } from '@ai-sdk/mcp'; | ||
| import type { ToolCallOptions } from 'ai'; | ||
|
|
||
| export interface ListToolResult { | ||
| description?: string; | ||
| inputSchema: any; | ||
| } | ||
|
|
||
| export interface ListToolArgs { | ||
| clientArgs?: any; | ||
| } | ||
|
|
||
| export interface CallToolArgs { | ||
| clientArgs?: any; | ||
| name: string; | ||
| args: any; | ||
| options: ToolCallOptions; | ||
| } | ||
|
|
||
| /** | ||
| * Creates Temporal activities for AI model invocation using the provided AI SDK provider. | ||
| * These activities allow workflows to call AI models while maintaining Temporal's | ||
| * execution guarantees and replay safety. | ||
| * | ||
| * @param provider The AI SDK provider to use for model invocations | ||
| * @param mcpClientFactory | ||
| * @returns An object containing the activity functions | ||
| * | ||
| * @experimental The AI SDK integration is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export const createActivities = (provider: ProviderV2, mcpClientFactory?: (_: any) => Promise<MCPClient>): object => { | ||
chris-olszewski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const activities = { | ||
| async invokeModel( | ||
| modelId: string, | ||
| options: LanguageModelV2CallOptions | ||
| ): Promise<{ | ||
| content: Array<LanguageModelV2Content>; | ||
| finishReason: LanguageModelV2FinishReason; | ||
| usage: LanguageModelV2Usage; | ||
| providerMetadata?: SharedV2ProviderMetadata; | ||
| request?: { body?: unknown }; | ||
| response?: LanguageModelV2ResponseMetadata & { headers?: SharedV2Headers; body?: unknown }; | ||
| warnings: Array<LanguageModelV2CallWarning>; | ||
| }> { | ||
| const model = provider.languageModel(modelId); | ||
| return await model.doGenerate(options); | ||
| }, | ||
| }; | ||
| if (mcpClientFactory === undefined) { | ||
| return activities; | ||
| } | ||
| return { | ||
| ...activities, | ||
| async listTools(args: ListToolArgs): Promise<Record<string, ListToolResult>> { | ||
| const mcpClient = await mcpClientFactory(args.clientArgs); | ||
chris-olszewski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const tools = await mcpClient.tools(); | ||
|
|
||
| return Object.fromEntries( | ||
| Object.entries(tools).map(([k, v]) => [ | ||
| k, | ||
| { | ||
| description: v.description, | ||
| inputSchema: v.inputSchema, | ||
| }, | ||
| ]) | ||
| ); | ||
| }, | ||
| async callTool(args: CallToolArgs): Promise<any> { | ||
| const mcpClient = await mcpClientFactory(args.clientArgs); | ||
| const tools = await mcpClient.tools(); | ||
| if (!(args.name in tools)) { | ||
| throw new Error(`Tool ${args.name} not found.`); | ||
| } | ||
| return tools[args.name].execute(args.args, args.options); | ||
tconley1428 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| }; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // eslint-disable-next-line import/no-unassigned-import | ||
| import './load-polyfills'; | ||
|
|
||
| export * from './mcp'; | ||
| export * from './plugin'; | ||
| export * from './provider'; | ||
| export * from './testing'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { Headers } from 'headers-polyfill'; | ||
| import { inWorkflowContext } from '@temporalio/workflow'; | ||
|
|
||
| if (inWorkflowContext()) { | ||
| // Apply Headers polyfill | ||
| if (typeof globalThis.Headers === 'undefined') { | ||
| globalThis.Headers = Headers; | ||
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-require-imports,import/no-unassigned-import | ||
| require('web-streams-polyfill/polyfill'); | ||
| // Attach the polyfill as a Global function | ||
| if (!('structuredClone' in globalThis)) { | ||
| // eslint-disable-next-line @typescript-eslint/no-require-imports,import/no-unassigned-import | ||
| const structuredClone = require('@ungap/structured-clone'); | ||
| globalThis.structuredClone = structuredClone.default; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import type { ToolSet } from 'ai'; | ||
| import * as workflow from '@temporalio/workflow'; | ||
| import type { ActivityOptions } from '@temporalio/workflow'; | ||
| import type { ListToolResult } from './activities'; | ||
|
|
||
| export class TemporalMCPClient { | ||
| constructor( | ||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
| readonly clientArgs?: any, | ||
| readonly options?: ActivityOptions | ||
| ) {} | ||
|
|
||
| async tools(): Promise<ToolSet> { | ||
| const tools: Record<string, ListToolResult> = await workflow | ||
| .proxyActivities(this.options ?? { startToCloseTimeout: '10 minutes' }) | ||
| .listTools({ clientArgs: this.clientArgs }); | ||
| return Object.fromEntries( | ||
| Object.entries(tools).map(([toolName, toolResult]) => [ | ||
| toolName, | ||
| { | ||
| execute: async (args: any, options) => | ||
| await workflow | ||
| .proxyActivities({ | ||
| summary: toolName, | ||
| ...(this.options ?? { startToCloseTimeout: '10 minutes' }), | ||
| }) | ||
| .callTool({ name: toolName, args, options, clientArgs: this.clientArgs }), | ||
| inputSchema: { | ||
| ...toolResult.inputSchema, | ||
| _type: undefined, | ||
| validate: undefined, | ||
| [Symbol.for('vercel.ai.schema')]: true, | ||
| [Symbol.for('vercel.ai.validator')]: true, | ||
| }, | ||
| type: 'dynamic', | ||
| }, | ||
| ]) | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import type { ProviderV2 } from '@ai-sdk/provider'; | ||
| import type { experimental_MCPClient as MCPClient } from '@ai-sdk/mcp'; | ||
| import { SimplePlugin } from '@temporalio/plugin'; | ||
| import { createActivities } from './activities'; | ||
|
|
||
| export interface AiSDKPluginOptions { | ||
| modelProvider: ProviderV2; | ||
| mcpClientFactory?: (args?: any) => Promise<MCPClient>; | ||
| } | ||
|
|
||
| /** | ||
| * A Temporal plugin that integrates AI SDK providers for use in workflows. | ||
| * This plugin creates activities that allow workflows to invoke AI models. | ||
| * | ||
| * @experimental The AI SDK plugin is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export class AiSDKPlugin extends SimplePlugin { | ||
| constructor(options: AiSDKPluginOptions) { | ||
| super({ | ||
| name: 'AiSDKPlugin', | ||
| activities: createActivities(options.modelProvider, options.mcpClientFactory), | ||
| }); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { | ||
| EmbeddingModelV2, | ||
| ImageModelV2, | ||
| LanguageModelV2, | ||
| LanguageModelV2CallOptions, | ||
| LanguageModelV2CallWarning, | ||
| LanguageModelV2Content, | ||
| LanguageModelV2FinishReason, | ||
| LanguageModelV2ResponseMetadata, | ||
| LanguageModelV2Usage, | ||
| ProviderV2, | ||
| SharedV2Headers, | ||
| SharedV2ProviderMetadata, | ||
| } from '@ai-sdk/provider'; | ||
| import * as workflow from '@temporalio/workflow'; | ||
| import { ActivityOptions } from '@temporalio/workflow'; | ||
|
|
||
| /** | ||
| * A language model implementation that delegates AI model calls to Temporal activities. | ||
| * This allows workflows to invoke AI models through the Temporal execution model. | ||
| * | ||
| * @experimental The AI SDK integration is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export class TemporalLanguageModel implements LanguageModelV2 { | ||
| readonly specificationVersion = 'v2'; | ||
| readonly provider = 'temporal'; | ||
| readonly supportedUrls = {}; | ||
|
|
||
| constructor( | ||
| readonly modelId: string, | ||
| readonly options?: ActivityOptions | ||
| ) {} | ||
|
|
||
| async doGenerate(options: LanguageModelV2CallOptions): Promise<{ | ||
| content: Array<LanguageModelV2Content>; | ||
| finishReason: LanguageModelV2FinishReason; | ||
| usage: LanguageModelV2Usage; | ||
| providerMetadata?: SharedV2ProviderMetadata; | ||
| request?: { body?: unknown }; | ||
| response?: LanguageModelV2ResponseMetadata & { headers?: SharedV2Headers; body?: unknown }; | ||
| warnings: Array<LanguageModelV2CallWarning>; | ||
| }> { | ||
| const result = await workflow | ||
| .proxyActivities(this.options ?? { startToCloseTimeout: '10 minutes' }) | ||
| .invokeModel(this.modelId, options); | ||
| if (result === undefined) { | ||
| throw new Error('Received undefined response from model activity.'); | ||
| } | ||
| if (result.response !== undefined) { | ||
| result.response.timestamp = new Date(result.response.timestamp); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| doStream(_options: LanguageModelV2CallOptions): PromiseLike<{ | ||
| stream: any; | ||
| request?: { body?: unknown }; | ||
| response?: { headers?: SharedV2Headers }; | ||
| }> { | ||
| throw new Error('Streaming not supported.'); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A Temporal-specific provider implementation that creates AI models which execute | ||
| * through Temporal activities. This provider integrates AI SDK models with Temporal's | ||
| * execution model to ensure reliable, durable AI model invocations. | ||
| * | ||
| * @experimental The AI SDK integration is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export class TemporalProvider implements ProviderV2 { | ||
| constructor(readonly options?: ActivityOptions) {} | ||
|
|
||
| imageModel(_modelId: string): ImageModelV2 { | ||
| throw new Error('Not implemented'); | ||
| } | ||
|
|
||
| languageModel(modelId: string): LanguageModelV2 { | ||
| return new TemporalLanguageModel(modelId, this.options); | ||
| } | ||
|
|
||
| textEmbeddingModel(_modelId: string): EmbeddingModelV2<string> { | ||
| throw new Error('Not implemented'); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A singleton instance of TemporalProvider for convenient use in applications. | ||
| * | ||
| * @experimental The AI SDK integration is an experimental feature; APIs may change without notice. | ||
| */ | ||
| export const temporalProvider: TemporalProvider = new TemporalProvider(); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
metapackage's pacakge.json, tsconfig.json and index.ts files. These control what's included in generated API docs.