mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-03 18:27:09 +02:00
166 lines
4.2 KiB
TypeScript
166 lines
4.2 KiB
TypeScript
import { serializeIntermediateSteps } from '../serializeIntermediateSteps';
|
|
|
|
describe('serializeIntermediateSteps', () => {
|
|
it('should convert class instances with toJSON to plain objects', () => {
|
|
const fakeAIMessage = {
|
|
content: 'I need to call a tool',
|
|
tool_calls: [{ name: 'TestTool', args: { input: 'test' }, id: 'call_123' }],
|
|
additional_kwargs: {},
|
|
response_metadata: { model: 'gpt-4' },
|
|
id: 'msg_abc',
|
|
name: undefined,
|
|
toJSON() {
|
|
return {
|
|
lc: 1,
|
|
type: 'constructor',
|
|
id: ['langchain_core', 'messages', 'AIMessage'],
|
|
kwargs: {
|
|
content: this.content,
|
|
tool_calls: this.tool_calls,
|
|
additional_kwargs: this.additional_kwargs,
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
const steps = [
|
|
{
|
|
action: {
|
|
tool: 'TestTool',
|
|
toolInput: { input: 'test' },
|
|
log: 'Calling TestTool',
|
|
messageLog: [fakeAIMessage],
|
|
toolCallId: 'call_123',
|
|
type: 'function',
|
|
},
|
|
observation: 'Tool result',
|
|
},
|
|
];
|
|
|
|
serializeIntermediateSteps(steps);
|
|
|
|
const serializedMsg = steps[0].action.messageLog[0] as Record<string, unknown>;
|
|
|
|
// Should be a plain object, not the original class instance
|
|
expect(serializedMsg).not.toBe(fakeAIMessage);
|
|
expect(typeof serializedMsg.toJSON).toBe('undefined');
|
|
|
|
// Direct property access should work
|
|
expect(serializedMsg.content).toBe('I need to call a tool');
|
|
expect(serializedMsg.tool_calls).toEqual([
|
|
{ name: 'TestTool', args: { input: 'test' }, id: 'call_123' },
|
|
]);
|
|
expect(serializedMsg.additional_kwargs).toEqual({});
|
|
expect(serializedMsg.response_metadata).toEqual({ model: 'gpt-4' });
|
|
expect(serializedMsg.id).toBe('msg_abc');
|
|
});
|
|
|
|
it('should leave plain objects unchanged', () => {
|
|
const plainMsg = {
|
|
content: 'Hello',
|
|
tool_calls: [],
|
|
};
|
|
|
|
const steps = [
|
|
{
|
|
action: {
|
|
tool: 'TestTool',
|
|
toolInput: {},
|
|
log: '',
|
|
messageLog: [plainMsg],
|
|
toolCallId: 'call_1',
|
|
type: 'function',
|
|
},
|
|
},
|
|
];
|
|
|
|
serializeIntermediateSteps(steps);
|
|
|
|
// Should be the same reference since it has no toJSON
|
|
expect(steps[0].action.messageLog[0]).toBe(plainMsg);
|
|
});
|
|
|
|
it('should handle steps without messageLog', () => {
|
|
const steps = [
|
|
{
|
|
action: {
|
|
tool: 'TestTool',
|
|
toolInput: {},
|
|
log: '',
|
|
toolCallId: 'call_1',
|
|
type: 'function',
|
|
},
|
|
},
|
|
];
|
|
|
|
// Should not throw
|
|
expect(() =>
|
|
serializeIntermediateSteps(steps as Array<{ action: { messageLog?: unknown[] } }>),
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should handle empty steps array', () => {
|
|
const steps: Array<{ action: { messageLog?: unknown[] } }> = [];
|
|
expect(() => serializeIntermediateSteps(steps)).not.toThrow();
|
|
});
|
|
|
|
it('should include type from _getType when not an own property', () => {
|
|
const proto = {
|
|
_getType() {
|
|
return 'ai';
|
|
},
|
|
};
|
|
const fakeMsg = Object.create(proto) as Record<string, unknown>;
|
|
fakeMsg.content = 'test';
|
|
fakeMsg.toJSON = () => ({ lc: 1, type: 'constructor', kwargs: {} });
|
|
|
|
const steps = [
|
|
{
|
|
action: {
|
|
messageLog: [fakeMsg],
|
|
},
|
|
},
|
|
];
|
|
|
|
serializeIntermediateSteps(steps as Array<{ action: { messageLog?: unknown[] } }>);
|
|
|
|
const serialized = steps[0].action.messageLog[0] as Record<string, unknown>;
|
|
expect(serialized.type).toBe('ai');
|
|
expect(serialized.content).toBe('test');
|
|
});
|
|
|
|
it('should handle mixed messageLog entries', () => {
|
|
const classInstance = {
|
|
content: 'from class',
|
|
toJSON() {
|
|
return { kwargs: { content: this.content } };
|
|
},
|
|
};
|
|
const plainObject = { content: 'from plain' };
|
|
const primitiveValue = 'just a string';
|
|
|
|
const steps = [
|
|
{
|
|
action: {
|
|
messageLog: [classInstance, plainObject, primitiveValue],
|
|
},
|
|
},
|
|
];
|
|
|
|
serializeIntermediateSteps(steps as Array<{ action: { messageLog?: unknown[] } }>);
|
|
|
|
const [serializedClass, unchangedPlain, unchangedPrimitive] = steps[0].action.messageLog;
|
|
|
|
// Class instance should be serialized
|
|
expect(serializedClass).not.toBe(classInstance);
|
|
expect((serializedClass as Record<string, unknown>).content).toBe('from class');
|
|
expect(typeof (serializedClass as Record<string, unknown>).toJSON).toBe('undefined');
|
|
|
|
// Plain object should be unchanged
|
|
expect(unchangedPlain).toBe(plainObject);
|
|
|
|
// Primitive should be unchanged
|
|
expect(unchangedPrimitive).toBe(primitiveValue);
|
|
});
|
|
});
|