Skip to content

Commit 2ebb412

Browse files
author
tomfrenken
committed
add util tests
1 parent c97d4a8 commit 2ebb412

File tree

5 files changed

+243
-38
lines changed

5 files changed

+243
-38
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// test timeout & maxRetries with mocked target
File renamed without changes.

packages/langchain/src/orchestration/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from './chat.js';
1+
export * from './client.js';
22
export * from './orchestration-message.js';
33
export * from './orchestration-message-chunk.js';
44
export * from './types.js';
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import {
2+
AIMessage,
3+
HumanMessage,
4+
SystemMessage,
5+
ToolMessage
6+
} from '@langchain/core/messages';
7+
import {
8+
mapLangchainMessagesToOrchestrationMessages,
9+
mapOutputToChatResult
10+
} from './util.js';
11+
import type { OrchestrationMessage } from './orchestration-message.js';
12+
import type {
13+
CompletionPostResponse,
14+
ResponseMessageToolCall
15+
} from '@sap-ai-sdk/orchestration';
16+
17+
describe('mapLangchainMessagesToOrchestrationMessages', () => {
18+
it('should map an array of LangChain messages to Orchestration messages', () => {
19+
const langchainMessages = [
20+
new SystemMessage('System message content'),
21+
new HumanMessage('Human message content'),
22+
new AIMessage('AI message content')
23+
];
24+
25+
const result =
26+
mapLangchainMessagesToOrchestrationMessages(langchainMessages);
27+
28+
expect(result).toEqual([
29+
{ role: 'system', content: 'System message content' },
30+
{ role: 'user', content: 'Human message content' },
31+
{ role: 'assistant', content: 'AI message content' }
32+
]);
33+
});
34+
35+
it('should throw error for unsupported message types', () => {
36+
const langchainMessages = [
37+
new ToolMessage('Tool message content', 'tool-id')
38+
];
39+
40+
expect(() =>
41+
mapLangchainMessagesToOrchestrationMessages(langchainMessages)
42+
).toThrow('Unsupported message type: tool');
43+
});
44+
});
45+
46+
describe('mapBaseMessageToChatMessage', () => {
47+
it('should map HumanMessage to ChatMessage with user role', () => {
48+
const humanMessage = new HumanMessage('Human message content');
49+
50+
// Since mapBaseMessageToChatMessage is internal, we'll test it through mapLangchainMessagesToOrchestrationMessages
51+
const result = mapLangchainMessagesToOrchestrationMessages([humanMessage]);
52+
53+
expect(result[0]).toEqual({
54+
role: 'user',
55+
content: 'Human message content'
56+
});
57+
});
58+
59+
it('should map SystemMessage to ChatMessage with system role', () => {
60+
const systemMessage = new SystemMessage('System message content');
61+
62+
const result = mapLangchainMessagesToOrchestrationMessages([systemMessage]);
63+
64+
expect(result[0]).toEqual({
65+
role: 'system',
66+
content: 'System message content'
67+
});
68+
});
69+
70+
it('should map AIMessage to ChatMessage with assistant role', () => {
71+
const aiMessage = new AIMessage('AI message content');
72+
73+
const result = mapLangchainMessagesToOrchestrationMessages([aiMessage]);
74+
75+
expect(result[0]).toEqual({
76+
role: 'assistant',
77+
content: 'AI message content'
78+
});
79+
});
80+
81+
it('should throw error when mapping SystemMessage with image_url content', () => {
82+
const systemMessage = new SystemMessage({
83+
content: [
84+
{ type: 'text', text: 'System text' },
85+
{
86+
type: 'image_url',
87+
image_url: { url: 'https://example.com/image.jpg' }
88+
}
89+
]
90+
});
91+
92+
expect(() =>
93+
mapLangchainMessagesToOrchestrationMessages([systemMessage])
94+
).toThrow(
95+
'System messages with image URLs are not supported by the Orchestration Client.'
96+
);
97+
});
98+
});
99+
100+
describe('mapOutputToChatResult', () => {
101+
it('should map CompletionPostResponse to ChatResult', () => {
102+
const completionResponse: CompletionPostResponse = {
103+
orchestration_result: {
104+
id: 'test-id',
105+
object: 'chat.completion',
106+
created: 1634840000,
107+
model: 'test-model',
108+
choices: [
109+
{
110+
message: {
111+
content: 'Test content',
112+
role: 'assistant'
113+
},
114+
finish_reason: 'stop',
115+
index: 0
116+
}
117+
],
118+
usage: {
119+
completion_tokens: 10,
120+
prompt_tokens: 20,
121+
total_tokens: 30
122+
}
123+
},
124+
request_id: 'req-123',
125+
module_results: {}
126+
};
127+
128+
const result = mapOutputToChatResult(completionResponse);
129+
130+
expect(result.generations).toHaveLength(1);
131+
expect(result.generations[0].text).toBe('Test content');
132+
expect(result.generations[0].message.content).toBe('Test content');
133+
expect(result.generations[0].generationInfo).toEqual({
134+
finish_reason: 'stop',
135+
index: 0,
136+
function_call: undefined,
137+
tool_calls: undefined
138+
});
139+
expect(result.llmOutput).toEqual({
140+
created: 1634840000,
141+
id: 'test-id',
142+
model: 'test-model',
143+
object: 'chat.completion',
144+
tokenUsage: {
145+
completionTokens: 10,
146+
promptTokens: 20,
147+
totalTokens: 30
148+
}
149+
});
150+
});
151+
152+
it('should map tool_calls correctly', () => {
153+
const toolCallData: ResponseMessageToolCall = {
154+
id: 'call-123',
155+
type: 'function',
156+
function: {
157+
name: 'test_function',
158+
arguments: '{"arg1":"value1"}'
159+
}
160+
};
161+
162+
const completionResponse: CompletionPostResponse = {
163+
orchestration_result: {
164+
id: 'test-id',
165+
object: 'chat.completion',
166+
created: 1634840000,
167+
model: 'test-model',
168+
choices: [
169+
{
170+
index: 0,
171+
message: {
172+
content: 'Test content',
173+
role: 'assistant',
174+
tool_calls: [toolCallData]
175+
},
176+
finish_reason: 'tool_calls'
177+
}
178+
],
179+
usage: {
180+
completion_tokens: 10,
181+
prompt_tokens: 20,
182+
total_tokens: 30
183+
}
184+
},
185+
request_id: 'req-123',
186+
module_results: {}
187+
};
188+
189+
const result = mapOutputToChatResult(completionResponse);
190+
191+
expect(
192+
(result.generations[0].message as OrchestrationMessage).tool_calls
193+
).toEqual([
194+
{
195+
id: 'call-123',
196+
name: 'test_function',
197+
args: { arg1: 'value1' },
198+
type: 'tool_call'
199+
}
200+
]);
201+
});
202+
});

packages/langchain/src/orchestration/util.ts

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import type { ChatResult } from '@langchain/core/outputs';
33
import type {
44
ChatMessage,
55
CompletionPostResponse,
6-
Template,
7-
TemplatingModuleConfig
6+
Template
87
} from '@sap-ai-sdk/orchestration';
98
import type { ToolCall } from '@langchain/core/messages/tool';
109
import type { AzureOpenAiChatCompletionMessageToolCalls } from '@sap-ai-sdk/foundation-models';
@@ -21,7 +20,7 @@ import type {
2120
* @returns True if the object is a {@link Template}.
2221
* @internal
2322
*/
24-
export function isTemplate(object: TemplatingModuleConfig): object is Template {
23+
export function isTemplate(object: Record<string, any>): object is Template {
2524
return 'template' in object;
2625
}
2726

@@ -134,43 +133,46 @@ function mapAzureOpenAiToLangchainToolCall(
134133
export function mapOutputToChatResult(
135134
completionResponse: CompletionPostResponse
136135
): ChatResult {
136+
const { orchestration_result, module_results, request_id } =
137+
completionResponse;
138+
const { choices, created, id, model, object, usage, system_fingerprint } =
139+
orchestration_result;
137140
return {
138-
generations: completionResponse.orchestration_result.choices.map(
139-
choice => ({
140-
text: choice.message.content ?? '',
141-
message: new OrchestrationMessage(
142-
{
143-
content: choice.message.content ?? '',
144-
tool_calls: mapAzureOpenAiToLangchainToolCall(
145-
choice.message.tool_calls
146-
),
147-
additional_kwargs: {
148-
finish_reason: choice.finish_reason,
149-
index: choice.index,
150-
function_call: choice.message.function_call,
151-
tool_calls: choice.message.tool_calls
152-
}
153-
},
154-
completionResponse.module_results,
155-
completionResponse.request_id
156-
),
157-
generationInfo: {
158-
finish_reason: choice.finish_reason,
159-
index: choice.index,
160-
function_call: choice.message.function_call,
161-
tool_calls: choice.message.tool_calls
162-
}
163-
})
164-
),
141+
generations: choices.map(choice => ({
142+
text: choice.message.content ?? '',
143+
message: new OrchestrationMessage(
144+
{
145+
content: choice.message.content ?? '',
146+
tool_calls: mapAzureOpenAiToLangchainToolCall(
147+
choice.message.tool_calls
148+
),
149+
additional_kwargs: {
150+
finish_reason: choice.finish_reason,
151+
index: choice.index,
152+
function_call: choice.message.function_call,
153+
tool_calls: choice.message.tool_calls
154+
}
155+
},
156+
module_results,
157+
request_id
158+
),
159+
generationInfo: {
160+
finish_reason: choice.finish_reason,
161+
index: choice.index,
162+
function_call: choice.message.function_call,
163+
tool_calls: choice.message.tool_calls
164+
}
165+
})),
165166
llmOutput: {
166-
created: completionResponse.created,
167-
id: completionResponse.id,
168-
model: completionResponse.model,
169-
object: completionResponse.object,
167+
created,
168+
id,
169+
model,
170+
object,
171+
system_fingerprint,
170172
tokenUsage: {
171-
completionTokens: completionResponse.usage?.completion_tokens ?? 0,
172-
promptTokens: completionResponse.usage?.prompt_tokens ?? 0,
173-
totalTokens: completionResponse.usage?.total_tokens ?? 0
173+
completionTokens: usage?.completion_tokens ?? 0,
174+
promptTokens: usage?.prompt_tokens ?? 0,
175+
totalTokens: usage?.total_tokens ?? 0
174176
}
175177
}
176178
};

0 commit comments

Comments
 (0)