Skip to content

Commit

Permalink
feat: new package stream-object (#18)
Browse files Browse the repository at this point in the history
* feat: new package `stream-object`

* chore(xsai): update exports

* chore(stream-object): fix test name

* refactor(generate-object): de-duplication
  • Loading branch information
kwaa authored Jan 8, 2025
1 parent c7b21e9 commit 880dc06
Show file tree
Hide file tree
Showing 9 changed files with 3,878 additions and 0 deletions.
50 changes: 50 additions & 0 deletions packages/stream-object/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@xsai/stream-object",
"version": "0.0.24",
"type": "module",
"author": "Moeru AI",
"license": "MIT",
"homepage": "https://xsai.js.org",
"description": "extra-small AI SDK for Browser, Node.js, Deno, Bun or Edge Runtime.",
"keywords": [
"xsai",
"openai",
"ai"
],
"repository": {
"type": "git",
"url": "git+https://github.com/moeru-ai/xsai.git",
"directory": "packages/stream-object"
},
"bugs": "https://github.com/moeru-ai/xsai/issues",
"sideEffects": false,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "pkgroll",
"build:watch": "pkgroll --watch",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@typeschema/main": "catalog:",
"@xsai/shared": "workspace:",
"@xsai/stream-text": "workspace:"
},
"devDependencies": {
"@gcornut/valibot-json-schema": "catalog:",
"@xsai/providers": "workspace:",
"best-effort-json-parser": "^1.1.2",
"type-fest": "^4.31.0",
"valibot": "catalog:"
}
}
68 changes: 68 additions & 0 deletions packages/stream-object/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { PartialDeep } from 'type-fest'

import { type Infer, type Schema, toJSONSchema } from '@typeschema/main'
import { clean } from '@xsai/shared'
import { streamText, type StreamTextOptions, type StreamTextResult } from '@xsai/stream-text'
import { parse } from 'best-effort-json-parser'

export interface GenerateObjectOptions<T extends Schema> extends StreamTextOptions {
schema: T
schemaDescription?: string
schemaName?: string
}

export interface GenerateObjectResult<T extends Schema> extends StreamTextResult {
partialObjectStream: ReadableStream<PartialDeep<Infer<T>>>
}

/** @experimental WIP */
export const streamObject = async <T extends Schema>(options: GenerateObjectOptions<T>): Promise<GenerateObjectResult<T>> =>
await streamText({
...options,
response_format: {
json_schema: {
description: options.schemaDescription,
name: options.schemaName ?? 'json_schema',
schema: await toJSONSchema(options.schema)
.then(json => clean({
...json,
$schema: undefined,
})),
strict: true,
},
type: 'json_schema',
},
schema: undefined,
schemaDescription: undefined,
schemaName: undefined,
}).then(({ chunkStream, finishReason, textStream: rawTextStream, usage }) => {
const [textStream, rawPartialObjectStream] = rawTextStream.tee()

let partialObjectData = ''
let partialObject = {}
const partialObjectStream = rawPartialObjectStream.pipeThrough(new TransformStream({
transform: (chunk, controller) => {
partialObjectData += chunk
try {
const data = parse(partialObjectData)

if (JSON.stringify(partialObject) !== JSON.stringify(data)) {
partialObject = data
controller.enqueue(data)
}
}
// TODO: maybe handle
catch {}
},
}))

return {
chunkStream,
finishReason,
partialObjectStream,
textStream,
usage,
}
})

export default streamObject
Loading

0 comments on commit 880dc06

Please sign in to comment.