feat: Add Claude LLM support via Anthropic API and Infisical
This commit is contained in:
parent
294f10698e
commit
5892a7b734
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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) => {
|
||||
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 url = new URL(OLLAMA_URL);
|
||||
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.write(body);
|
||||
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.`;
|
||||
|
||||
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', () => {
|
||||
const handleResponse = (d) => {
|
||||
try {
|
||||
const raw = JSON.parse(d).response;
|
||||
// Extract JSON array using regex in case of conversational wrapper
|
||||
const raw = d;
|
||||
const match = raw.match(/\[[\s\S]*\]/);
|
||||
if (match) {
|
||||
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);
|
||||
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.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` +
|
||||
|
|
|
|||
Loading…
Reference in New Issue