273 lines
8.2 KiB
TypeScript
273 lines
8.2 KiB
TypeScript
import assert from 'node:assert';
|
|
import { z } from 'zod';
|
|
import { convertArrayToReadableStream } from '../test/convert-array-to-readable-stream';
|
|
import { convertAsyncIterableToArray } from '../test/convert-async-iterable-to-array';
|
|
import { MockLanguageModelV1 } from '../test/mock-language-model-v1';
|
|
import { experimental_streamText } from './stream-text';
|
|
|
|
describe('result.textStream', () => {
|
|
it('should send text deltas', async () => {
|
|
const result = await experimental_streamText({
|
|
model: new MockLanguageModelV1({
|
|
doStream: async ({ prompt, mode }) => {
|
|
assert.deepStrictEqual(mode, { type: 'regular', tools: undefined });
|
|
assert.deepStrictEqual(prompt, [
|
|
{ role: 'user', content: [{ type: 'text', text: 'test-input' }] },
|
|
]);
|
|
|
|
return {
|
|
stream: convertArrayToReadableStream([
|
|
{ type: 'text-delta', textDelta: 'Hello' },
|
|
{ type: 'text-delta', textDelta: ', ' },
|
|
{ type: 'text-delta', textDelta: `world!` },
|
|
{
|
|
type: 'finish',
|
|
finishReason: 'stop',
|
|
usage: { completionTokens: 10, promptTokens: 3 },
|
|
},
|
|
]),
|
|
rawCall: { rawPrompt: 'prompt', rawSettings: {} },
|
|
};
|
|
},
|
|
}),
|
|
prompt: 'test-input',
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
await convertAsyncIterableToArray(result.textStream),
|
|
['Hello', ', ', 'world!'],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('result.fullStream', () => {
|
|
it('should send text deltas', async () => {
|
|
const result = await experimental_streamText({
|
|
model: new MockLanguageModelV1({
|
|
doStream: async ({ prompt, mode }) => {
|
|
assert.deepStrictEqual(mode, { type: 'regular', tools: undefined });
|
|
assert.deepStrictEqual(prompt, [
|
|
{ role: 'user', content: [{ type: 'text', text: 'test-input' }] },
|
|
]);
|
|
|
|
return {
|
|
stream: convertArrayToReadableStream([
|
|
{ type: 'text-delta', textDelta: 'Hello' },
|
|
{ type: 'text-delta', textDelta: ', ' },
|
|
{ type: 'text-delta', textDelta: `world!` },
|
|
{
|
|
type: 'finish',
|
|
finishReason: 'stop',
|
|
usage: { completionTokens: 10, promptTokens: 3 },
|
|
},
|
|
]),
|
|
rawCall: { rawPrompt: 'prompt', rawSettings: {} },
|
|
};
|
|
},
|
|
}),
|
|
prompt: 'test-input',
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
await convertAsyncIterableToArray(result.fullStream),
|
|
[
|
|
{ type: 'text-delta', textDelta: 'Hello' },
|
|
{ type: 'text-delta', textDelta: ', ' },
|
|
{ type: 'text-delta', textDelta: 'world!' },
|
|
{
|
|
type: 'finish',
|
|
finishReason: 'stop',
|
|
usage: { completionTokens: 10, promptTokens: 3, totalTokens: 13 },
|
|
},
|
|
],
|
|
);
|
|
});
|
|
|
|
it('should send tool calls', async () => {
|
|
const result = await experimental_streamText({
|
|
model: new MockLanguageModelV1({
|
|
doStream: async ({ prompt, mode }) => {
|
|
assert.deepStrictEqual(mode, {
|
|
type: 'regular',
|
|
tools: [
|
|
{
|
|
type: 'function',
|
|
name: 'tool1',
|
|
description: undefined,
|
|
parameters: {
|
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
additionalProperties: false,
|
|
properties: { value: { type: 'string' } },
|
|
required: ['value'],
|
|
type: 'object',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
assert.deepStrictEqual(prompt, [
|
|
{ role: 'user', content: [{ type: 'text', text: 'test-input' }] },
|
|
]);
|
|
|
|
return {
|
|
stream: convertArrayToReadableStream([
|
|
{
|
|
type: 'tool-call',
|
|
toolCallType: 'function',
|
|
toolCallId: 'call-1',
|
|
toolName: 'tool1',
|
|
args: `{ "value": "value" }`,
|
|
},
|
|
{
|
|
type: 'finish',
|
|
finishReason: 'stop',
|
|
usage: { completionTokens: 10, promptTokens: 3 },
|
|
},
|
|
]),
|
|
rawCall: { rawPrompt: 'prompt', rawSettings: {} },
|
|
};
|
|
},
|
|
}),
|
|
tools: {
|
|
tool1: {
|
|
parameters: z.object({ value: z.string() }),
|
|
},
|
|
},
|
|
prompt: 'test-input',
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
await convertAsyncIterableToArray(result.fullStream),
|
|
[
|
|
{
|
|
type: 'tool-call',
|
|
toolCallId: 'call-1',
|
|
toolName: 'tool1',
|
|
args: { value: 'value' },
|
|
},
|
|
{
|
|
type: 'finish',
|
|
finishReason: 'stop',
|
|
usage: { completionTokens: 10, promptTokens: 3, totalTokens: 13 },
|
|
},
|
|
],
|
|
);
|
|
});
|
|
|
|
it('should send tool results', async () => {
|
|
const result = await experimental_streamText({
|
|
model: new MockLanguageModelV1({
|
|
doStream: async ({ prompt, mode }) => {
|
|
assert.deepStrictEqual(mode, {
|
|
type: 'regular',
|
|
tools: [
|
|
{
|
|
type: 'function',
|
|
name: 'tool1',
|
|
description: undefined,
|
|
parameters: {
|
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
additionalProperties: false,
|
|
properties: { value: { type: 'string' } },
|
|
required: ['value'],
|
|
type: 'object',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
assert.deepStrictEqual(prompt, [
|
|
{ role: 'user', content: [{ type: 'text', text: 'test-input' }] },
|
|
]);
|
|
|
|
return {
|
|
stream: convertArrayToReadableStream([
|
|
{
|
|
type: 'tool-call',
|
|
toolCallType: 'function',
|
|
toolCallId: 'call-1',
|
|
toolName: 'tool1',
|
|
args: `{ "value": "value" }`,
|
|
},
|
|
{
|
|
type: 'finish',
|
|
finishReason: 'stop',
|
|
usage: { completionTokens: 10, promptTokens: 3 },
|
|
},
|
|
]),
|
|
rawCall: { rawPrompt: 'prompt', rawSettings: {} },
|
|
};
|
|
},
|
|
}),
|
|
tools: {
|
|
tool1: {
|
|
parameters: z.object({ value: z.string() }),
|
|
execute: async ({ value }) => `${value}-result`,
|
|
},
|
|
},
|
|
prompt: 'test-input',
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
await convertAsyncIterableToArray(result.fullStream),
|
|
[
|
|
{
|
|
type: 'tool-call',
|
|
toolCallId: 'call-1',
|
|
toolName: 'tool1',
|
|
args: { value: 'value' },
|
|
},
|
|
{
|
|
type: 'tool-result',
|
|
toolCallId: 'call-1',
|
|
toolName: 'tool1',
|
|
args: { value: 'value' },
|
|
result: 'value-result',
|
|
},
|
|
{
|
|
type: 'finish',
|
|
finishReason: 'stop',
|
|
usage: { completionTokens: 10, promptTokens: 3, totalTokens: 13 },
|
|
},
|
|
],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('result.toTextStreamResponse', () => {
|
|
it('should create a Response with a text stream', async () => {
|
|
const result = await experimental_streamText({
|
|
model: new MockLanguageModelV1({
|
|
doStream: async ({ prompt, mode }) => {
|
|
return {
|
|
stream: convertArrayToReadableStream([
|
|
{ type: 'text-delta', textDelta: 'Hello' },
|
|
{ type: 'text-delta', textDelta: ', ' },
|
|
{ type: 'text-delta', textDelta: 'world!' },
|
|
]),
|
|
rawCall: { rawPrompt: 'prompt', rawSettings: {} },
|
|
};
|
|
},
|
|
}),
|
|
prompt: 'test-input',
|
|
});
|
|
|
|
const response = result.toTextStreamResponse();
|
|
|
|
assert.strictEqual(response.status, 200);
|
|
assert.strictEqual(
|
|
response.headers.get('Content-Type'),
|
|
'text/plain; charset=utf-8',
|
|
);
|
|
|
|
// Read the chunks into an array
|
|
const reader = response.body!.getReader();
|
|
const chunks = [];
|
|
while (true) {
|
|
const { value, done } = await reader.read();
|
|
if (done) break;
|
|
chunks.push(new TextDecoder().decode(value));
|
|
}
|
|
|
|
assert.deepStrictEqual(chunks, ['Hello', ', ', 'world!']);
|
|
});
|
|
});
|