feat: add support for temperature, top_p to be configurable (#2200)

Co-authored-by: twix03 <sathwikbalaa@gmail.com>
Co-authored-by: RainbowBird <git@luoling.moe>
This commit is contained in:
Vikranth Kumar Bala
2026-09-08 00:02:53 +08:00
committed by GitHub
co-authored by twix03 RainbowBird
parent 66c6aa9b57
commit e293b71abc
17 changed files with 179 additions and 39 deletions
@@ -1,5 +1,5 @@
import { createInsertSchema, createSelectSchema } from 'drizzle-valibot'
import { array, literal, number, object, optional, pipe, string, transform, union } from 'valibot'
import { array, literal, number, object, optional, string, union } from 'valibot'
import * as schema from '../../schemas/characters'
@@ -17,6 +17,7 @@ export const CharacterCapabilityConfigSchema = object({
apiBaseUrl: string(),
llm: optional(object({
temperature: number(),
topP: optional(number()),
model: string(),
})),
tts: optional(object({
@@ -63,11 +64,6 @@ export const InsertCharacterCoverSchema = createInsertSchema(schema.characterCov
export const CharacterPromptSchema = createSelectSchema(schema.characterPrompts)
export const InsertCharacterPromptSchema = createInsertSchema(schema.characterPrompts)
const DateSchema = pipe(
string(),
transform(v => new Date(v)),
)
export const CreateCharacterSchema = object({
// TODO: Replace createInsertSchema-derived request bodies with explicit HTTP DTO schemas.
// The current shape still leaks persistence fields such as ownerId/creatorId into the API boundary.
@@ -102,16 +98,13 @@ export const CreateCharacterSchema = object({
// TODO: Split update request schema from DB insert schema.
// This route should reject server-managed fields like id/ownerId/creatorId/timestamps instead of allowing them here.
export const UpdateCharacterSchema = createInsertSchema(schema.character, {
id: optional(string()),
export const UpdateCharacterSchema = object({
version: optional(string()),
coverUrl: optional(string()),
avatarUrl: optional(string()),
creatorRole: optional(string()),
priceCredit: optional(string()),
creatorId: optional(string()),
ownerId: optional(string()),
characterId: optional(string()),
createdAt: optional(DateSchema),
updatedAt: optional(DateSchema),
capabilities: optional(array(createInsertSchema(schema.characterCapabilities, {
characterId: optional(string()),
type: CharacterCapabilityTypeSchema,
config: CharacterCapabilityConfigSchema,
}))),
})
@@ -2,7 +2,7 @@ import type { Database } from '../../libs/db'
import type { EngagementMetrics } from '../../otel'
import { useLogger } from '@guiiai/logg'
import { and, eq, isNull, or, sql } from 'drizzle-orm'
import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm'
import * as schema from '../../schemas/characters'
import * as userCharacterSchema from '../../schemas/user-character'
@@ -190,16 +190,60 @@ export function createCharacterService(db: Database, metrics?: EngagementMetrics
return inserted
},
async update(id: string, data: Partial<schema.NewCharacter>) {
// TODO: Return a stable single-object response shape for HTTP callers.
// leaking Drizzle returning() arrays across the service boundary makes route contracts drift.
const result = await db.update(schema.character)
.set({ ...data, updatedAt: new Date() })
.where(and(
eq(schema.character.id, id),
isNull(schema.character.deletedAt),
))
.returning()
async update(id: string, data: {
version?: string
coverUrl?: string
characterId?: string
capabilities?: {
type: 'llm' | 'tts' | 'vlm' | 'asr'
config: any
}[]
}) {
const { capabilities, ...characterData } = data
const result = await db.transaction(async (tx) => {
let updatedChar
if (Object.keys(characterData).length > 0) {
const [res] = await tx.update(schema.character)
.set({ ...characterData, updatedAt: new Date() })
.where(and(
eq(schema.character.id, id),
isNull(schema.character.deletedAt),
))
.returning()
updatedChar = res
}
if (capabilities) {
const submittedTypes = capabilities.map(c => c.type)
if (submittedTypes.length > 0) {
await tx.delete(schema.characterCapabilities)
.where(and(
eq(schema.characterCapabilities.characterId, id),
inArray(schema.characterCapabilities.type, submittedTypes),
))
await tx.insert(schema.characterCapabilities).values(
capabilities.map(c => ({ ...c, characterId: id })),
)
}
}
if (updatedChar) {
return updatedChar
}
const fallback = await tx.query.character.findFirst({
where: and(
eq(schema.character.id, id),
isNull(schema.character.deletedAt),
),
})
if (!fallback)
throw new Error('Character not found')
return fallback
})
logger.withFields({ id }).log('Updated character')
return result
},
@@ -7,6 +7,7 @@ interface CharacterCapabilityBaseConfig {
export interface CharacterCapabilityConfig extends CharacterCapabilityBaseConfig {
llm: {
temperature: number
topP?: number
model: string
}
tts: {