feat(平台): 补齐服务端持久化和AI拆解契约
This commit is contained in:
88
apps/server/src/modules/ai/providers/anthropic.provider.ts
Normal file
88
apps/server/src/modules/ai/providers/anthropic.provider.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
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(';');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user