fix(stream-text): close after error (#192)
* feat(tests): add tool test cases and improve existing test structure * refactor(tests): remove unused imports from index.test.ts * refactor(tests): remove console.log from tool test case * fix(tests): correct text initialization in stream test case * fix(stream): correct formatting in finally block of streamText function * fix(tests): ensure text comparison is case insensitive in stream test
This commit is contained in:
1 parent
d1439dd65d
commit
5949bb6b53
5 files changed
+213
-88
No files matched your search
@@ -36,6 +36,7 @@
|
||||
"scripts": {
|
||||
"build": "pkgroll",
|
||||
"test": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -187,6 +187,9 @@ export const streamText = async (options: StreamTextOptions): Promise<StreamText
|
||||
|
||||
try {
|
||||
await trampoline(async () => startStream())
|
||||
|
||||
eventCtrl?.close()
|
||||
textCtrl?.close()
|
||||
}
|
||||
catch (err) {
|
||||
eventCtrl?.error(err)
|
||||
@@ -197,9 +200,6 @@ export const streamText = async (options: StreamTextOptions): Promise<StreamText
|
||||
resultUsage.reject(err)
|
||||
}
|
||||
finally {
|
||||
eventCtrl?.close()
|
||||
textCtrl?.close()
|
||||
|
||||
resultSteps.resolve(steps)
|
||||
resultMessages.resolve(messages)
|
||||
resultUsage.resolve(usage)
|
||||
|
||||
@@ -1,11 +1,87 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`@xsai/stream-text > basic 1`] = `Promise {}`;
|
||||
exports[`@xsai/stream-text basic > basic 1`] = `Promise {}`;
|
||||
|
||||
exports[`@xsai/stream-text > basic 2`] = `
|
||||
exports[`@xsai/stream-text basic > basic 2`] = `
|
||||
[
|
||||
{
|
||||
"text": "YES",
|
||||
"text": "Yes",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": "",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"finishReason": "stop",
|
||||
"type": "finish",
|
||||
"usage": undefined,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`@xsai/stream-text basic > stream 1`] = `Promise {}`;
|
||||
|
||||
exports[`@xsai/stream-text basic > stream 2`] = `
|
||||
[
|
||||
{
|
||||
"text": "Why",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " don",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": "'t",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " sc",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": "ient",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": "ists",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " trust",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " atoms",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": "?",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " Because",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " they",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " make",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " up",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": " everything",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
"text": "!",
|
||||
"type": "text-delta",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { clean } from '@xsai/shared'
|
||||
import { tool } from '@xsai/tool'
|
||||
import { description, object, pipe, string } from 'valibot'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { StreamTextEvent } from '../src/types/event'
|
||||
@@ -16,7 +13,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
describe('@xsai/stream-text', async () => {
|
||||
describe('@xsai/stream-text basic', async () => {
|
||||
it('basic', async () => {
|
||||
const { fullStream, steps, textStream } = await streamText({
|
||||
baseURL: 'http://localhost:11434/v1/',
|
||||
@@ -40,7 +37,7 @@ describe('@xsai/stream-text', async () => {
|
||||
for await (const t of textStream) {
|
||||
text += t
|
||||
}
|
||||
expect(text).toBe('YES')
|
||||
expect(text.toUpperCase()).toBe('YES')
|
||||
|
||||
const events: StreamTextEvent[] = []
|
||||
for await (const event of fullStream) {
|
||||
@@ -49,97 +46,35 @@ describe('@xsai/stream-text', async () => {
|
||||
expect(events).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('basic tool calls', async () => {
|
||||
const add = await tool({
|
||||
description: 'Adds two numbers',
|
||||
execute: ({ a, b }) => (Number.parseInt(a) + Number.parseInt(b)).toString(),
|
||||
name: 'add',
|
||||
parameters: object({
|
||||
a: pipe(
|
||||
string(),
|
||||
description('First number'),
|
||||
),
|
||||
b: pipe(
|
||||
string(),
|
||||
description('Second number'),
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
const { fullStream, steps } = await streamText({
|
||||
it('stream', async () => {
|
||||
const { fullStream, steps, textStream } = await streamText({
|
||||
baseURL: 'http://localhost:11434/v1/',
|
||||
maxSteps: 2,
|
||||
messages: [
|
||||
{
|
||||
content: 'You are a helpful assistant.',
|
||||
role: 'system',
|
||||
},
|
||||
{
|
||||
content: 'How many times does 114514 plus 1919810 equal? Please try to call the `add` tool to solve the problem.',
|
||||
content: 'tell me a joke',
|
||||
role: 'user',
|
||||
},
|
||||
],
|
||||
model: 'qwen3:0.6b',
|
||||
seed: 1145141919810,
|
||||
toolChoice: 'required',
|
||||
tools: [add],
|
||||
model: 'granite3.3:2b',
|
||||
seed: 114514,
|
||||
})
|
||||
|
||||
expect(steps).toMatchSnapshot()
|
||||
|
||||
const text = []
|
||||
for await (const t of textStream) {
|
||||
text.push(t)
|
||||
}
|
||||
expect(text.length).toBeGreaterThan(1)
|
||||
|
||||
const events: StreamTextEvent[] = []
|
||||
for await (const event of fullStream) {
|
||||
// eslint-disable-next-line @masknet/type-no-force-cast-via-top-type
|
||||
events.push(clean({
|
||||
...event,
|
||||
toolCallId: undefined,
|
||||
}) as unknown as StreamTextEvent)
|
||||
events.push(event)
|
||||
}
|
||||
|
||||
expect(events.find(e => e.type === 'tool-call-streaming-start')).toStrictEqual({
|
||||
toolName: 'add',
|
||||
type: 'tool-call-streaming-start',
|
||||
})
|
||||
|
||||
expect(events.find(e => e.type === 'tool-call')).toStrictEqual({
|
||||
args: '{"a":"114514","b":"1919810"}',
|
||||
toolCallType: 'function',
|
||||
toolName: 'add',
|
||||
type: 'tool-call',
|
||||
})
|
||||
|
||||
expect(events.find(e => e.type === 'tool-result')).toStrictEqual({
|
||||
args: {
|
||||
a: '114514',
|
||||
b: '1919810',
|
||||
},
|
||||
result: '2034324',
|
||||
toolName: 'add',
|
||||
type: 'tool-result',
|
||||
})
|
||||
|
||||
const allSteps = await steps
|
||||
|
||||
const cleanToolCallId = (obj: object) => clean({
|
||||
...obj,
|
||||
toolCallId: undefined,
|
||||
})
|
||||
|
||||
expect(allSteps.length).toBe(2)
|
||||
expect(allSteps[0].toolCalls.map(cleanToolCallId)).toStrictEqual([
|
||||
{
|
||||
args: '{"a":"114514","b":"1919810"}',
|
||||
toolCallType: 'function',
|
||||
toolName: 'add',
|
||||
},
|
||||
])
|
||||
expect(allSteps[0].toolResults.map(cleanToolCallId)).toStrictEqual([
|
||||
{
|
||||
args: {
|
||||
a: '114514',
|
||||
b: '1919810',
|
||||
},
|
||||
result: '2034324',
|
||||
toolName: 'add',
|
||||
},
|
||||
])
|
||||
expect(events).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,113 @@
|
||||
import { clean } from '@xsai/shared'
|
||||
import { tool } from '@xsai/tool'
|
||||
import { description, object, pipe, string } from 'valibot'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { StreamTextEvent } from '../src/types/event'
|
||||
|
||||
import { streamText } from '../src'
|
||||
|
||||
// make TS happy
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#browser_compatibility
|
||||
declare global {
|
||||
interface ReadableStream<R = any> {
|
||||
// eslint-disable-next-line ts/method-signature-style
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<R>
|
||||
}
|
||||
}
|
||||
|
||||
describe('@xsai/stream-text tool', async () => {
|
||||
it('basic tool calls', async () => {
|
||||
const add = await tool({
|
||||
description: 'Adds two numbers',
|
||||
execute: ({ a, b }) => (Number.parseInt(a) + Number.parseInt(b)).toString(),
|
||||
name: 'add',
|
||||
parameters: object({
|
||||
a: pipe(
|
||||
string(),
|
||||
description('First number'),
|
||||
),
|
||||
b: pipe(
|
||||
string(),
|
||||
description('Second number'),
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
const { fullStream, steps } = await streamText({
|
||||
baseURL: 'http://localhost:11434/v1/',
|
||||
maxSteps: 2,
|
||||
messages: [
|
||||
{
|
||||
content: 'You are a helpful assistant.',
|
||||
role: 'system',
|
||||
},
|
||||
{
|
||||
content: 'How many times does 114514 plus 1919810 equal? Please try to call the `add` tool to solve the problem.',
|
||||
role: 'user',
|
||||
},
|
||||
],
|
||||
model: 'qwen3:0.6b',
|
||||
seed: 1145141919810,
|
||||
toolChoice: 'required',
|
||||
tools: [add],
|
||||
})
|
||||
|
||||
const events: StreamTextEvent[] = []
|
||||
for await (const event of fullStream) {
|
||||
// eslint-disable-next-line @masknet/type-no-force-cast-via-top-type
|
||||
events.push(clean({
|
||||
...event,
|
||||
toolCallId: undefined,
|
||||
}) as unknown as StreamTextEvent)
|
||||
}
|
||||
|
||||
expect(events.find(e => e.type === 'tool-call-streaming-start')).toStrictEqual({
|
||||
toolName: 'add',
|
||||
type: 'tool-call-streaming-start',
|
||||
})
|
||||
|
||||
expect(events.find(e => e.type === 'tool-call')).toStrictEqual({
|
||||
args: '{"a":"114514","b":"1919810"}',
|
||||
toolCallType: 'function',
|
||||
toolName: 'add',
|
||||
type: 'tool-call',
|
||||
})
|
||||
|
||||
expect(events.find(e => e.type === 'tool-result')).toStrictEqual({
|
||||
args: {
|
||||
a: '114514',
|
||||
b: '1919810',
|
||||
},
|
||||
result: '2034324',
|
||||
toolName: 'add',
|
||||
type: 'tool-result',
|
||||
})
|
||||
|
||||
const allSteps = await steps
|
||||
|
||||
const cleanToolCallId = (obj: object) => clean({
|
||||
...obj,
|
||||
toolCallId: undefined,
|
||||
})
|
||||
|
||||
expect(allSteps.length).toBe(2)
|
||||
expect(allSteps[0].toolCalls.map(cleanToolCallId)).toStrictEqual([
|
||||
{
|
||||
args: '{"a":"114514","b":"1919810"}',
|
||||
toolCallType: 'function',
|
||||
toolName: 'add',
|
||||
},
|
||||
])
|
||||
expect(allSteps[0].toolResults.map(cleanToolCallId)).toStrictEqual([
|
||||
{
|
||||
args: {
|
||||
a: '114514',
|
||||
b: '1919810',
|
||||
},
|
||||
result: '2034324',
|
||||
toolName: 'add',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user