124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
CHAT_TRUNCATION_MARKER,
|
|
fitChatRequest,
|
|
LLM_PROXY_CHAT_LIMITS,
|
|
toChatRequest,
|
|
toRoleMessages,
|
|
} from '../src/llm-chat'
|
|
|
|
describe('toChatRequest', () => {
|
|
it('separates system messages from turns and keeps turn order', () => {
|
|
expect(toChatRequest([
|
|
{ role: 'system', content: 'persona' },
|
|
{ role: 'user', content: 'hi' },
|
|
{ role: 'assistant', content: 'hello' },
|
|
{ role: 'user', content: 'again' },
|
|
])).toEqual({
|
|
system: 'persona',
|
|
turns: [
|
|
{ role: 'user', content: 'hi' },
|
|
{ role: 'assistant', content: 'hello' },
|
|
{ role: 'user', content: 'again' },
|
|
],
|
|
})
|
|
})
|
|
|
|
it('joins several system messages and drops non-chat roles', () => {
|
|
expect(toChatRequest([
|
|
{ role: 'system', content: 'a' },
|
|
{ role: 'tool', content: 'ignored' },
|
|
{ role: 'system', content: 'b' },
|
|
{ role: 'system', content: ' ' },
|
|
{ role: 'user', content: 'q' },
|
|
])).toEqual({ system: 'a\n\nb', turns: [{ role: 'user', content: 'q' }] })
|
|
})
|
|
|
|
it('omits system when there is none', () => {
|
|
expect(toChatRequest([{ role: 'user', content: 'q' }])).toEqual({
|
|
turns: [{ role: 'user', content: 'q' }],
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('toRoleMessages', () => {
|
|
it('round-trips a canonical Ollama message list unchanged', () => {
|
|
const messages = [
|
|
{ role: 'system', content: 'persona' },
|
|
{ role: 'user', content: 'hi' },
|
|
{ role: 'assistant', content: 'hello' },
|
|
]
|
|
expect(toRoleMessages(toChatRequest(messages))).toEqual(messages)
|
|
})
|
|
|
|
it('puts the system message first', () => {
|
|
expect(toRoleMessages({ system: 's', turns: [{ role: 'user', content: 'u' }] })).toEqual([
|
|
{ role: 'system', content: 's' },
|
|
{ role: 'user', content: 'u' },
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('fitChatRequest', () => {
|
|
const limits = { maxSystemChars: 50, maxMessageChars: 20, maxMessages: 3, maxTotalChars: 40 }
|
|
|
|
it('keeps the head of an over-long system prompt within the limit', () => {
|
|
const system = `INSTRUCTIONS ${'x'.repeat(200)}`
|
|
const fitted = fitChatRequest({ system, turns: [{ role: 'user', content: 'q' }] }, limits)
|
|
expect(fitted.system?.length).toBeLessThanOrEqual(limits.maxSystemChars)
|
|
expect(fitted.system?.startsWith('INSTRUCTIONS')).toBe(true)
|
|
expect(fitted.system?.endsWith(CHAT_TRUNCATION_MARKER)).toBe(true)
|
|
})
|
|
|
|
it('drops empty turns such as an empty assistant reply', () => {
|
|
const fitted = fitChatRequest({
|
|
turns: [
|
|
{ role: 'user', content: 'q1' },
|
|
{ role: 'assistant', content: '' },
|
|
{ role: 'user', content: 'q2' },
|
|
],
|
|
}, limits)
|
|
expect(fitted.turns).toEqual([
|
|
{ role: 'user', content: 'q1' },
|
|
{ role: 'user', content: 'q2' },
|
|
])
|
|
})
|
|
|
|
it('keeps the newest turns within count and size limits and never starts with assistant', () => {
|
|
const fitted = fitChatRequest({
|
|
turns: [
|
|
{ role: 'user', content: 'u1' },
|
|
{ role: 'assistant', content: 'a1' },
|
|
{ role: 'user', content: 'u2' },
|
|
{ role: 'assistant', content: 'a2' },
|
|
{ role: 'user', content: 'u3' },
|
|
],
|
|
}, limits)
|
|
// maxMessages=3 keeps [u2, a2, u3]
|
|
expect(fitted.turns.map((t) => t.content)).toEqual(['u2', 'a2', 'u3'])
|
|
|
|
const trimmed = fitChatRequest({
|
|
turns: [
|
|
{ role: 'user', content: 'u'.repeat(20) },
|
|
{ role: 'assistant', content: 'a'.repeat(15) },
|
|
{ role: 'user', content: 'q'.repeat(20) },
|
|
],
|
|
}, limits)
|
|
// total 40: [a(15), q(20)] fits, then the leading assistant turn is dropped
|
|
expect(trimmed.turns).toEqual([{ role: 'user', content: 'q'.repeat(20) }])
|
|
})
|
|
|
|
it('truncates a single turn over the per-message limit', () => {
|
|
const fitted = fitChatRequest({ turns: [{ role: 'user', content: 'z'.repeat(100) }] }, limits)
|
|
expect(fitted.turns[0].content.length).toBeLessThanOrEqual(limits.maxMessageChars)
|
|
})
|
|
|
|
it('exposes the llm-proxy limits', () => {
|
|
expect(LLM_PROXY_CHAT_LIMITS).toEqual({
|
|
maxSystemChars: 8_000,
|
|
maxMessageChars: 8_000,
|
|
maxMessages: 40,
|
|
maxTotalChars: 50_000,
|
|
})
|
|
})
|
|
})
|