| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- import { afterEach, describe, expect, it } from 'vitest'
- import { Context } from 'cordis'
- import Loader from '@cordisjs/plugin-loader'
- import Include from '@cordisjs/plugin-include'
- import { mkdtemp, rm, writeFile } from 'node:fs/promises'
- import { tmpdir } from 'node:os'
- import { join } from 'node:path'
- import { pathToFileURL } from 'node:url'
- import LlmService, { createUserMessage, LlmAdapter } from '@deepseek-ai/dsh-llm'
- import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
- import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
- import SessionTitleService from '@deepseek-ai/dsh-session-title'
- import * as providerPlugin from '@deepseek-ai/dsh-session-title-first-message-llm'
- let root: string | undefined
- let context: Context | undefined
- class LoaderAdapter extends LlmAdapter {
- readonly requests: GenerateOptions[] = []
- override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
- this.requests.push(options)
- yield { type: 'text-delta', index: 0, text: 'Loader composed title' }
- yield { type: 'finish', reason: { kind: 'stop' } }
- }
- }
- afterEach(async () => {
- await context?.fiber.dispose()
- context = undefined
- if (root !== undefined) await rm(root, { recursive: true, force: true })
- root = undefined
- })
- async function loadComposition(): Promise<Context> {
- root = await mkdtemp(join(tmpdir(), 'dsh-title-loader-'))
- const configPath = join(root, 'cordis.yml')
- await writeFile(configPath, [
- "- name: '@deepseek-ai/dsh-llm'",
- "- name: '@deepseek-ai/dsh-session'",
- "- name: '@deepseek-ai/dsh-session-title'",
- ' config:',
- ' fallbackMaxWords: 5',
- ' fallbackMaxBytes: 40',
- ' maxTitleBytes: 80',
- "- name: '@deepseek-ai/dsh-session-title-first-message-llm'",
- ' config:',
- ' targetWords: 5',
- ' targetCjkCharacters: 10',
- ' maxInputBytes: 1000',
- ' maxOutputTokens: 32',
- ' timeoutMs: 1000',
- " provider: 'title-route'",
- " model: 'title-model'",
- '',
- ].join('\n'))
- context = new Context()
- context.baseUrl = pathToFileURL(root).href + '/'
- await context.plugin(Loader)
- context.loader.builtins.include = Include
- const modules = new Map<string, unknown>([
- ['@deepseek-ai/dsh-llm', LlmService],
- ['@deepseek-ai/dsh-session', SessionStore],
- ['@deepseek-ai/dsh-session-title', SessionTitleService],
- ['@deepseek-ai/dsh-session-title-first-message-llm', providerPlugin],
- ])
- context.loader.internal = {
- version: 'v2',
- async import(specifier: string) {
- if (!modules.has(specifier)) throw new Error(`unexpected Loader import: ${specifier}`)
- return modules.get(specifier)
- },
- } as unknown as NonNullable<typeof context.loader.internal>
- await context.loader.create({
- name: 'cordis:include',
- config: { path: pathToFileURL(configPath).href },
- })
- await context.loader.await()
- return context
- }
- describe('session-title Loader composition', () => {
- it('loads the service and one model provider with required deployment policy', async () => {
- const ctx = await loadComposition()
- const unloaded = [...ctx.loader.entries()]
- .filter(entry => entry.fiber === undefined && !entry.disabled)
- .map(entry => entry.options.name)
- expect(unloaded).toEqual([])
- const adapter = new LoaderAdapter()
- ctx.llm.registerAdapter(['title-route'], adapter)
- const session = ctx.sessions.create(SessionId('loader-title'))
- session.append('turn/start', {
- turn: 1,
- })
- const message = session.append('user/message', createUserMessage({
- content: [{ type: 'text', text: 'Compose a title through Loader' }],
- source: { kind: 'user' },
- }), { surfaceOp: 'append' })
- await new Promise(resolve => setTimeout(resolve, 0))
- session.append('request/header', {
- header: { config: { provider: 'main-route', model: 'main-model' } },
- reason: 'initial',
- })
- await new Promise(resolve => setTimeout(resolve, 0))
- expect(adapter.requests[0]).toMatchObject({ provider: 'title-route', model: 'title-model' })
- expect(ctx.sessionTitle.get(session)).toMatchObject({
- title: 'Loader composed title',
- messageSeqs: [message.seq],
- source: {
- kind: 'provider',
- provider: 'session-title-first-message-llm',
- model: { provider: 'title-route', model: 'title-model' },
- },
- })
- })
- })
|