Vercel ai gateway (#10)
* feat: use vercel ai gateway * fix: correct response handling * ci: add pipeline
This commit is contained in:
66
utils/aiGatewayClient.ts
Normal file
66
utils/aiGatewayClient.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'dotenv/config';
|
||||
import { generateText, tool, jsonSchema } from 'ai';
|
||||
import type { JSONSchema7 } from 'json-schema';
|
||||
|
||||
export interface BaseTool {
|
||||
readonly name: string;
|
||||
readonly input_schema: {
|
||||
readonly type: 'object';
|
||||
readonly properties: Record<string, unknown>;
|
||||
readonly required?: readonly string[];
|
||||
readonly description?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToolUseBlock {
|
||||
type: 'tool_use';
|
||||
id: string;
|
||||
name: string;
|
||||
input: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function makeRequest<T extends BaseTool>(
|
||||
prompt: string,
|
||||
toolDef: T
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const { steps } = await generateText({
|
||||
model: 'anthropic/claude-sonnet-4.5',
|
||||
temperature: 1,
|
||||
tools: {
|
||||
[toolDef.name]: tool({
|
||||
description: toolDef.input_schema.description || '',
|
||||
inputSchema: jsonSchema(toolDef.input_schema as JSONSchema7),
|
||||
execute: async args => args
|
||||
})
|
||||
},
|
||||
toolChoice: {
|
||||
type: 'tool',
|
||||
toolName: toolDef.name
|
||||
},
|
||||
prompt
|
||||
});
|
||||
|
||||
const toolCalls = steps.flatMap(step => step.toolCalls);
|
||||
|
||||
if (!toolCalls || toolCalls.length === 0) {
|
||||
throw new Error('No tool calls found in response');
|
||||
}
|
||||
|
||||
const typedCall = toolCalls[0] as unknown as {
|
||||
toolName: string;
|
||||
input: Record<string, unknown>;
|
||||
};
|
||||
|
||||
if (typedCall.toolName !== toolDef.name) {
|
||||
throw new Error(
|
||||
`Expected tool ${toolDef.name} but got ${typedCall.toolName}`
|
||||
);
|
||||
}
|
||||
|
||||
return typedCall.input;
|
||||
} catch (error) {
|
||||
console.error('Error making request:', error);
|
||||
throw Error('Vercel AI Gateway client error.');
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
|
||||
export interface BaseTool {
|
||||
readonly name: string;
|
||||
readonly input_schema: {
|
||||
readonly type: 'object';
|
||||
readonly properties: Record<string, unknown>;
|
||||
readonly required?: readonly string[];
|
||||
readonly description?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ToolUseBlock = {
|
||||
type: 'tool_use';
|
||||
id: string;
|
||||
name: string;
|
||||
input: Record<string, any>;
|
||||
};
|
||||
|
||||
export async function makeRequest<T extends BaseTool>(prompt: string, tool: T) {
|
||||
if (!process.env.ANTHROPIC_API_KEY) {
|
||||
throw Error('No Anthropic API key found.');
|
||||
}
|
||||
|
||||
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
||||
|
||||
try {
|
||||
const response = await client.messages.create({
|
||||
model: 'claude-sonnet-4-0',
|
||||
max_tokens: 8192,
|
||||
temperature: 1,
|
||||
tools: [tool],
|
||||
messages: [{ role: 'user', content: prompt }]
|
||||
});
|
||||
|
||||
if (response.stop_reason && response.stop_reason !== 'tool_use') {
|
||||
throw Error(JSON.stringify(response));
|
||||
}
|
||||
|
||||
if (response.content.length != 2) {
|
||||
throw Error(JSON.stringify(response));
|
||||
}
|
||||
|
||||
const content = response.content;
|
||||
|
||||
const toolUse = content.find((block): block is ToolUseBlock => block.type === 'tool_use');
|
||||
|
||||
if (!toolUse) {
|
||||
throw new Error('No tool_use block found in response');
|
||||
}
|
||||
|
||||
return toolUse.input;
|
||||
} catch (error) {
|
||||
console.error('Error making request:', error);
|
||||
throw Error('Anthropic client error.');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dotenv/config';
|
||||
import { Consumer, consumerSchema } from './types';
|
||||
import { Tool } from './tool';
|
||||
import { BaseTool, makeRequest } from '@anthropic';
|
||||
import { BaseTool, makeRequest } from '@utils/aiGatewayClient';
|
||||
import { generatePrompt } from './prompt';
|
||||
import { generateConsumerSeed } from '@utils/generateConsumerSeed';
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ const coreSchema = z.object({
|
||||
});
|
||||
|
||||
const routinesSchema = z.object({
|
||||
weekday: z.record(weekdayActivitySchema),
|
||||
weekday: z.record(z.string(), weekdayActivitySchema),
|
||||
weekend: z.array(z.string()),
|
||||
commute: z.object({
|
||||
method: z.string(),
|
||||
@@ -111,7 +111,7 @@ const financesSchema = z.object({
|
||||
subscriptions: z.array(subscriptionSchema),
|
||||
spending_patterns: z.object({
|
||||
impulsive_score: z.number().min(1).max(10),
|
||||
categories: z.record(spendingCategorySchema)
|
||||
categories: z.record(z.string(), spendingCategorySchema)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PurchaseList, purchaseListSchema } from './types';
|
||||
import { Tool } from './tool';
|
||||
import { BaseTool, makeRequest } from '@anthropic';
|
||||
import { BaseTool, makeRequest } from '@utils/aiGatewayClient';
|
||||
import { generatePrompt } from './prompt';
|
||||
import { Consumer } from '@consumer/types';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user