feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
99
server/supabase/functions/_shared/llm-contract.ts
Normal file
99
server/supabase/functions/_shared/llm-contract.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
export type LlmRole = 'user' | 'assistant'
|
||||
|
||||
export interface LlmMessage {
|
||||
role: LlmRole
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ValidatedLlmRequest {
|
||||
messages: LlmMessage[]
|
||||
system?: string
|
||||
max_tokens: number
|
||||
model?: string
|
||||
stream: boolean
|
||||
}
|
||||
|
||||
const MAX_MESSAGES = 40
|
||||
const MAX_MESSAGE_CHARS = 8_000
|
||||
const MAX_TOTAL_CHARS = 50_000
|
||||
const MAX_SYSTEM_CHARS = 8_000
|
||||
const DEFAULT_MAX_TOKENS = 1_024
|
||||
const MAX_OUTPUT_TOKENS = 4_096
|
||||
|
||||
export class LlmRequestError extends Error {
|
||||
readonly status = 400
|
||||
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = 'LlmRequestError'
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
export function parseLlmRequest(value: unknown): ValidatedLlmRequest {
|
||||
if (!isRecord(value)) throw new LlmRequestError('Invalid JSON body')
|
||||
if (!Array.isArray(value.messages) || value.messages.length === 0) {
|
||||
throw new LlmRequestError('At least one message is required')
|
||||
}
|
||||
if (value.messages.length > MAX_MESSAGES) {
|
||||
throw new LlmRequestError(`At most ${MAX_MESSAGES} messages are allowed`)
|
||||
}
|
||||
|
||||
let totalChars = 0
|
||||
const messages = value.messages.map((entry, index): LlmMessage => {
|
||||
if (!isRecord(entry) || (entry.role !== 'user' && entry.role !== 'assistant')) {
|
||||
throw new LlmRequestError(`Invalid role at message ${index}`)
|
||||
}
|
||||
if (typeof entry.content !== 'string') {
|
||||
throw new LlmRequestError(`Invalid content at message ${index}`)
|
||||
}
|
||||
const content = entry.content.trim()
|
||||
if (content.length === 0 || content.length > MAX_MESSAGE_CHARS) {
|
||||
throw new LlmRequestError(`Invalid content length at message ${index}`)
|
||||
}
|
||||
totalChars += content.length
|
||||
if (totalChars > MAX_TOTAL_CHARS) throw new LlmRequestError('Conversation is too large')
|
||||
return { role: entry.role, content }
|
||||
})
|
||||
|
||||
let system: string | undefined
|
||||
if (value.system !== undefined) {
|
||||
if (typeof value.system !== 'string') throw new LlmRequestError('Invalid system prompt')
|
||||
system = value.system.trim()
|
||||
if (system.length === 0 || system.length > MAX_SYSTEM_CHARS) {
|
||||
throw new LlmRequestError('Invalid system prompt length')
|
||||
}
|
||||
}
|
||||
|
||||
const maxTokens = value.max_tokens === undefined ? DEFAULT_MAX_TOKENS : value.max_tokens
|
||||
if (!Number.isSafeInteger(maxTokens) || Number(maxTokens) < 1 || Number(maxTokens) > MAX_OUTPUT_TOKENS) {
|
||||
throw new LlmRequestError(`max_tokens must be between 1 and ${MAX_OUTPUT_TOKENS}`)
|
||||
}
|
||||
if (value.model !== undefined && (typeof value.model !== 'string' || value.model.trim().length === 0)) {
|
||||
throw new LlmRequestError('Invalid model')
|
||||
}
|
||||
if (value.stream !== undefined && typeof value.stream !== 'boolean') {
|
||||
throw new LlmRequestError('Invalid stream flag')
|
||||
}
|
||||
|
||||
return {
|
||||
messages,
|
||||
system,
|
||||
max_tokens: Number(maxTokens),
|
||||
model: typeof value.model === 'string' ? value.model.trim() : undefined,
|
||||
stream: value.stream === true,
|
||||
}
|
||||
}
|
||||
|
||||
export function hasAssistantText(value: unknown): boolean {
|
||||
if (!isRecord(value) || !Array.isArray(value.content)) return false
|
||||
return value.content.some((block) => (
|
||||
isRecord(block)
|
||||
&& block.type === 'text'
|
||||
&& typeof block.text === 'string'
|
||||
&& block.text.trim().length > 0
|
||||
))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue