fix(agents): download text file content for Claude instead of passing URL
Claude API cannot fetch arbitrary URLs. Text-based attachments (txt, csv, json, md) are now downloaded via their presigned MinIO URL and embedded directly as text blocks. PDF uses Claude's native document block. Added 50KB size limit with truncation for large text files. buildMessages() is now async to support text content fetching. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5076e16cc7
commit
8f7b633041
|
|
@ -204,8 +204,8 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
return;
|
||||
}
|
||||
|
||||
// 1. Build messages from conversation history
|
||||
const messages = this.buildMessages(context, userContent, attachments);
|
||||
// 1. Build messages from conversation history (async: 文本附件需下载内容)
|
||||
const messages = await this.buildMessages(context, userContent, attachments);
|
||||
|
||||
// 2. Build system prompt blocks (with cache control)
|
||||
const systemPrompt = this.buildSystemPromptBlocks();
|
||||
|
|
@ -328,13 +328,16 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
// Message Building
|
||||
// ============================================================
|
||||
|
||||
/** 文本类文件最大嵌入大小 (超过则截断) */
|
||||
private readonly MAX_TEXT_EMBED_SIZE = 50_000;
|
||||
|
||||
/**
|
||||
* 将附件转换为 Claude API content blocks
|
||||
* - image → { type: 'image', source: { type: 'url', url } }
|
||||
* - PDF → { type: 'document', source: { type: 'url', url }, title }
|
||||
* - text/csv/json/md → 嵌入为带文件名提示的 text block
|
||||
* - text/csv/json/md → 下载内容嵌入为 text block(Claude 无法自行访问 URL)
|
||||
*/
|
||||
private buildAttachmentBlocks(attachments: FileAttachment[]): any[] {
|
||||
private async buildAttachmentBlocks(attachments: FileAttachment[]): Promise<any[]> {
|
||||
const blocks: any[] = [];
|
||||
|
||||
for (const att of attachments) {
|
||||
|
|
@ -353,17 +356,39 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
title: att.originalName,
|
||||
});
|
||||
} else if (this.isTextBasedMime(att.mimeType)) {
|
||||
// 文本类文件:提示模型通过 URL 获取内容
|
||||
blocks.push({
|
||||
type: 'text',
|
||||
text: `[附件: ${att.originalName} (${att.mimeType})] — 文件内容可通过URL获取: ${att.downloadUrl}`,
|
||||
});
|
||||
// 文本类文件:下载内容并直接嵌入
|
||||
try {
|
||||
const textContent = await this.fetchTextContent(att.downloadUrl);
|
||||
blocks.push({
|
||||
type: 'text',
|
||||
text: `--- 附件: ${att.originalName} ---\n${textContent}`,
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.warn(`Failed to fetch text content for ${att.originalName}: ${err}`);
|
||||
blocks.push({
|
||||
type: 'text',
|
||||
text: `[附件: ${att.originalName} — 内容加载失败]`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/** 通过预签名 URL 获取文本文件内容 */
|
||||
private async fetchTextContent(url: string): Promise<string> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (text.length > this.MAX_TEXT_EMBED_SIZE) {
|
||||
return text.substring(0, this.MAX_TEXT_EMBED_SIZE) + '\n...[内容过长,已截断]';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/** 判断是否为可直接阅读的文本类 MIME */
|
||||
private isTextBasedMime(mimeType: string): boolean {
|
||||
const textMimes = [
|
||||
|
|
@ -373,18 +398,18 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
return textMimes.includes(mimeType) || mimeType.startsWith('text/');
|
||||
}
|
||||
|
||||
private buildMessages(
|
||||
private async buildMessages(
|
||||
context: LegacyConversationContext,
|
||||
userContent: string,
|
||||
attachments?: FileAttachment[],
|
||||
): ClaudeMessage[] {
|
||||
): Promise<ClaudeMessage[]> {
|
||||
const messages: ClaudeMessage[] = [];
|
||||
|
||||
// Convert previous messages
|
||||
if (context.previousMessages) {
|
||||
for (const msg of context.previousMessages) {
|
||||
if (msg.attachments?.length) {
|
||||
const contentBlocks = this.buildAttachmentBlocks(msg.attachments);
|
||||
const contentBlocks = await this.buildAttachmentBlocks(msg.attachments);
|
||||
contentBlocks.push({ type: 'text', text: msg.content });
|
||||
messages.push({ role: msg.role, content: contentBlocks });
|
||||
} else {
|
||||
|
|
@ -395,7 +420,7 @@ export class CoordinatorAgentService implements OnModuleInit {
|
|||
|
||||
// Build current user message
|
||||
if (attachments?.length) {
|
||||
const contentBlocks = this.buildAttachmentBlocks(attachments);
|
||||
const contentBlocks = await this.buildAttachmentBlocks(attachments);
|
||||
contentBlocks.push({ type: 'text', text: userContent });
|
||||
messages.push({ role: 'user', content: contentBlocks });
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue