feat: switch from Anthropic to Groq API (llama-3.3-70b-versatile)
All checks were successful
Deploy / deploy (push) Successful in 2m47s

- route.ts: replace @anthropic-ai/sdk with groq-sdk, rewrite chat loop
- voice-tool-schemas.ts: convert from Anthropic format to OpenAI/Groq function tools
- voice-history.ts: extend HistoryMessage type to include tool role, simplify cache stubs

No prompt caching (Groq does not support it), tool calling preserved.
This commit is contained in:
Cosmo
2026-04-30 20:43:30 +00:00
parent 96fa78bd5c
commit 04b7d1f104
3 changed files with 263 additions and 257 deletions

View File

@@ -3,6 +3,8 @@
* /data — это volume контейнера (на хосте /opt/digital-home/smart-home-tablet-data/).
*
* Fallback: если /data не существует (локальная разработка) — пишем в /tmp/voice-history.
*
* Формат обновлён под Groq/OpenAI: role может быть 'user' | 'assistant' | 'tool'.
*/
import { promises as fs } from 'node:fs'
import { existsSync } from 'node:fs'
@@ -10,15 +12,16 @@ import path from 'node:path'
const PRIMARY_DIR = process.env.VOICE_HISTORY_DIR || '/data/voice-history'
const DATA_DIR = (() => {
// Проверяем существование родителя (/data) — без него запись упадёт ENOENT.
const parent = path.dirname(PRIMARY_DIR)
return existsSync(parent) ? PRIMARY_DIR : '/tmp/voice-history'
})()
const MAX_HISTORY = parseInt(process.env.VOICE_MAX_HISTORY || '40', 10)
export type HistoryMessage = {
role: 'user' | 'assistant'
role: 'user' | 'assistant' | 'tool'
content: any
tool_calls?: any[]
tool_call_id?: string
}
function todayIso(): string {
@@ -60,7 +63,8 @@ export async function resetHistory(agent: string): Promise<void> {
}
/**
* Убирает cache_control из блоков (для записи в историю — следующий turn пересчитает границу).
* Заглушка для обратной совместимости — убирает cache_control из блоков.
* В Groq-режиме cache_control не используется, функция — no-op.
*/
export function stripCacheControl(content: any): any {
if (Array.isArray(content)) {
@@ -76,33 +80,9 @@ export function stripCacheControl(content: any): any {
}
/**
* Граница prompt-кеша: всё, кроме последних N сообщений, помечаем cache_control
* на последнем блоке последнего «старого» сообщения. Даёт cache hit на каждом turn.
* Заглушка для обратной совместимости.
* В Groq-режиме prompt-кеширование не используется — просто возвращаем историю как есть.
*/
export function buildMessagesWithCache(history: HistoryMessage[], cacheTailUncached = 2): HistoryMessage[] {
if (history.length <= cacheTailUncached) {
return history.map((m) => ({ role: m.role, content: m.content }))
}
const cacheBoundary = history.length - cacheTailUncached
return history.map((msg, i) => {
if (i === cacheBoundary - 1) {
return { role: msg.role, content: wrapLastBlockWithCache(msg.content) }
}
return { role: msg.role, content: msg.content }
})
}
function wrapLastBlockWithCache(content: any): any {
if (typeof content === 'string') {
return [{ type: 'text', text: content, cache_control: { type: 'ephemeral' } }]
}
if (Array.isArray(content) && content.length) {
const out = [...content]
const last = out[out.length - 1]
if (last && typeof last === 'object') {
out[out.length - 1] = { ...last, cache_control: { type: 'ephemeral' } }
}
return out
}
return content
export function buildMessagesWithCache(history: HistoryMessage[], _cacheTailUncached = 2): HistoryMessage[] {
return history.map((m) => ({ role: m.role, content: m.content }))
}