Initial API scaffold: Elysia + Bun + Drizzle + BetterAuth + LangChain

This commit is contained in:
2026-01-27 02:43:11 +00:00
commit 06f1b4e548
18 changed files with 1807 additions and 0 deletions

109
src/services/ai.ts Normal file
View File

@@ -0,0 +1,109 @@
import { ChatAnthropic } from '@langchain/anthropic';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';
export type AIProvider = 'anthropic' | 'openai';
// Get model based on provider
function getModel(provider: AIProvider = 'anthropic') {
if (provider === 'anthropic') {
return new ChatAnthropic({
modelName: 'claude-sonnet-4-20250514',
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});
}
// Add OpenAI support later
throw new Error(`Provider ${provider} not yet supported`);
}
// Email generation prompt
const emailPrompt = ChatPromptTemplate.fromMessages([
['system', `You are a professional wealth advisor writing to a valued client.
Maintain a warm but professional tone. Incorporate personal details naturally.
Keep emails concise (3-4 paragraphs max).
Do not include subject line - just the body.`],
['human', `Advisor: {advisorName}
Client: {clientName}
Their interests: {interests}
Recent notes: {notes}
Purpose: {purpose}
Generate a personalized email that feels genuine, not templated.`],
]);
export interface GenerateEmailParams {
advisorName: string;
clientName: string;
interests: string[];
notes: string;
purpose: string;
provider?: AIProvider;
}
export async function generateEmail(params: GenerateEmailParams): Promise<string> {
const model = getModel(params.provider);
const parser = new StringOutputParser();
const chain = emailPrompt.pipe(model).pipe(parser);
const response = await chain.invoke({
advisorName: params.advisorName,
clientName: params.clientName,
interests: params.interests.join(', ') || 'not specified',
notes: params.notes || 'No recent notes',
purpose: params.purpose,
});
return response;
}
// Birthday message generation
const birthdayPrompt = ChatPromptTemplate.fromMessages([
['system', `Generate a thoughtful birthday message from a wealth advisor to their client.
Should feel personal, not generic. Keep it brief (2-3 sentences) and sincere.`],
['human', `Client: {clientName}
Years as client: {yearsAsClient}
Interests: {interests}
Generate a warm birthday message.`],
]);
export interface GenerateBirthdayMessageParams {
clientName: string;
yearsAsClient: number;
interests: string[];
provider?: AIProvider;
}
export async function generateBirthdayMessage(params: GenerateBirthdayMessageParams): Promise<string> {
const model = getModel(params.provider);
const parser = new StringOutputParser();
const chain = birthdayPrompt.pipe(model).pipe(parser);
const response = await chain.invoke({
clientName: params.clientName,
yearsAsClient: params.yearsAsClient.toString(),
interests: params.interests.join(', ') || 'not specified',
});
return response;
}
// Email subject generation
const subjectPrompt = ChatPromptTemplate.fromMessages([
['system', `Generate a professional but warm email subject line for a wealth advisor's email.
Keep it short (under 50 characters). Do not use quotes.`],
['human', `Purpose: {purpose}
Client name: {clientName}
Generate just the subject line, nothing else.`],
]);
export async function generateSubject(purpose: string, clientName: string, provider?: AIProvider): Promise<string> {
const model = getModel(provider);
const parser = new StringOutputParser();
const chain = subjectPrompt.pipe(model).pipe(parser);
const response = await chain.invoke({ purpose, clientName });
return response.trim();
}