Vercel ai gateway (#10)

* feat: use vercel ai gateway

* fix: correct response handling

* ci: add pipeline
This commit is contained in:
2025-10-11 15:07:54 +02:00
committed by GitHub
parent 99a04d2a3e
commit 017e538396
11 changed files with 1116 additions and 1003 deletions

66
utils/aiGatewayClient.ts Normal file
View 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.');
}
}

View File

@@ -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.');
}
}

View File

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

View File

@@ -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)
})
});

View File

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