feat(平台): 补齐服务端持久化和AI拆解契约

This commit is contained in:
Script Generator
2026-06-25 15:21:32 +08:00
parent 5723356d08
commit 5adc7759ad
73 changed files with 5599 additions and 368 deletions

View 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('');
}
}