diff --git a/.changeset/crisp-animals-move.md b/.changeset/crisp-animals-move.md new file mode 100644 index 000000000..9bc568647 --- /dev/null +++ b/.changeset/crisp-animals-move.md @@ -0,0 +1,5 @@ +--- +'@sap-ai-sdk/langchain': minor +--- + +[feat] Bump langchain to v1 diff --git a/.changeset/fresh-hotels-knock.md b/.changeset/fresh-hotels-knock.md new file mode 100644 index 000000000..efbbc5257 --- /dev/null +++ b/.changeset/fresh-hotels-knock.md @@ -0,0 +1,5 @@ +--- +'@sap-ai-sdk/langchain': minor +--- + +[feat] Support auto-streaming with langchain via the `streaming` langchain-option diff --git a/package.json b/package.json index 4331ac72a..2e0c29c90 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "nock": "^14.0.10", "orval": "^7.17.0", "prettier": "^3.6.2", - "ts-jest": "^29.4.5", + "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "tsx": "^4.21.0", "typescript": "^5.9.3", diff --git a/packages/langchain/package.json b/packages/langchain/package.json index 09baa6b5b..a3c0fef9a 100644 --- a/packages/langchain/package.json +++ b/packages/langchain/package.json @@ -31,7 +31,7 @@ "lint:fix": "eslint \"**/*.ts\" --fix && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -w --log-level error" }, "dependencies": { - "@langchain/core": "^1.1.0", + "@langchain/core": "^1.1.1", "@sap-ai-sdk/ai-api": "workspace:^", "@sap-ai-sdk/core": "workspace:^", "@sap-ai-sdk/foundation-models": "workspace:^", @@ -39,5 +39,8 @@ "@sap-cloud-sdk/connectivity": "^4.2.0", "@sap-cloud-sdk/util": "^4.2.0", "uuid": "^13.0.0" + }, + "devDependencies": { + "@langchain/langgraph": "^1.0.2" } } diff --git a/packages/langchain/src/openai/__snapshots__/chat.test.ts.snap b/packages/langchain/src/openai/__snapshots__/chat.test.ts.snap index 431f0cfc5..c64d665d4 100644 --- a/packages/langchain/src/openai/__snapshots__/chat.test.ts.snap +++ b/packages/langchain/src/openai/__snapshots__/chat.test.ts.snap @@ -1,5 +1,173 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`Chat client streaming has langchain handle disabling streaming via disableStreaming flag in stream 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessage", + ], + "kwargs": { + "additional_kwargs": { + "function_call": undefined, + "tool_calls": undefined, + }, + "content": "The capital of France is Paris.", + "invalid_tool_calls": [], + "response_metadata": { + "created": undefined, + "finish_reason": undefined, + "function_call": undefined, + "id": undefined, + "index": 0, + "model": undefined, + "object": undefined, + "promptFilterResults": undefined, + "tokenUsage": { + "completionTokens": 0, + "promptTokens": 0, + "totalTokens": 0, + }, + "tool_calls": undefined, + }, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 0, + "output_tokens": 0, + "total_tokens": 0, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + +exports[`Chat client streaming supports auto-streaming responses 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessageChunk", + ], + "kwargs": { + "additional_kwargs": {}, + "content": "The capital of France is Paris.", + "id": undefined, + "invalid_tool_calls": [], + "response_metadata": { + "completion": 0, + "created": 1730125149, + "finish_reason": "stop", + "id": "chatcmpl-ANKsHIdjvozwuOGpGI6rygvwSJH0I", + "index": 0, + "model_name": "gpt-4o", + "prompt": 0, + "system_fingerprint": "fp_808245b034", + "token_usage": { + "completion_tokens": 7, + "prompt_tokens": 14, + "total_tokens": 21, + }, + }, + "tool_call_chunks": [], + "tool_calls": [], + "usage_metadata": { + "input_token_details": {}, + "input_tokens": 14, + "output_token_details": {}, + "output_tokens": 7, + "total_tokens": 21, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + +exports[`Chat client streaming supports auto-streaming responses via invoke-stream 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessageChunk", + ], + "kwargs": { + "additional_kwargs": {}, + "content": "The capital of France is Paris.", + "id": undefined, + "invalid_tool_calls": [], + "response_metadata": { + "completion": 0, + "created": 1730125149, + "finish_reason": "stop", + "id": "chatcmpl-ANKsHIdjvozwuOGpGI6rygvwSJH0I", + "index": 0, + "model_name": "gpt-4o", + "prompt": 0, + "system_fingerprint": "fp_808245b034", + "token_usage": { + "completion_tokens": 7, + "prompt_tokens": 14, + "total_tokens": 21, + }, + }, + "tool_call_chunks": [], + "tool_calls": [], + "usage_metadata": { + "input_token_details": {}, + "input_tokens": 14, + "output_token_details": {}, + "output_tokens": 7, + "total_tokens": 21, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + +exports[`Chat client streaming supports disabling auto-streaming via disableStreaming flag 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessage", + ], + "kwargs": { + "additional_kwargs": { + "function_call": undefined, + "tool_calls": undefined, + }, + "content": "The capital of France is Paris.", + "invalid_tool_calls": [], + "response_metadata": { + "created": undefined, + "finish_reason": undefined, + "function_call": undefined, + "id": undefined, + "index": 0, + "model": undefined, + "object": undefined, + "promptFilterResults": undefined, + "tokenUsage": { + "completionTokens": 0, + "promptTokens": 0, + "totalTokens": 0, + }, + "tool_calls": undefined, + }, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 0, + "output_tokens": 0, + "total_tokens": 0, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + exports[`Chat client streaming supports streaming responses 1`] = ` { "id": [ diff --git a/packages/langchain/src/openai/chat.test.ts b/packages/langchain/src/openai/chat.test.ts index 2014b4a7f..3b7c6d7f7 100644 --- a/packages/langchain/src/openai/chat.test.ts +++ b/packages/langchain/src/openai/chat.test.ts @@ -3,6 +3,12 @@ import { apiVersion } from '@sap-ai-sdk/foundation-models/internal.js'; import { toJsonSchema } from '@langchain/core/utils/json_schema'; import { getSchemaDescription } from '@langchain/core/utils/types'; import { jest } from '@jest/globals'; +import { + START, + END, + MessagesAnnotation, + StateGraph +} from '@langchain/langgraph'; import { addNumbersTool, joke } from '../../../../test-util/tools.js'; import { mockClientCredentialsGrantCall, @@ -332,6 +338,195 @@ describe('Chat client', () => { expect(finalOutput).toMatchSnapshot(); }); + it('supports auto-streaming responses', async () => { + mockInference( + { + data: { + messages: [ + { + role: 'user' as const, + content: 'What is the capital of France?' + } + ], + stream: true, + stream_options: { + include_usage: true + } + } + }, + { + data: mockResponseStream, + status: 200 + }, + endpoint + ); + jest.spyOn(AzureOpenAiChatClient.prototype, '_streamResponseChunks'); + + client.streaming = true; + expect(client.streaming).toBe(true); + + const finalOutput = await client.invoke('What is the capital of France?'); + + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).toHaveBeenCalled(); + }); + + it('supports auto-streaming responses via invoke-stream', async () => { + mockInference( + { + data: { + messages: [ + { + role: 'user' as const, + content: 'What is the capital of France?' + } + ], + stream: true, + stream_options: { + include_usage: true + } + } + }, + { + data: mockResponseStream, + status: 200 + }, + endpoint + ); + jest.spyOn(AzureOpenAiChatClient.prototype, '_streamResponseChunks'); + + const finalOutput = await client.invoke( + 'What is the capital of France?', + { + stream: true + } + ); + + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).toHaveBeenCalled(); + }); + + it('supports disabling auto-streaming via disableStreaming flag', async () => { + mockInference( + { + data: { + messages: [ + { + role: 'user' as const, + content: 'What is the capital of France?' + } + ] + } + }, + { + data: { + choices: [ + { + message: { + role: 'assistant', + content: 'The capital of France is Paris.' + }, + index: 0 + } + ] + }, + status: 200 + }, + endpoint + ); + jest.spyOn(AzureOpenAiChatClient.prototype, '_streamResponseChunks'); + + client.streaming = true; + client.disableStreaming = true; + + const finalOutput = await client.invoke('What is the capital of France?'); + + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).not.toHaveBeenCalled(); + }); + + it('has langchain handle disabling streaming via disableStreaming flag in stream', async () => { + mockInference( + { + data: { + messages: [ + { + role: 'user' as const, + content: 'What is the capital of France?' + } + ] + } + }, + { + data: { + choices: [ + { + message: { + role: 'assistant', + content: 'The capital of France is Paris.' + }, + index: 0 + } + ] + }, + status: 200 + }, + endpoint + ); + jest.spyOn(AzureOpenAiChatClient.prototype, '_streamResponseChunks'); + + client.disableStreaming = true; + client.streaming = true; + + const stream = await client.stream('What is the capital of France?'); + + let finalOutput: AIMessageChunk | undefined; + for await (const chunk of stream) { + finalOutput = finalOutput ? finalOutput.concat(chunk) : chunk; + } + + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).not.toHaveBeenCalled(); + }); + + it('should handle streaming and disabling streaming flags as expected', async () => { + let testClient = new AzureOpenAiChatClient({ + modelName: 'gpt-4o', + streaming: true, + disableStreaming: true + }); + + // streaming should be disabled due to disableStreaming being true + expect(testClient.streaming).toBe(false); + expect(testClient.disableStreaming).toBe(true); + + testClient = new AzureOpenAiChatClient({ + modelName: 'gpt-4o', + streaming: false + }); + + // streaming should be disabled + expect(testClient.streaming).toBe(false); + expect(testClient.disableStreaming).toBe(true); + + testClient = new AzureOpenAiChatClient({ + modelName: 'gpt-4o', + streaming: true + }); + + // auto-streaming should be enabled + expect(testClient.streaming).toBe(true); + expect(testClient.disableStreaming).toBe(false); + + testClient = new AzureOpenAiChatClient({ + modelName: 'gpt-4o' + }); + + // auto-streaming and disable-streaming should be disabled by default + expect(testClient.streaming).toBe(false); + expect(testClient.disableStreaming).toBe(false); + }); + it('streams and aborts with a signal', async () => { mockInference( { @@ -406,5 +601,58 @@ describe('Chat client', () => { }); expect(finalOutput).toMatchSnapshot(); }); + it('streams when invoked in a streaming langgraph', async () => { + mockInference( + { + data: { + messages: [ + { + role: 'user', + content: 'Hello!' + } + ], + stream: true, + stream_options: { + include_usage: true + } + } + }, + { + data: mockResponseStream, + status: 200 + }, + endpoint + ); + jest.spyOn(AzureOpenAiChatClient.prototype, '_streamResponseChunks'); + // Simulate a minimal streaming langgraph-like workflow + const llm = new AzureOpenAiChatClient({ modelName: 'gpt-4o' }); + + // Simulate a node function that calls the model using invoke + const callModel = async (state: { messages: any }) => { + const messages = await llm.invoke(state.messages); + return { messages }; + }; + + // Define a new graph + const workflow = new StateGraph(MessagesAnnotation) + // Define the (single) node in the graph + .addNode('model', callModel) + .addEdge(START, 'model') + .addEdge('model', END); + + const app = workflow.compile(); + const stream = await app.stream( + { messages: [{ role: 'user', content: 'Hello!' }] }, + // langgraph will only enable streaming in a granular streaming mode + { streamMode: 'messages' as const } + ); + + let finalOutput; + for await (const chunk of stream) { + finalOutput = + finalOutput !== undefined ? finalOutput.concat(chunk) : chunk; + } + expect(llm._streamResponseChunks).toHaveBeenCalled(); + }); }); }); diff --git a/packages/langchain/src/openai/chat.ts b/packages/langchain/src/openai/chat.ts index 855438c92..87a95044b 100644 --- a/packages/langchain/src/openai/chat.ts +++ b/packages/langchain/src/openai/chat.ts @@ -55,6 +55,8 @@ export class AzureOpenAiChatClient extends BaseChatModel { + // If both streaming and disableStreaming are set, disable streaming + if ((options?.stream ?? this.streaming) && !this.disableStreaming) { + let generation; + const stream = this._streamResponseChunks(messages, options, runManager); + for await (const chunk of stream) { + generation = + generation === undefined ? chunk : generation.concat(chunk); + } + if (generation === undefined) { + throw new Error('No chunks were generated from the stream.'); + } + return { generations: [generation] }; + } + const res = await this.caller.callWithOptions( { signal: options.signal diff --git a/packages/langchain/src/openai/types.ts b/packages/langchain/src/openai/types.ts index dba68d0fb..6b7ccd707 100644 --- a/packages/langchain/src/openai/types.ts +++ b/packages/langchain/src/openai/types.ts @@ -35,6 +35,13 @@ export type AzureOpenAiChatModelParams = Pick< * If `undefined` the `strict` argument will not be passed to OpenAI. */ supportsStrictToolCalling?: boolean; + /** + * Whether the model should stream all results. + * If {@link disableStreaming} is set to `true`, this option will be ignored. + * If {@link streaming} is explicitly set to `false`, {@link disableStreaming} will be set to `true`. + * Defaults to `false`. + */ + streaming?: boolean; } & BaseChatModelParams & ModelConfig & ResourceGroupConfig; @@ -63,6 +70,7 @@ export type AzureOpenAiChatCallOptions = BaseChatModelCallOptions & | 'function_call' > & { strict?: boolean; + stream?: boolean; tools?: ChatAzureOpenAIToolType[]; promptIndex?: number; requestConfig?: CustomRequestConfig; diff --git a/packages/langchain/src/orchestration/__snapshots__/client.test.ts.snap b/packages/langchain/src/orchestration/__snapshots__/client.test.ts.snap index 15bb600d9..fc9b54a8c 100644 --- a/packages/langchain/src/orchestration/__snapshots__/client.test.ts.snap +++ b/packages/langchain/src/orchestration/__snapshots__/client.test.ts.snap @@ -78,6 +78,286 @@ exports[`orchestration service client resilience returns successful response whe } `; +exports[`orchestration service client streaming does not stream when disableStreaming is set to true 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessage", + ], + "kwargs": { + "additional_kwargs": {}, + "content": "Hello! How can I assist you today?", + "invalid_tool_calls": [], + "response_metadata": { + "created": 1754390060, + "finish_reason": "stop", + "id": "chatcmpl-C19HolLlkUltFBAMq4Jdgi4dMUFKg", + "index": 0, + "model": "gpt-4o-2024-08-06", + "object": "chat.completion", + "request_id": "903367ba-f7b6-42a5-857f-8cff615e201b", + "system_fingerprint": "fp_ee1d74bde0", + "tokenUsage": { + "completionTokens": 10, + "promptTokens": 9, + "totalTokens": 19, + }, + "tool_calls": undefined, + }, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 9, + "output_tokens": 10, + "total_tokens": 19, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + +exports[`orchestration service client streaming has langchain handle disabling streaming via disableStreaming flag in stream 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessage", + ], + "kwargs": { + "additional_kwargs": {}, + "content": "Hello! How can I assist you today?", + "invalid_tool_calls": [], + "response_metadata": { + "created": 1754390060, + "finish_reason": "stop", + "id": "chatcmpl-C19HolLlkUltFBAMq4Jdgi4dMUFKg", + "index": 0, + "model": "gpt-4o-2024-08-06", + "object": "chat.completion", + "request_id": "903367ba-f7b6-42a5-857f-8cff615e201b", + "system_fingerprint": "fp_ee1d74bde0", + "tokenUsage": { + "completionTokens": 10, + "promptTokens": 9, + "totalTokens": 19, + }, + "tool_calls": undefined, + }, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 9, + "output_tokens": 10, + "total_tokens": 19, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + +exports[`orchestration service client streaming supports auto-streaming responses 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessageChunk", + ], + "kwargs": { + "additional_kwargs": { + "intermediate_results": { + "llm": { + "choices": [ + { + "delta": { + "content": "The SAP Cloud SDK is a comprehensive development toolkit designed to simplify and accelerate the creation of applications that integrate with SAP solutions, particularly those built on the SAP Business Technology Platform (BTP). It provides developers with libraries, tools, and best practices that streamline the process of connecting to SAP systems, such as S/4HANA and other services available on the SAP Cloud Platform. + +Key features of the SAP Cloud SDK include: + +1. **Simplified Connectivity**: The SDK offers pre-built libraries to easily interact with SAP services, providing capabilities for authentication, service consumption, and OData/REST client generation. + +2. **Multi-cloud Support**: It supports multiple cloud environments, ensuring that applications remain flexible and can be deployed across various cloud providers. + +3. **Best Practices and Guidelines**: The SDK includes best practices for development, ensuring high-quality, scalable, and maintainable code. + +4. **Project Scaffolding and Code Samples**: Developers can quickly start their projects using provided templates and samples, accelerating the development process and reducing the learning curve. + +5. **Extensive Documentation and Community Support**: Ample documentation, tutorials, and an active community help developers overcome challenges and adopt the SDK efficiently. + +Overall, the SAP Cloud SDK is an essential tool for developers looking to build cloud-native applications and extensions that seamlessly integrate with SAP's enterprise solutions.", + "role": "assistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistant", + }, + "finish_reason": "stop", + "index": 0, + }, + ], + "created": 1734524005, + "id": "chatcmpl-AfnDZfYvuE4SDplaLGF9v0PJjB0wp", + "model": "gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06", + "object": "chat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunk", + "system_fingerprint": "fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48", + "usage": { + "completion_tokens": 271, + "prompt_tokens": 17, + "total_tokens": 288, + }, + }, + "templating": [ + { + "content": "Give me a short introduction of SAP Cloud SDK.", + "role": "user", + }, + ], + }, + }, + "content": "The SAP Cloud SDK is a comprehensive development toolkit designed to simplify and accelerate the creation of applications that integrate with SAP solutions, particularly those built on the SAP Business Technology Platform (BTP). It provides developers with libraries, tools, and best practices that streamline the process of connecting to SAP systems, such as S/4HANA and other services available on the SAP Cloud Platform. + +Key features of the SAP Cloud SDK include: + +1. **Simplified Connectivity**: The SDK offers pre-built libraries to easily interact with SAP services, providing capabilities for authentication, service consumption, and OData/REST client generation. + +2. **Multi-cloud Support**: It supports multiple cloud environments, ensuring that applications remain flexible and can be deployed across various cloud providers. + +3. **Best Practices and Guidelines**: The SDK includes best practices for development, ensuring high-quality, scalable, and maintainable code. + +4. **Project Scaffolding and Code Samples**: Developers can quickly start their projects using provided templates and samples, accelerating the development process and reducing the learning curve. + +5. **Extensive Documentation and Community Support**: Ample documentation, tutorials, and an active community help developers overcome challenges and adopt the SDK efficiently. + +Overall, the SAP Cloud SDK is an essential tool for developers looking to build cloud-native applications and extensions that seamlessly integrate with SAP's enterprise solutions.", + "id": undefined, + "invalid_tool_calls": [], + "response_metadata": { + "completion": 0, + "created": 1734524005, + "finish_reason": "stop", + "id": "chatcmpl-AfnDZfYvuE4SDplaLGF9v0PJjB0wp", + "model_name": "gpt-4o-2024-08-06", + "prompt": 0, + "request_id": "66172762-8c47-4438-89e7-2689be8f370b", + "system_fingerprint": "fp_4e924a4b48", + "token_usage": { + "completion_tokens": 271, + "prompt_tokens": 17, + "total_tokens": 288, + }, + }, + "tool_call_chunks": [], + "tool_calls": [], + "usage_metadata": { + "input_token_details": {}, + "input_tokens": 17, + "output_token_details": {}, + "output_tokens": 271, + "total_tokens": 288, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + +exports[`orchestration service client streaming supports auto-streaming responses with invoke-stream 1`] = ` +{ + "id": [ + "langchain_core", + "messages", + "AIMessageChunk", + ], + "kwargs": { + "additional_kwargs": { + "intermediate_results": { + "llm": { + "choices": [ + { + "delta": { + "content": "The SAP Cloud SDK is a comprehensive development toolkit designed to simplify and accelerate the creation of applications that integrate with SAP solutions, particularly those built on the SAP Business Technology Platform (BTP). It provides developers with libraries, tools, and best practices that streamline the process of connecting to SAP systems, such as S/4HANA and other services available on the SAP Cloud Platform. + +Key features of the SAP Cloud SDK include: + +1. **Simplified Connectivity**: The SDK offers pre-built libraries to easily interact with SAP services, providing capabilities for authentication, service consumption, and OData/REST client generation. + +2. **Multi-cloud Support**: It supports multiple cloud environments, ensuring that applications remain flexible and can be deployed across various cloud providers. + +3. **Best Practices and Guidelines**: The SDK includes best practices for development, ensuring high-quality, scalable, and maintainable code. + +4. **Project Scaffolding and Code Samples**: Developers can quickly start their projects using provided templates and samples, accelerating the development process and reducing the learning curve. + +5. **Extensive Documentation and Community Support**: Ample documentation, tutorials, and an active community help developers overcome challenges and adopt the SDK efficiently. + +Overall, the SAP Cloud SDK is an essential tool for developers looking to build cloud-native applications and extensions that seamlessly integrate with SAP's enterprise solutions.", + "role": "assistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistant", + }, + "finish_reason": "stop", + "index": 0, + }, + ], + "created": 1734524005, + "id": "chatcmpl-AfnDZfYvuE4SDplaLGF9v0PJjB0wp", + "model": "gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06gpt-4o-2024-08-06", + "object": "chat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunkchat.completion.chunk", + "system_fingerprint": "fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48fp_4e924a4b48", + "usage": { + "completion_tokens": 271, + "prompt_tokens": 17, + "total_tokens": 288, + }, + }, + "templating": [ + { + "content": "Give me a short introduction of SAP Cloud SDK.", + "role": "user", + }, + ], + }, + }, + "content": "The SAP Cloud SDK is a comprehensive development toolkit designed to simplify and accelerate the creation of applications that integrate with SAP solutions, particularly those built on the SAP Business Technology Platform (BTP). It provides developers with libraries, tools, and best practices that streamline the process of connecting to SAP systems, such as S/4HANA and other services available on the SAP Cloud Platform. + +Key features of the SAP Cloud SDK include: + +1. **Simplified Connectivity**: The SDK offers pre-built libraries to easily interact with SAP services, providing capabilities for authentication, service consumption, and OData/REST client generation. + +2. **Multi-cloud Support**: It supports multiple cloud environments, ensuring that applications remain flexible and can be deployed across various cloud providers. + +3. **Best Practices and Guidelines**: The SDK includes best practices for development, ensuring high-quality, scalable, and maintainable code. + +4. **Project Scaffolding and Code Samples**: Developers can quickly start their projects using provided templates and samples, accelerating the development process and reducing the learning curve. + +5. **Extensive Documentation and Community Support**: Ample documentation, tutorials, and an active community help developers overcome challenges and adopt the SDK efficiently. + +Overall, the SAP Cloud SDK is an essential tool for developers looking to build cloud-native applications and extensions that seamlessly integrate with SAP's enterprise solutions.", + "id": undefined, + "invalid_tool_calls": [], + "response_metadata": { + "completion": 0, + "created": 1734524005, + "finish_reason": "stop", + "id": "chatcmpl-AfnDZfYvuE4SDplaLGF9v0PJjB0wp", + "model_name": "gpt-4o-2024-08-06", + "prompt": 0, + "request_id": "66172762-8c47-4438-89e7-2689be8f370b", + "system_fingerprint": "fp_4e924a4b48", + "token_usage": { + "completion_tokens": 271, + "prompt_tokens": 17, + "total_tokens": 288, + }, + }, + "tool_call_chunks": [], + "tool_calls": [], + "usage_metadata": { + "input_token_details": {}, + "input_tokens": 17, + "output_token_details": {}, + "output_tokens": 271, + "total_tokens": 288, + }, + }, + "lc": 1, + "type": "constructor", +} +`; + exports[`orchestration service client streaming supports streaming responses 1`] = ` { "id": [ diff --git a/packages/langchain/src/orchestration/client.test.ts b/packages/langchain/src/orchestration/client.test.ts index a84c828c0..a43a48558 100644 --- a/packages/langchain/src/orchestration/client.test.ts +++ b/packages/langchain/src/orchestration/client.test.ts @@ -1,6 +1,12 @@ import { constructCompletionPostRequest } from '@sap-ai-sdk/orchestration/internal.js'; import { jest } from '@jest/globals'; import nock from 'nock'; +import { + START, + END, + MessagesAnnotation, + StateGraph +} from '@langchain/langgraph'; import { type AIMessageChunk } from '@langchain/core/messages'; import { mockClientCredentialsGrantCall, @@ -389,6 +395,198 @@ describe('orchestration service client', () => { expect(finalOutput).toMatchSnapshot(); }); + it('supports auto-streaming responses', async () => { + mockInference( + { + data: constructCompletionPostRequest( + { + ...config, + promptTemplating: { + ...config.promptTemplating, + prompt: { + template: messages + } + } + }, + { messages: [] }, + true + ) + }, + { + data: mockResponseStream, + status: 200 + }, + endpoint + ); + + jest.spyOn(OrchestrationClient.prototype, '_streamResponseChunks'); + + const client = new OrchestrationClient(config, { + streaming: true + } as any); + expect(client.streaming).toBe(true); + + const finalOutput = await client.invoke([ + { role: 'user', content: 'Hello!' } + ]); + + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).toHaveBeenCalled(); + }); + + it('supports auto-streaming responses with invoke-stream', async () => { + mockInference( + { + data: constructCompletionPostRequest( + { + ...config, + promptTemplating: { + ...config.promptTemplating, + prompt: { + template: messages + } + } + }, + { messages: [] }, + true + ) + }, + { + data: mockResponseStream, + status: 200 + }, + endpoint + ); + + jest.spyOn(OrchestrationClient.prototype, '_streamResponseChunks'); + + const client = new OrchestrationClient(config); + + const finalOutput = await client.invoke( + [{ role: 'user', content: 'Hello!' }], + { stream: true } + ); + + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).toHaveBeenCalled(); + }); + + it('does not stream when disableStreaming is set to true', async () => { + mockInference( + { + data: constructCompletionPostRequest( + { + ...config, + promptTemplating: { + ...config.promptTemplating, + prompt: { + template: messages + } + } + }, + { messages: [] }, + false + ) + }, + { + data: mockResponse, + status: 200 + }, + endpoint + ); + + jest.spyOn(OrchestrationClient.prototype, '_streamResponseChunks'); + + const client = new OrchestrationClient(config, { + streaming: true, + disableStreaming: true + } as any); + expect(client.streaming).toBe(false); + expect(client.disableStreaming).toBe(true); + client.streaming = true; // try to override + + const finalOutput = await client.invoke([ + { role: 'user', content: 'Hello!' } + ]); + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).not.toHaveBeenCalled(); + }); + + it('has langchain handle disabling streaming via disableStreaming flag in stream', async () => { + mockInference( + { + data: constructCompletionPostRequest( + { + ...config, + promptTemplating: { + ...config.promptTemplating, + prompt: { + template: messages + } + } + }, + { messages: [] }, + false + ) + }, + { + data: mockResponse, + status: 200 + }, + endpoint + ); + + jest.spyOn(OrchestrationClient.prototype, '_streamResponseChunks'); + + const client = new OrchestrationClient(config, { + streaming: true, + disableStreaming: true + } as any); + expect(client.streaming).toBe(false); + expect(client.disableStreaming).toBe(true); + + const stream = await client.stream('Hello!'); + let finalOutput: AIMessageChunk | undefined; + + for await (const chunk of stream) { + finalOutput = finalOutput ? finalOutput.concat(chunk) : chunk; + } + expect(finalOutput).toMatchSnapshot(); + expect(client._streamResponseChunks).not.toHaveBeenCalled(); + }); + + it('should handle streaming and disabling streaming flags as expected', async () => { + let testClient = new OrchestrationClient(config, { + streaming: true, + disableStreaming: true + } as any); + + // streaming should be disabled due to disableStreaming being true + expect(testClient.streaming).toBe(false); + expect(testClient.disableStreaming).toBe(true); + + testClient = new OrchestrationClient(config, { + streaming: false + } as any); + + // streaming should be disabled + expect(testClient.streaming).toBe(false); + expect(testClient.disableStreaming).toBe(true); + + testClient = new OrchestrationClient(config, { + streaming: true + } as any); + + // auto-streaming should be enabled + expect(testClient.streaming).toBe(true); + expect(testClient.disableStreaming).toBe(false); + + testClient = new OrchestrationClient(config); + // auto-streaming and disableStreaming should be disabled by default + expect(testClient.streaming).toBe(false); + expect(testClient.disableStreaming).toBe(false); + }); + it('streams and aborts with a signal', async () => { mockInference( { @@ -513,4 +711,63 @@ describe('orchestration service client', () => { expect(finalOutput).toMatchSnapshot(); }); }); + + it('streams when invoked in a streaming langgraph', async () => { + mockInference( + { + data: constructCompletionPostRequest( + { + ...config, + promptTemplating: { + ...config.promptTemplating, + prompt: { + template: messages + } + } + }, + { messages: [] }, + true + ) + }, + { + data: mockResponseStream, + status: 200 + }, + endpoint + ); + jest.spyOn(OrchestrationClient.prototype, '_streamResponseChunks'); + + const llm = new OrchestrationClient(config); + + // Define the function that calls the model + const callModel = async (state: typeof MessagesAnnotation.State) => { + const response = await llm.invoke(state.messages); + // Update message history with response: + return { messages: response }; + }; + + // Define a new graph + const workflow = new StateGraph(MessagesAnnotation) + // Define the (single) node in the graph + .addNode('model', callModel) + .addEdge(START, 'model') + .addEdge('model', END); + + const app = workflow.compile(); + const stream = await app.stream( + { + messages + }, + // langgraph will only enable streaming in a granular streaming mode + { streamMode: 'messages' as const } + ); + + let finalOutput; + for await (const chunk of stream) { + finalOutput = + finalOutput !== undefined ? finalOutput.concat(chunk) : chunk; + } + + expect(llm._streamResponseChunks).toHaveBeenCalled(); + }); }); diff --git a/packages/langchain/src/orchestration/client.ts b/packages/langchain/src/orchestration/client.ts index 41e241872..b4810b196 100644 --- a/packages/langchain/src/orchestration/client.ts +++ b/packages/langchain/src/orchestration/client.ts @@ -38,6 +38,9 @@ export class OrchestrationClient extends BaseChatModel< OrchestrationCallOptions, OrchestrationMessageChunk > { + streaming: boolean; + disableStreaming: boolean; + constructor( public orchestrationConfig: LangChainOrchestrationModuleConfig, public langchainOptions: BaseChatModelParams = {}, @@ -54,6 +57,17 @@ export class OrchestrationClient extends BaseChatModel< }; super(langchainOptions); + + this.disableStreaming = langchainOptions?.disableStreaming ?? false; + // Todo: Extend BaseChatModelParams? + this.streaming = + ((langchainOptions as { streaming?: boolean })?.streaming ?? false) && + // disable streaming has higher priority + !this.disableStreaming; + // if streaming is false, disable streaming + if ((langchainOptions as { streaming?: boolean })?.streaming === false) { + this.disableStreaming = true; + } } _llmType(): string { @@ -85,6 +99,19 @@ export class OrchestrationClient extends BaseChatModel< options: typeof this.ParsedCallOptions, runManager?: CallbackManagerForLLMRun ): Promise { + if ((options?.stream ?? this.streaming) && !this.disableStreaming) { + let generation; + const stream = this._streamResponseChunks(messages, options, runManager); + for await (const chunk of stream) { + generation = + generation === undefined ? chunk : generation.concat(chunk); + } + if (generation === undefined) { + throw new Error('No chunks were generated from the stream.'); + } + return { generations: [generation] }; + } + const { placeholderValues, customRequestConfig } = options; const allMessages = mapLangChainMessagesToOrchestrationMessages(messages); const mergedOrchestrationConfig = this.mergeOrchestrationConfig(options); diff --git a/packages/langchain/src/orchestration/types.ts b/packages/langchain/src/orchestration/types.ts index cb6de1c01..191321c25 100644 --- a/packages/langchain/src/orchestration/types.ts +++ b/packages/langchain/src/orchestration/types.ts @@ -36,6 +36,7 @@ export type OrchestrationCallOptions = Pick< > & { customRequestConfig?: CustomRequestConfig; strict?: boolean; + stream?: boolean; tools?: ChatOrchestrationToolType[]; promptIndex?: number; placeholderValues?: Record; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6fbe519d..77db1bb00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,8 +86,8 @@ importers: specifier: ^3.6.2 version: 3.7.3 ts-jest: - specifier: ^29.4.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + specifier: ^29.4.6 + version: 29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) @@ -155,8 +155,8 @@ importers: packages/langchain: dependencies: '@langchain/core': - specifier: ^1.1.0 - version: 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + specifier: ^1.1.1 + version: 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) '@sap-ai-sdk/ai-api': specifier: workspace:^ version: link:../ai-api @@ -178,6 +178,10 @@ importers: uuid: specifier: ^13.0.0 version: 13.0.0 + devDependencies: + '@langchain/langgraph': + specifier: ^1.0.2 + version: 1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) packages/orchestration: dependencies: @@ -225,14 +229,14 @@ importers: specifier: workspace:^ version: link:../packages/orchestration '@sap/cds': - specifier: ^9.4.4 - version: 9.4.4(@eslint/js@9.39.1)(express@5.1.0) + specifier: ^9.5.1 + version: 9.5.1(@eslint/js@9.39.1)(express@5.2.1) '@sap/xssec': specifier: ^4.11.2 version: 4.11.2 express: - specifier: ^5.1.0 - version: 5.1.0 + specifier: ^5.2.1 + version: 5.2.1 devDependencies: '@sap/cds-dk': specifier: ^9.4.3 @@ -242,19 +246,19 @@ importers: dependencies: '@langchain/classic': specifier: ^1.0.5 - version: 1.0.5(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(ws@8.18.3) + version: 1.0.5(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(ws@8.18.3) '@langchain/core': - specifier: ^1.1.0 - version: 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + specifier: ^1.1.1 + version: 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) '@langchain/langgraph': specifier: ^1.0.2 - version: 1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) + version: 1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) '@langchain/mcp-adapters': - specifier: ^1.0.1 - version: 1.0.1(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(@langchain/langgraph@1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13)) + specifier: ^1.0.2 + version: 1.0.2(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(@langchain/langgraph@1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13)) '@langchain/textsplitters': specifier: ^1.0.1 - version: 1.0.1(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) + version: 1.0.1(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) '@modelcontextprotocol/sdk': specifier: ^1.23.0 version: 1.23.0(@cfworker/json-schema@4.1.1)(zod@4.1.13) @@ -280,11 +284,14 @@ importers: specifier: ^4.2.0 version: 4.2.0 '@types/express': - specifier: ^5.0.5 - version: 5.0.5 + specifier: ^5.0.6 + version: 5.0.6 express: - specifier: ^5.1.0 - version: 5.1.0 + specifier: ^5.2.1 + version: 5.2.1 + langchain: + specifier: ^1.1.1 + version: 1.1.1(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(zod-to-json-schema@3.25.0(zod@4.1.13)) uuid: specifier: ^13.0.0 version: 13.0.0 @@ -323,17 +330,17 @@ importers: tests/smoke-tests: dependencies: '@langchain/core': - specifier: ^1.1.0 - version: 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + specifier: ^1.1.1 + version: 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) '@langchain/langgraph': specifier: ^1.0.2 - version: 1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) + version: 1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) '@langchain/mcp-adapters': - specifier: ^1.0.1 - version: 1.0.1(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(@langchain/langgraph@1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13)) + specifier: ^1.0.2 + version: 1.0.2(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(@langchain/langgraph@1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13)) '@langchain/textsplitters': specifier: ^1.0.1 - version: 1.0.1(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) + version: 1.0.1(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) '@sap-ai-sdk/ai-api': specifier: canary version: 2.3.1-20251202013332.0 @@ -356,11 +363,11 @@ importers: specifier: ^4.2.0 version: 4.2.0 express: - specifier: ^5.1.0 - version: 5.1.0 + specifier: ^5.2.1 + version: 5.2.1 langchain: - specifier: ^1.1.1 - version: 1.1.1(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(zod-to-json-schema@3.25.0(zod@4.1.13)) + specifier: ^1.1.2 + version: 1.1.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(zod-to-json-schema@3.25.0(zod@4.1.13)) uuid: specifier: ^13.0.0 version: 13.0.0 @@ -369,8 +376,8 @@ importers: version: 4.1.13 devDependencies: '@types/express': - specifier: ^5.0.5 - version: 5.0.5 + specifier: ^5.0.6 + version: 5.0.6 dotenv: specifier: ^17.2.3 version: 17.2.3 @@ -1244,8 +1251,8 @@ packages: typeorm: optional: true - '@langchain/core@1.1.0': - resolution: {integrity: sha512-yJ6JHcU9psjnQbzRFkXjIdNTA+3074dA+2pHdH8ewvQCSleSk6JcjkCMIb5+NASjeMoi1ZuntlLKVsNqF38YxA==} + '@langchain/core@1.1.1': + resolution: {integrity: sha512-vdUoj2CVbb+0Qszi8llP34vdUCfP7bfA9VoFr4Se1pFGu7VAPnk8lBnRat9IvqSxMfTvOHJSd7Rn6TUPjzKsnA==} engines: {node: '>=20'} '@langchain/langgraph-checkpoint@1.0.0': @@ -1279,8 +1286,8 @@ packages: zod-to-json-schema: optional: true - '@langchain/mcp-adapters@1.0.1': - resolution: {integrity: sha512-sBEtVzG4TifY4itWXXoR0n+rxcFx0rnCfmHnHa5bKJJPByG3kuBNWaq3xd+lNmzlJ2RGujmWU5skKWOrRXfx2g==} + '@langchain/mcp-adapters@1.0.2': + resolution: {integrity: sha512-UPbutXCt57qmC8gvr9z8Du+wMap4V6OYOnxRlWHcKxuF3YmZdqF2Z/N2V7tC3QfwG8waYsnkRNJlKBtby2Qzag==} engines: {node: '>=20.10.0'} peerDependencies: '@langchain/core': ^1.0.0 @@ -1452,8 +1459,8 @@ packages: peerDependencies: '@sap/cds': ^9 - '@sap/cds@9.4.4': - resolution: {integrity: sha512-JJCHeEJF4xzFyZSf2ToocvVE9dyHfNLTRXOauOxlmpfyaLg97G7Qp+L4bD132eB0onBG9bQj3eH8DzBm0hVvIw==} + '@sap/cds@9.5.1': + resolution: {integrity: sha512-rMvDSRytjqYQolB0pg8tiBlpS9kKGcleRhpZmBGUmSncbbwnotKYTKoDyMCWkflS8P9/Jq9YfY1qhK+fduHCVA==} engines: {node: '>=20'} hasBin: true peerDependencies: @@ -1663,8 +1670,8 @@ packages: '@types/express-serve-static-core@5.1.0': resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} - '@types/express@5.0.5': - resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -1693,9 +1700,6 @@ packages: '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/minimatch@3.0.5': resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} @@ -1732,14 +1736,11 @@ packages: '@types/retry@0.12.5': resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==} - '@types/send@0.17.6': - resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} - '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} - '@types/serve-static@1.15.10': - resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -2164,8 +2165,8 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + body-parser@2.2.1: + resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} engines: {node: '>=18'} brace-expansion@1.1.12: @@ -2859,8 +2860,8 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} extendable-error@0.1.7: @@ -3202,10 +3203,6 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - iconv-lite@0.7.0: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} @@ -3684,6 +3681,12 @@ packages: peerDependencies: '@langchain/core': 1.1.0 + langchain@1.1.2: + resolution: {integrity: sha512-5ChHmMDEA6EO4DdW33Bf3sbZ2zYzXHmD+BE9VuxznCnzBkoBkPY5Ly2Au2oyaQr7PyEFRSZHkHZhhEqQc4slVQ==} + engines: {node: '>=20'} + peerDependencies: + '@langchain/core': 1.1.1 + langsmith@0.3.81: resolution: {integrity: sha512-NFmp7TDrrbCE6TIfHqutN9xhdgvx0EOhULVo8bDW+ib5idprwjMTvmS0S1n9uVFwjN03zU2zVEWViXnwy5XPrw==} peerDependencies: @@ -4826,8 +4829,8 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-jest@29.4.5: - resolution: {integrity: sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==} + ts-jest@29.4.6: + resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5423,25 +5426,25 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@cap-js/asyncapi@1.0.3(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))': + '@cap-js/asyncapi@1.0.3(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))': dependencies: - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@5.1.0) + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@5.2.1) - '@cap-js/db-service@2.6.0(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))': + '@cap-js/db-service@2.6.0(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))': dependencies: - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@5.1.0) + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@5.2.1) generic-pool: 3.9.0 optional: true - '@cap-js/openapi@1.2.3(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))': + '@cap-js/openapi@1.2.3(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))': dependencies: - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@5.1.0) + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@5.2.1) pluralize: 8.0.0 - '@cap-js/sqlite@2.0.4(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))': + '@cap-js/sqlite@2.0.4(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))': dependencies: - '@cap-js/db-service': 2.6.0(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0)) - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@5.1.0) + '@cap-js/db-service': 2.6.0(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1)) + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@5.2.1) better-sqlite3: 12.4.6 optional: true @@ -6124,11 +6127,11 @@ snapshots: dependencies: jsep: 1.4.0 - '@langchain/classic@1.0.5(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(ws@8.18.3)': + '@langchain/classic@1.0.5(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(ws@8.18.3)': dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) - '@langchain/openai': 1.1.3(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(ws@8.18.3) - '@langchain/textsplitters': 1.0.1(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/openai': 1.1.3(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(ws@8.18.3) + '@langchain/textsplitters': 1.0.1(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) handlebars: 4.7.8 js-yaml: 4.1.1 jsonpointer: 5.0.1 @@ -6146,7 +6149,7 @@ snapshots: - openai - ws - '@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))': + '@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))': dependencies: '@cfworker/json-schema': 4.1.1 ansi-styles: 5.2.0 @@ -6165,24 +6168,24 @@ snapshots: - '@opentelemetry/sdk-trace-base' - openai - '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))': + '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))': dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) uuid: 10.0.0 - '@langchain/langgraph-sdk@1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))': + '@langchain/langgraph-sdk@1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))': dependencies: p-queue: 6.6.2 p-retry: 4.6.2 uuid: 9.0.1 optionalDependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) - '@langchain/langgraph@1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13)': + '@langchain/langgraph@1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13)': dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) - '@langchain/langgraph-sdk': 1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) + '@langchain/langgraph-sdk': 1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) uuid: 10.0.0 zod: 4.1.13 optionalDependencies: @@ -6191,10 +6194,10 @@ snapshots: - react - react-dom - '@langchain/mcp-adapters@1.0.1(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(@langchain/langgraph@1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13))': + '@langchain/mcp-adapters@1.0.2(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(@langchain/langgraph@1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13))': dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) - '@langchain/langgraph': 1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/langgraph': 1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) '@modelcontextprotocol/sdk': 1.23.0(@cfworker/json-schema@4.1.1)(zod@4.1.13) debug: 4.4.3 zod: 4.1.13 @@ -6204,18 +6207,18 @@ snapshots: - '@cfworker/json-schema' - supports-color - '@langchain/openai@1.1.3(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(ws@8.18.3)': + '@langchain/openai@1.1.3(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(ws@8.18.3)': dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) js-tiktoken: 1.0.21 openai: 6.9.1(ws@8.18.3)(zod@4.1.13) zod: 4.1.13 transitivePeerDependencies: - ws - '@langchain/textsplitters@1.0.1(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))': + '@langchain/textsplitters@1.0.1(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))': dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) js-tiktoken: 1.0.21 '@manypkg/find-root@1.1.0': @@ -6243,8 +6246,8 @@ snapshots: cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 - express: 5.1.0 - express-rate-limit: 7.5.1(express@5.1.0) + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) pkce-challenge: 5.0.1 raw-body: 3.0.2 zod: 4.1.13 @@ -6460,7 +6463,7 @@ snapshots: '@sap-ai-sdk/langchain@2.3.1-20251202013332.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))': dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) '@sap-ai-sdk/ai-api': 2.3.1-20251202013332.0 '@sap-ai-sdk/core': 2.3.1-20251202013332.0 '@sap-ai-sdk/foundation-models': 2.3.1-20251202013332.0 @@ -6603,10 +6606,10 @@ snapshots: '@sap/cds-dk@9.4.3(@eslint/js@9.39.1)': dependencies: - '@cap-js/asyncapi': 1.0.3(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0)) - '@cap-js/openapi': 1.2.3(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0)) - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@4.21.2) - '@sap/cds-mtxs': 3.4.4(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))(hdb@0.19.12) + '@cap-js/asyncapi': 1.0.3(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1)) + '@cap-js/openapi': 1.2.3(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1)) + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@4.21.2) + '@sap/cds-mtxs': 3.4.4(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))(hdb@0.19.12) '@sap/hdi-deploy': 5.5.1(hdb@0.19.12) axios: 1.13.2 express: 4.21.2 @@ -6618,7 +6621,7 @@ snapshots: xml-js: 1.6.11 yaml: 2.8.2 optionalDependencies: - '@cap-js/sqlite': 2.0.4(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0)) + '@cap-js/sqlite': 2.0.4(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1)) transitivePeerDependencies: - '@eslint/js' - '@sap/hana-client' @@ -6628,42 +6631,42 @@ snapshots: - tar - utf-8-validate - '@sap/cds-fiori@2.1.1(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))(express@4.21.2)': + '@sap/cds-fiori@2.1.1(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))(express@4.21.2)': dependencies: - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@5.1.0) + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@5.2.1) express: 4.21.2 - '@sap/cds-fiori@2.1.1(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))(express@5.1.0)': + '@sap/cds-fiori@2.1.1(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))(express@5.2.1)': dependencies: - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@5.1.0) - express: 5.1.0 + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@5.2.1) + express: 5.2.1 - '@sap/cds-mtxs@3.4.4(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))(hdb@0.19.12)': + '@sap/cds-mtxs@3.4.4(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))(hdb@0.19.12)': dependencies: - '@sap/cds': 9.4.4(@eslint/js@9.39.1)(express@5.1.0) + '@sap/cds': 9.5.1(@eslint/js@9.39.1)(express@5.2.1) '@sap/hdi-deploy': 5.5.1(hdb@0.19.12) transitivePeerDependencies: - '@sap/hana-client' - hdb - supports-color - '@sap/cds@9.4.4(@eslint/js@9.39.1)(express@4.21.2)': + '@sap/cds@9.5.1(@eslint/js@9.39.1)(express@4.21.2)': dependencies: '@eslint/js': 9.39.1 '@sap/cds-compiler': 6.4.6 - '@sap/cds-fiori': 2.1.1(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))(express@4.21.2) + '@sap/cds-fiori': 2.1.1(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))(express@4.21.2) js-yaml: 4.1.1 optionalDependencies: express: 4.21.2 - '@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0)': + '@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1)': dependencies: '@eslint/js': 9.39.1 '@sap/cds-compiler': 6.4.6 - '@sap/cds-fiori': 2.1.1(@sap/cds@9.4.4(@eslint/js@9.39.1)(express@5.1.0))(express@5.1.0) + '@sap/cds-fiori': 2.1.1(@sap/cds@9.5.1(@eslint/js@9.39.1)(express@5.2.1))(express@5.2.1) js-yaml: 4.1.1 optionalDependencies: - express: 5.1.0 + express: 5.2.1 '@sap/hdi-deploy@5.5.1(hdb@0.19.12)': dependencies: @@ -6986,11 +6989,11 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 1.2.1 - '@types/express@5.0.5': + '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 '@types/express-serve-static-core': 5.1.0 - '@types/serve-static': 1.15.10 + '@types/serve-static': 2.2.0 '@types/hast@3.0.4': dependencies: @@ -7022,8 +7025,6 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 22.19.1 - '@types/mime@1.3.5': {} - '@types/minimatch@3.0.5': {} '@types/minimist@1.2.5': {} @@ -7052,20 +7053,14 @@ snapshots: '@types/retry@0.12.5': {} - '@types/send@0.17.6': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 22.19.1 - '@types/send@1.2.1': dependencies: '@types/node': 22.19.1 - '@types/serve-static@1.15.10': + '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 '@types/node': 22.19.1 - '@types/send': 0.17.6 '@types/stack-utils@2.0.3': {} @@ -7538,13 +7533,13 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.2.0: + body-parser@2.2.1: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 4.4.3 http-errors: 2.0.1 - iconv-lite: 0.6.3 + iconv-lite: 0.7.0 on-finished: 2.4.1 qs: 6.14.0 raw-body: 3.0.2 @@ -8334,9 +8329,9 @@ snapshots: jest-mock: 30.2.0 jest-util: 30.2.0 - express-rate-limit@7.5.1(express@5.1.0): + express-rate-limit@7.5.1(express@5.2.1): dependencies: - express: 5.1.0 + express: 5.2.1 express@4.21.2: dependencies: @@ -8374,15 +8369,16 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.1.0: + express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.0 + body-parser: 2.2.1 content-disposition: 1.0.1 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 debug: 4.4.3 + depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -8768,10 +8764,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - iconv-lite@0.7.0: dependencies: safer-buffer: 2.1.2 @@ -9415,11 +9407,28 @@ snapshots: kuler@2.0.0: {} - langchain@1.1.1(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(zod-to-json-schema@3.25.0(zod@4.1.13)): + langchain@1.1.1(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(zod-to-json-schema@3.25.0(zod@4.1.13)): + dependencies: + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/langgraph': 1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) + langsmith: 0.3.81(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + uuid: 10.0.0 + zod: 4.1.13 + transitivePeerDependencies: + - '@opentelemetry/api' + - '@opentelemetry/exporter-trace-otlp-proto' + - '@opentelemetry/sdk-trace-base' + - openai + - react + - react-dom + - zod-to-json-schema + + langchain@1.1.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(openai@6.9.1(ws@8.18.3)(zod@4.1.13))(zod-to-json-schema@3.25.0(zod@4.1.13)): dependencies: - '@langchain/core': 1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) - '@langchain/langgraph': 1.0.2(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.0(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) + '@langchain/core': 1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) + '@langchain/langgraph': 1.0.2(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13)))(zod-to-json-schema@3.25.0(zod@4.1.13))(zod@4.1.13) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.1(openai@6.9.1(ws@8.18.3)(zod@4.1.13))) langsmith: 0.3.81(openai@6.9.1(ws@8.18.3)(zod@4.1.13)) uuid: 10.0.0 zod: 4.1.13 @@ -10639,7 +10648,7 @@ snapshots: dependencies: typescript: 5.9.3 - ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 diff --git a/sample-cap/package.json b/sample-cap/package.json index 30942fed7..a660ccc51 100644 --- a/sample-cap/package.json +++ b/sample-cap/package.json @@ -9,9 +9,9 @@ "@sap-ai-sdk/ai-api": "workspace:^", "@sap-ai-sdk/foundation-models": "workspace:^", "@sap-ai-sdk/orchestration": "workspace:^", - "express": "^5.1.0", + "express": "^5.2.1", "@sap/xssec": "^4.11.2", - "@sap/cds": "^9.4.4" + "@sap/cds": "^9.5.1" }, "devDependencies": { "@sap/cds-dk": "^9.4.3" diff --git a/sample-code/package.json b/sample-code/package.json index 08b3c369f..374377411 100644 --- a/sample-code/package.json +++ b/sample-code/package.json @@ -22,9 +22,9 @@ }, "dependencies": { "@langchain/classic": "^1.0.5", - "@langchain/core": "^1.1.0", + "@langchain/core": "^1.1.1", "@langchain/langgraph": "^1.0.2", - "@langchain/mcp-adapters": "^1.0.1", + "@langchain/mcp-adapters": "^1.0.2", "@langchain/textsplitters": "^1.0.1", "@modelcontextprotocol/sdk": "^1.23.0", "@sap-ai-sdk/ai-api": "workspace:^", @@ -34,8 +34,9 @@ "@sap-ai-sdk/orchestration": "workspace:^", "@sap-ai-sdk/prompt-registry": "workspace:^", "@sap-cloud-sdk/util": "^4.2.0", - "@types/express": "^5.0.5", - "express": "^5.1.0", + "@types/express": "^5.0.6", + "express": "^5.2.1", + "langchain": "^1.1.1", "uuid": "^13.0.0", "zod": "^4.1.13" } diff --git a/sample-code/src/index.ts b/sample-code/src/index.ts index d539c30aa..26a5a5a10 100644 --- a/sample-code/src/index.ts +++ b/sample-code/src/index.ts @@ -28,6 +28,7 @@ export { } from './orchestration.js'; export { invoke, + invokeWithStreaming, invokeWithStructuredOutputJsonSchema, invokeWithStructuredOutputToolCalling, invokeChain, @@ -35,7 +36,9 @@ export { } from './langchain-azure-openai.js'; export { invokeChain as orchestrationInvokeChain, + invokeChainWithStreaming as orchestrationInvokeChainWithStreaming, invokeLangGraphChain, + invokeLangGraphChainStream, streamChain } from './langchain-orchestration.js'; export { diff --git a/sample-code/src/langchain-azure-openai.ts b/sample-code/src/langchain-azure-openai.ts index 986dc6a03..747968ab5 100644 --- a/sample-code/src/langchain-azure-openai.ts +++ b/sample-code/src/langchain-azure-openai.ts @@ -45,6 +45,29 @@ export async function invoke(): Promise { return parser.invoke(response); } +/** + * Ask GPT about the capital of France, using streaming internally. + * @returns The answer from GPT. + */ +export async function invokeWithStreaming(): Promise { + // initialize client with options + const client = new AzureOpenAiChatClient({ + modelName: 'gpt-4o', + max_tokens: 1000, + temperature: 0.7, + streaming: true + }); + + // invoke a prompt + const response = await client.invoke('What is the capital of France?'); + + // create an output parser + const parser = new StringOutputParser(); + + // parse the response + return parser.invoke(response); +} + /** * Ask GPT about the capital of France, as part of a chain. * @returns The answer from ChatGPT. diff --git a/sample-code/src/langchain-orchestration.ts b/sample-code/src/langchain-orchestration.ts index 00355b4a6..bdf6545ed 100644 --- a/sample-code/src/langchain-orchestration.ts +++ b/sample-code/src/langchain-orchestration.ts @@ -50,6 +50,33 @@ export async function invokeChain(): Promise { ]); } +/** + * Ask GPT about an introduction to SAP Cloud SDK. + * Internally uses streaming, despite using the non-streaming invoke method. + * @returns The answer from ChatGPT. + */ +export async function invokeChainWithStreaming(): Promise { + const orchestrationConfig: LangChainOrchestrationModuleConfig = { + // define the language model to be used + promptTemplating: { + model: { + name: 'gpt-4o' + } + } + }; + + return new OrchestrationClient(orchestrationConfig, { + streaming: true + } as any) + .pipe(new StringOutputParser()) + .invoke([ + { + role: 'user', + content: 'Tell me about SAP Cloud SDK' + } + ]); +} + /** * Trigger input content filter. * @returns The answer from ChatGPT. @@ -113,11 +140,7 @@ export async function invokeChainWithOutputFilter(): Promise { ]); } -/** - * Invoke the model with memory. - * @returns The answer from ChatGPT. - */ -export async function invokeLangGraphChain(): Promise { +function createLangGraphApp() { const orchestrationConfig: LangChainOrchestrationModuleConfig = { // define the language model to be used promptTemplating: { @@ -144,7 +167,15 @@ export async function invokeLangGraphChain(): Promise { // Add memory const memory = new MemorySaver(); - const app = workflow.compile({ checkpointer: memory }); + return workflow.compile({ checkpointer: memory }); +} + +/** + * Invoke the model with memory. + * @returns The answer from ChatGPT. + */ +export async function invokeLangGraphChain(): Promise { + const app = createLangGraphApp(); const config = { configurable: { thread_id: uuidv4() } }; const input = [ @@ -169,6 +200,54 @@ export async function invokeLangGraphChain(): Promise { return `${firstResponse}\n\n${secondResponse}`; } +/** + * Stream responses using LangGraph Orchestration client. + * @returns The answer from ChatGPT. + */ +export async function invokeLangGraphChainStream(): Promise { + const app = createLangGraphApp(); + + const config = { + configurable: { thread_id: uuidv4() }, + streamMode: 'messages' as const + }; + const input = [ + { + role: 'user', + content: 'Tell me something about the SAP Cloud SDK' + } + ]; + const stream = await app.stream({ messages: input }, config); + + let firstResponse; + for await (const chunk of stream) { + firstResponse = + firstResponse === undefined ? chunk : firstResponse.concat(chunk); + } + const firstResponseStr = firstResponse! + .map(chunk => chunk?.content ?? '') + .join(''); + + const input2 = [ + { + role: 'user', + content: 'What is special about it? Tell me in 3 sentences!' + } + ]; + const stream2 = await app.stream({ messages: input2 }, config); + + let secondResponse; + for await (const chunk of stream2) { + secondResponse = + secondResponse === undefined ? chunk : secondResponse.concat(chunk); + } + const secondResponseStr = secondResponse! + .map(chunk => chunk?.content ?? '') + .join(''); + + return `${firstResponseStr}\n\n${secondResponseStr}`; +} + /** * Stream responses using LangChain Orchestration client. * @param controller - The abort controller to cancel the request if needed. diff --git a/tests/e2e-tests/src/open-ai-langchain.test.ts b/tests/e2e-tests/src/open-ai-langchain.test.ts index 4655cba0f..1ad85deca 100644 --- a/tests/e2e-tests/src/open-ai-langchain.test.ts +++ b/tests/e2e-tests/src/open-ai-langchain.test.ts @@ -2,7 +2,8 @@ import { invoke, invokeChain, invokeRagChain, - invokeWithStructuredOutputJsonSchema + invokeWithStructuredOutputJsonSchema, + invokeWithStreaming } from '@sap-ai-sdk/sample-code'; import { loadEnv } from './utils/load-env.js'; @@ -14,6 +15,11 @@ describe('LangChain OpenAI Access', () => { expect(result).toContain('Paris'); }); + it('executes a basic invoke with streaming', async () => { + const result = await invokeWithStreaming(); + expect(result).toContain('Paris'); + }); + it('executes invoke as part of a chain ', async () => { const result = await invokeChain(); expect(result).toContain('Paris'); diff --git a/tests/e2e-tests/src/orchestration-langchain.test.ts b/tests/e2e-tests/src/orchestration-langchain.test.ts index 96ac1ea6c..6df13dd24 100644 --- a/tests/e2e-tests/src/orchestration-langchain.test.ts +++ b/tests/e2e-tests/src/orchestration-langchain.test.ts @@ -1,6 +1,8 @@ import { orchestrationInvokeChain, - invokeLangGraphChain + orchestrationInvokeChainWithStreaming, + invokeLangGraphChain, + invokeLangGraphChainStream } from '@sap-ai-sdk/sample-code'; import { loadEnv } from './utils/load-env.js'; @@ -12,8 +14,18 @@ describe('Orchestration LangChain client', () => { expect(result).toContain('SAP Cloud SDK'); }); + it('executes invoke as part of a chain with streaming', async () => { + const result = await orchestrationInvokeChainWithStreaming(); + expect(result).toContain('SAP Cloud SDK'); + }); + it('executes an invoke with LangGraph', async () => { const result = await invokeLangGraphChain(); expect(result).toContain('SAP Cloud SDK'); }); + + it('executes an stream with LangGraph', async () => { + const result = await invokeLangGraphChainStream(); + expect(result).toContain('SAP Cloud SDK'); + }); }); diff --git a/tests/smoke-tests/package.json b/tests/smoke-tests/package.json index 1b6f26656..d0695aa04 100644 --- a/tests/smoke-tests/package.json +++ b/tests/smoke-tests/package.json @@ -15,9 +15,9 @@ "lint:fix": "eslint . --fix && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -w --log-level error" }, "dependencies": { - "@langchain/core": "^1.1.0", + "@langchain/core": "^1.1.1", "@langchain/langgraph": "^1.0.2", - "@langchain/mcp-adapters": "^1.0.1", + "@langchain/mcp-adapters": "^1.0.2", "@langchain/textsplitters": "^1.0.1", "@sap-ai-sdk/ai-api": "canary", "@sap-ai-sdk/document-grounding": "canary", @@ -26,13 +26,13 @@ "@sap-ai-sdk/orchestration": "canary", "@sap-ai-sdk/prompt-registry": "canary", "@sap-cloud-sdk/util": "^4.2.0", - "express": "^5.1.0", - "langchain": "^1.1.1", + "express": "^5.2.1", + "langchain": "^1.1.2", "uuid": "^13.0.0", "zod": "^4.1.12" }, "devDependencies": { - "@types/express": "^5.0.5", + "@types/express": "^5.0.6", "dotenv": "^17.2.3", "dotenv-cli": "^11.0.0" },