CSV handling

This commit is contained in:
2026-05-11 23:21:45 -04:00
parent 5de3955a3f
commit a9873b5634
+76 -5
View File
@@ -38,6 +38,49 @@ specific dollar amounts.`
const MODEL = process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-5'
// File uploaded to the Anthropic Console / Files API that the agent should
// consult on every call.
const KNOWLEDGE_FILE_ID =
process.env.ANTHROPIC_KNOWLEDGE_FILE_ID || 'file_011Cawv1SPNaknuMmyDtM1S7'
// ---------------------------------------------------------------------------
// Module-scoped singletons (reused across warm Lambda invocations)
// ---------------------------------------------------------------------------
// Netlify keeps the function process alive between invocations as long as it
// stays warm, so anything declared at module scope persists. We lazily build
// the Anthropic client and download the knowledge file exactly once per
// cold start, then reuse them for the lifetime of the container.
let _client
function getClient() {
if (_client) return _client
if (!process.env.ANTHROPIC_API_KEY) {
throw new Error('Server is missing ANTHROPIC_API_KEY')
}
_client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
// Required while the Files API is in beta.
defaultHeaders: { 'anthropic-beta': 'files-api-2025-04-14' },
})
return _client
}
// Cache the in-flight promise so concurrent requests during a cold start
// share a single download instead of fetching the CSV N times.
let _knowledgeFilePromise
function loadKnowledgeFile() {
if (_knowledgeFilePromise) return _knowledgeFilePromise
_knowledgeFilePromise = (async () => {
const resp = await getClient().beta.files.download(KNOWLEDGE_FILE_ID)
return await resp.text()
})().catch((err) => {
// Reset on failure so the next request can retry.
_knowledgeFilePromise = undefined
throw err
})
return _knowledgeFilePromise
}
// ---------------------------------------------------------------------------
// Handler
// ---------------------------------------------------------------------------
@@ -68,12 +111,20 @@ export default async (req /*, context */) => {
return new Response('Invalid station website URL', { status: 400 })
}
if (!process.env.ANTHROPIC_API_KEY) {
return new Response('Server is missing ANTHROPIC_API_KEY', { status: 500 })
let client
let knowledgeFileText
try {
client = getClient()
// First call after a cold start awaits the download; subsequent calls
// resolve immediately from the cached promise.
knowledgeFileText = await loadKnowledgeFile()
} catch (err) {
return new Response(
`Failed to initialize agent: ${err.message || err}`,
{ status: 500 },
)
}
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
const userMessage = `Here is a new public media station intake:
- Submitter Name: ${userName}
@@ -93,7 +144,27 @@ Please draft the funding outlook brief described in your instructions.`
model: MODEL,
max_tokens: 10000,
system: SYSTEM_PROMPT,
messages: [{ role: 'user', content: userMessage }],
messages: [
{
role: 'user',
content: [
{
type: 'document',
source: {
type: 'text',
media_type: 'text/plain',
data: knowledgeFileText,
},
title: 'Funder knowledge base (CSV)',
citations: { enabled: true },
// Cache the tokenized CSV on Anthropic's side for ~5 minutes
// so repeat requests pay ~10% of input-token cost for it.
cache_control: { type: 'ephemeral' },
},
{ type: 'text', text: userMessage },
],
},
],
}
if (AGENT_ID && AGENT_ID !== 'YOUR_AGENT_ID_HERE') {
streamArgs.agent_id = AGENT_ID