diff --git a/agents/k8s/trip-service-deployment.yaml b/agents/k8s/trip-service-deployment.yaml index 7088f5b..bf9df6c 100644 --- a/agents/k8s/trip-service-deployment.yaml +++ b/agents/k8s/trip-service-deployment.yaml @@ -62,6 +62,11 @@ spec: value: "+14085505485" - name: GROUP_URL value: "https://chat.whatsapp.com/0Txfg7iZBbvIuvTbTqqgAX" + - name: ANTHROPIC_API_KEY + valueFrom: + secretKeyRef: + name: trip-service-anthropic + key: api_key volumeMounts: - name: app-volume mountPath: /app diff --git a/agents/k8s/trip-service-externalsecrets.yaml b/agents/k8s/trip-service-externalsecrets.yaml new file mode 100644 index 0000000..9e4d4bd --- /dev/null +++ b/agents/k8s/trip-service-externalsecrets.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: trip-service-anthropic + namespace: ai-agents + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + refreshInterval: 1h + secretStoreRef: + name: infisical + kind: ClusterSecretStore + target: + name: trip-service-anthropic + creationPolicy: Owner + data: + - secretKey: api_key + remoteRef: + key: ANTHROPIC_API_KEY diff --git a/trip-service/server.js b/trip-service/server.js index 243fce0..c6943fc 100644 --- a/trip-service/server.js +++ b/trip-service/server.js @@ -88,24 +88,61 @@ async function getTdotTraffic() { } } -async function getAiInsight(activityDetails, location, modelName = 'qwen2.5:0.5b') { +async function getAiInsight(activityDetails, location, trip) { return new Promise((resolve) => { const prompt = `Provide one short, clever tip or insight for someone visiting ${location} today, doing: ${activityDetails}. Keep it under 2 sentences.`; - const body = JSON.stringify({ model: modelName, prompt, stream: false }); - const url = new URL(OLLAMA_URL); - const req = http.request({ - hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST', - headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } - }, (res) => { - let d = ''; - res.on('data', c => d += c); - res.on('end', () => { - try { resolve(JSON.parse(d).response.trim()); } catch { resolve('Have a great trip!'); } + + if (trip && trip.llmProvider === 'anthropic') { + const modelName = trip.llmModel || 'claude-3-5-sonnet-20240620'; + const body = JSON.stringify({ + model: modelName, + max_tokens: 150, + messages: [{ role: 'user', content: prompt }] }); - }); - req.on('error', () => resolve('Have a great trip!')); - req.write(body); - req.end(); + + const req = https.request({ + hostname: 'api.anthropic.com', + path: '/v1/messages', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': process.env.ANTHROPIC_API_KEY, + 'anthropic-version': '2023-06-01', + 'Content-Length': Buffer.byteLength(body) + } + }, (res) => { + let d = ''; + res.on('data', c => d += c); + res.on('end', () => { + try { + const data = JSON.parse(d); + resolve(data.content[0].text.trim()); + } catch { resolve('Have a great trip!'); } + }); + }); + req.on('error', () => resolve('Have a great trip!')); + req.write(body); + req.end(); + + } else { + // Default to Ollama + const modelName = trip ? (trip.llmModel || 'qwen2.5:0.5b') : 'qwen2.5:0.5b'; + const body = JSON.stringify({ model: modelName, prompt, stream: false }); + const url = new URL(OLLAMA_URL); + const req = http.request({ + hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST', + headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } + }, (res) => { + let d = ''; + res.on('data', c => d += c); + res.on('end', () => { + try { resolve(JSON.parse(d).response.trim()); } catch { resolve('Have a great trip!'); } + }); + }); + req.on('error', () => resolve('Have a great trip!')); + req.write(body); + req.end(); + } }); } @@ -118,37 +155,73 @@ Date: ${date} Return ONLY a JSON array of objects. Each object must have "time" (in 24-hour HH:mm format) and "message" (a short, fun 1-sentence reminder). Estimate reasonable times if none are given (e.g. breakfast 8:00, lunch 12:30, dinner 18:30). DO NOT output any markdown or conversational text.`; - const modelName = trip.llmModel || 'qwen2.5:0.5b'; - // Support future providers via trip.llmProvider - // Defaulting to Ollama for now - const body = JSON.stringify({ model: modelName, prompt, stream: false }); - const url = new URL(OLLAMA_URL); - - const req = http.request({ - hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST', - headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } - }, (res) => { - let d = ''; - res.on('data', c => d += c); - res.on('end', () => { - try { - const raw = JSON.parse(d).response; - // Extract JSON array using regex in case of conversational wrapper - const match = raw.match(/\[[\s\S]*\]/); - if (match) { - resolve(JSON.parse(match[0])); - } else { - resolve([]); - } - } catch (e) { - console.error('[TripService] AI Schedule parsing failed:', e.message); + const handleResponse = (d) => { + try { + const raw = d; + const match = raw.match(/\[[\s\S]*\]/); + if (match) { + resolve(JSON.parse(match[0])); + } else { resolve([]); } + } catch (e) { + console.error('[TripService] AI Schedule parsing failed:', e.message); + resolve([]); + } + }; + + if (trip && trip.llmProvider === 'anthropic') { + const modelName = trip.llmModel || 'claude-3-5-sonnet-20240620'; + const body = JSON.stringify({ + model: modelName, + max_tokens: 300, + messages: [{ role: 'user', content: prompt }] }); - }); - req.on('error', () => resolve([])); - req.write(body); - req.end(); + const req = https.request({ + hostname: 'api.anthropic.com', + path: '/v1/messages', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': process.env.ANTHROPIC_API_KEY, + 'anthropic-version': '2023-06-01', + 'Content-Length': Buffer.byteLength(body) + } + }, (res) => { + let d = ''; + res.on('data', c => d += c); + res.on('end', () => { + try { + const data = JSON.parse(d); + handleResponse(data.content[0].text); + } catch { resolve([]); } + }); + }); + req.on('error', () => resolve([])); + req.write(body); + req.end(); + + } else { + // Default to Ollama + const modelName = trip.llmModel || 'qwen2.5:0.5b'; + const body = JSON.stringify({ model: modelName, prompt, stream: false }); + const url = new URL(OLLAMA_URL); + const req = http.request({ + hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST', + headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } + }, (res) => { + let d = ''; + res.on('data', c => d += c); + res.on('end', () => { + try { + handleResponse(JSON.parse(d).response); + } catch { resolve([]); } + }); + }); + req.on('error', () => resolve([])); + req.write(body); + req.end(); + } }); } @@ -218,7 +291,7 @@ async function generateAndSendBriefing(trip, dayPlan) { const weather = await getWeather(dayPlan.location || 'Pigeon Forge, TN'); const nps = await getNpsAlerts(trip.npsParkCode); const traffic = await getTdotTraffic(); - const insight = await getAiInsight(dayPlan.activities, dayPlan.location || 'Smoky Mountains', trip.llmModel); + const insight = await getAiInsight(dayPlan.activities, dayPlan.location || 'Smoky Mountains', trip); const message = `*🏕️ ${trip.name} - ${dayPlan.date} Briefing*\n\n` + `*🌤️ Weather (${dayPlan.location || 'Pigeon Forge'}):* ${weather}\n\n` +