feat: Add Claude LLM support via Anthropic API and Infisical

This commit is contained in:
Antigravity 2026-05-29 17:29:59 -04:00
parent 294f10698e
commit 5892a7b734
3 changed files with 142 additions and 44 deletions

View File

@ -62,6 +62,11 @@ spec:
value: "+14085505485" value: "+14085505485"
- name: GROUP_URL - name: GROUP_URL
value: "https://chat.whatsapp.com/0Txfg7iZBbvIuvTbTqqgAX" value: "https://chat.whatsapp.com/0Txfg7iZBbvIuvTbTqqgAX"
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: trip-service-anthropic
key: api_key
volumeMounts: volumeMounts:
- name: app-volume - name: app-volume
mountPath: /app mountPath: /app

View File

@ -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

View File

@ -88,9 +88,45 @@ async function getTdotTraffic() {
} }
} }
async function getAiInsight(activityDetails, location, modelName = 'qwen2.5:0.5b') { async function getAiInsight(activityDetails, location, trip) {
return new Promise((resolve) => { 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 prompt = `Provide one short, clever tip or insight for someone visiting ${location} today, doing: ${activityDetails}. Keep it under 2 sentences.`;
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 }]
});
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 body = JSON.stringify({ model: modelName, prompt, stream: false });
const url = new URL(OLLAMA_URL); const url = new URL(OLLAMA_URL);
const req = http.request({ const req = http.request({
@ -106,6 +142,7 @@ async function getAiInsight(activityDetails, location, modelName = 'qwen2.5:0.5b
req.on('error', () => resolve('Have a great trip!')); req.on('error', () => resolve('Have a great trip!'));
req.write(body); req.write(body);
req.end(); req.end();
}
}); });
} }
@ -118,22 +155,9 @@ 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.`; 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'; const handleResponse = (d) => {
// 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 { try {
const raw = JSON.parse(d).response; const raw = d;
// Extract JSON array using regex in case of conversational wrapper
const match = raw.match(/\[[\s\S]*\]/); const match = raw.match(/\[[\s\S]*\]/);
if (match) { if (match) {
resolve(JSON.parse(match[0])); resolve(JSON.parse(match[0]));
@ -144,11 +168,60 @@ Return ONLY a JSON array of objects. Each object must have "time" (in 24-hour HH
console.error('[TripService] AI Schedule parsing failed:', e.message); console.error('[TripService] AI Schedule parsing failed:', e.message);
resolve([]); 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 }]
});
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.on('error', () => resolve([]));
req.write(body); req.write(body);
req.end(); 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 weather = await getWeather(dayPlan.location || 'Pigeon Forge, TN');
const nps = await getNpsAlerts(trip.npsParkCode); const nps = await getNpsAlerts(trip.npsParkCode);
const traffic = await getTdotTraffic(); 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` + const message = `*🏕️ ${trip.name} - ${dayPlan.date} Briefing*\n\n` +
`*🌤️ Weather (${dayPlan.location || 'Pigeon Forge'}):* ${weather}\n\n` + `*🌤️ Weather (${dayPlan.location || 'Pigeon Forge'}):* ${weather}\n\n` +