From b5fe34e50cff29ca21eb6db601b08fd017f94047 Mon Sep 17 00:00:00 2001 From: Aegis Date: Tue, 2 Jun 2026 10:13:15 -0500 Subject: [PATCH] fix: route workers ai helpers through providers --- web/src/kernel/router.ts | 23 ++++++++++--------- .../kernel/scheduled/conversation-facts.ts | 16 +++++++------ web/src/kernel/scheduled/dreaming/llm.ts | 14 +++++++---- web/src/kernel/scheduled/social-engage.ts | 20 +++++++++------- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/web/src/kernel/router.ts b/web/src/kernel/router.ts index 7b1a233..74824c3 100755 --- a/web/src/kernel/router.ts +++ b/web/src/kernel/router.ts @@ -1,3 +1,4 @@ +import { createLLMProviderFactory } from '@stackbilt/llm-providers'; import { getProcedure, findNearMiss, procedureKey, PROCEDURE_MIN_SUCCESSES, PROCEDURE_MIN_SUCCESS_RATE, getConversationHistory } from './memory/index.js'; import { askGroq, askGroqWithLogprobs } from '../groq.js'; import type { KernelIntent, ExecutionPlan, Executor } from './types.js'; @@ -73,21 +74,21 @@ async function classifyWithWorkersAI( systemPrompt: string, userPrompt: string, ): Promise { - const result = await ai.run('@cf/meta/llama-3.2-3b-instruct', { + const result = await createLLMProviderFactory({ + cloudflare: { ai }, + fallbackRules: [], + enableCircuitBreaker: true, + enableRetries: true, + }).generateResponse({ + model: '@cf/meta/llama-3.1-8b-instruct', + systemPrompt, messages: [ - { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }, ], - max_tokens: 200, + maxTokens: 200, temperature: 0.1, - }) as { response?: unknown }; - const raw = result.response; - if (typeof raw === 'string') return raw; - if (raw == null) return ''; - // Workers AI sometimes returns structured responses (objects with tool_calls, - // arrays of segments, etc.). Coerce to string so downstream .trim()/JSON.parse - // callers don't crash on non-string payloads. - return typeof raw === 'object' ? JSON.stringify(raw) : String(raw); + }); + return result.message ?? ''; } diff --git a/web/src/kernel/scheduled/conversation-facts.ts b/web/src/kernel/scheduled/conversation-facts.ts index e94f8cc..46e25bc 100644 --- a/web/src/kernel/scheduled/conversation-facts.ts +++ b/web/src/kernel/scheduled/conversation-facts.ts @@ -5,6 +5,7 @@ // Workers AI llama-3.1-8b (free tier) as fallback. import { type EdgeEnv } from '../dispatch.js'; +import { buildLLMProviderFactory } from '../provider-factory.js'; import { recordMemory as recordMemoryAdapter } from '../memory-adapter.js'; import { askGroq } from '../../groq.js'; import { pushFactsToMindSpring, type FactEntry } from './mindspring-notebook.js'; @@ -97,13 +98,14 @@ async function askAi( } // Workers AI fallback — llama-3.1-8b is on the genuine free tier if (env.ai) { - const result = await env.ai.run( - '@cf/meta/llama-3.1-8b-instruct' as Parameters[0], - { messages: [{ role: 'system', content: system }, { role: 'user', content: user }] }, - ); - if (typeof result === 'string') return result; - const obj = result as { response?: string; choices?: Array<{ message?: { content?: string } }> }; - return obj.choices?.[0]?.message?.content ?? obj.response ?? ''; + const result = await buildLLMProviderFactory(env).generateResponse({ + model: '@cf/meta/llama-3.1-8b-instruct', + systemPrompt: system, + messages: [{ role: 'user', content: user }], + maxTokens: 1024, + temperature: 0.2, + }); + return result.message ?? ''; } throw new Error('[conv-facts] No LLM provider available (groqApiKey and env.ai both missing)'); } diff --git a/web/src/kernel/scheduled/dreaming/llm.ts b/web/src/kernel/scheduled/dreaming/llm.ts index 3243962..1604b4a 100644 --- a/web/src/kernel/scheduled/dreaming/llm.ts +++ b/web/src/kernel/scheduled/dreaming/llm.ts @@ -1,6 +1,7 @@ // Shared LLM helper — Groq first (free, 70B quality), Workers AI 70B fallback import type { EdgeEnv } from '../../dispatch.js'; +import { buildLLMProviderFactory } from '../../provider-factory.js'; import { askGroq } from '../../../groq.js'; export async function askWorkersAiOrGroq( @@ -20,11 +21,14 @@ export async function askWorkersAiOrGroq( } // Workers AI fallback — only fires if Groq is unavailable or throws if (env.ai) { - const result = await env.ai.run( - '@cf/meta/llama-3.3-70b-instruct-fp8-fast' as Parameters[0], - { messages: [{ role: 'system', content: system }, { role: 'user', content: user }] }, - ) as { response?: string; choices?: Array<{ message?: { content?: string } }> }; - return result.choices?.[0]?.message?.content ?? result.response ?? ''; + const result = await buildLLMProviderFactory(env).generateResponse({ + model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast', + systemPrompt: system, + messages: [{ role: 'user', content: user }], + maxTokens: 2048, + temperature: 0.2, + }); + return result.message ?? ''; } throw new Error('[dreaming] No LLM provider available (groqApiKey and env.ai both missing)'); } diff --git a/web/src/kernel/scheduled/social-engage.ts b/web/src/kernel/scheduled/social-engage.ts index 7e5060b..9488cc0 100644 --- a/web/src/kernel/scheduled/social-engage.ts +++ b/web/src/kernel/scheduled/social-engage.ts @@ -2,6 +2,7 @@ // Runs every 6 hours. Likes replies, follows back real accounts, // replies to substantive comments with Workers AI. +import { createLLMProviderFactory } from '@stackbilt/llm-providers'; import { type EdgeEnv } from '../dispatch.js'; import { getNotifications, @@ -153,21 +154,24 @@ async function generateReply( incomingText: string, authorHandle: string, ): Promise { - const result = await ai.run('@cf/meta/llama-3.1-8b-instruct' as Parameters[0], { + const result = await createLLMProviderFactory({ + cloudflare: { ai }, + fallbackRules: [], + enableCircuitBreaker: true, + enableRetries: true, + }).generateResponse({ + model: '@cf/meta/llama-3.1-8b-instruct', + systemPrompt: `You are the operator's social media voice. Direct, builder-energy, anti-corporate. No emoji spam. No "excited to announce." Keep replies under 200 chars. Be genuine and conversational. If you can't add value, return SKIP.`, messages: [ - { - role: 'system', - content: `You are the operator's social media voice. Direct, builder-energy, anti-corporate. No emoji spam. No "excited to announce." Keep replies under 200 chars. Be genuine and conversational. If you can't add value, return SKIP.`, - }, { role: 'user', content: `@${authorHandle} replied to our Bluesky post: "${incomingText}"\n\nWrite a brief, genuine reply. Return ONLY the reply text, or SKIP if there's nothing meaningful to add.`, }, ], - max_tokens: 100, - }) as { response?: string }; + maxTokens: 100, + }); - const reply = result.response?.trim(); + const reply = result.message?.trim(); if (!reply || reply === 'SKIP' || reply.length < 5) return null; // Safety: truncate to 300 chars (Bluesky limit)