89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
import Anthropic from '@anthropic-ai/sdk';
|
||
import type { AiProvider, PingRequest, PingResponse, ToolCallRequest, ToolCallResponse } from './provider.interface';
|
||
|
||
/**
|
||
* Anthropic 格式提供商(官方 API + 兼容 Anthropic 格式的中转站)
|
||
*/
|
||
export class AnthropicProvider implements AiProvider {
|
||
readonly format = 'anthropic' as const;
|
||
private client: Anthropic;
|
||
|
||
constructor(apiKey: string, baseURL?: string) {
|
||
this.client = new Anthropic({
|
||
apiKey,
|
||
...(baseURL && baseURL !== 'https://api.anthropic.com' ? { baseURL } : {}),
|
||
});
|
||
}
|
||
|
||
async callTool(req: ToolCallRequest): Promise<ToolCallResponse> {
|
||
const toolChoice = req.forceTool
|
||
? ({
|
||
type: 'tool',
|
||
name: req.tool.name,
|
||
disable_parallel_tool_use: true,
|
||
} as any)
|
||
: { type: 'auto' as const };
|
||
|
||
const response = await this.client.messages.create({
|
||
model: req.model,
|
||
max_tokens: req.maxTokens,
|
||
system: req.systemPrompt,
|
||
tools: [
|
||
{
|
||
name: req.tool.name,
|
||
description: req.tool.description,
|
||
input_schema: req.tool.inputSchema,
|
||
},
|
||
],
|
||
tool_choice: toolChoice,
|
||
messages: [{ role: 'user', content: req.userPrompt }],
|
||
});
|
||
|
||
const toolUse = response.content.find(
|
||
(c): c is Anthropic.ToolUseBlock => c.type === 'tool_use',
|
||
);
|
||
if (!toolUse) {
|
||
throw new Error(this.describeMissingToolUse(response));
|
||
}
|
||
|
||
return {
|
||
toolName: toolUse.name,
|
||
toolInput: toolUse.input as Record<string, any>,
|
||
inputTokens: response.usage.input_tokens,
|
||
outputTokens: response.usage.output_tokens,
|
||
rawModel: response.model,
|
||
};
|
||
}
|
||
|
||
async ping(req: PingRequest): Promise<PingResponse> {
|
||
const resp = await this.client.messages.create({
|
||
model: req.model,
|
||
max_tokens: 16,
|
||
messages: [{ role: 'user', content: 'ping' }],
|
||
});
|
||
return {
|
||
ok: true,
|
||
inputTokens: resp.usage.input_tokens,
|
||
outputTokens: resp.usage.output_tokens,
|
||
};
|
||
}
|
||
|
||
private describeMissingToolUse(response: Anthropic.Message): string {
|
||
const contentSummary = response.content
|
||
.map((block) => {
|
||
if (block.type === 'text') {
|
||
const text = block.text.replace(/\s+/g, ' ').slice(0, 160);
|
||
return `text("${text}")`;
|
||
}
|
||
return block.type;
|
||
})
|
||
.join(', ');
|
||
|
||
return [
|
||
'Anthropic 未通过 tool_use 返回结果',
|
||
`stop_reason=${response.stop_reason || 'unknown'}`,
|
||
`content=${contentSummary || 'empty'}`,
|
||
].join(';');
|
||
}
|
||
}
|