diff --git a/src/modules/aix/server/dispatch/chatGenerate/adapters/anthropic.messageCreate.ts b/src/modules/aix/server/dispatch/chatGenerate/adapters/anthropic.messageCreate.ts index 3b35ab0d4..92a32fe1a 100644 --- a/src/modules/aix/server/dispatch/chatGenerate/adapters/anthropic.messageCreate.ts +++ b/src/modules/aix/server/dispatch/chatGenerate/adapters/anthropic.messageCreate.ts @@ -1,6 +1,6 @@ import { escapeXml } from '~/server/wire'; -import type { AixAPI_Model, AixAPIChatGenerate_Request, AixMessages_ChatMessage, AixParts_MetaInReferenceToPart, AixTools_ToolDefinition, AixTools_ToolsPolicy } from '../../../api/aix.wiretypes'; +import type { AixAPI_Model, AixAPIChatGenerate_Request, AixMessages_ChatMessage, AixParts_DocPart, AixParts_MetaInReferenceToPart, AixTools_ToolDefinition, AixTools_ToolsPolicy } from '../../../api/aix.wiretypes'; import { AnthropicWire_API_Message_Create, AnthropicWire_Blocks } from '../../wiretypes/anthropic.wiretypes'; @@ -21,6 +21,15 @@ export function aixToAnthropicMessageCreate(model: AixAPI_Model, chatGenerate: A if (chatGenerate.systemMessage?.parts.length) { systemMessage = chatGenerate.systemMessage.parts.reduce((acc, part) => { switch (part.pt) { + + case 'text': + acc.push(AnthropicWire_Blocks.TextBlock(part.text)); + break; + + case 'doc': + acc.push(AnthropicWire_Blocks.TextBlock(approxDocPart_To_String(part))); + break; + case 'meta_cache_control': if (!acc.length) console.warn('Anthropic: cache_control without a message to attach to'); @@ -29,12 +38,16 @@ export function aixToAnthropicMessageCreate(model: AixAPI_Model, chatGenerate: A else AnthropicWire_Blocks.blockSetCacheControl(acc[acc.length - 1], 'ephemeral'); break; - case 'text': - acc.push(AnthropicWire_Blocks.TextBlock(part.text)); - break; + + default: + throw new Error(`Unsupported part type in System message: ${(part as any).pt}`); } return acc; }, [] as Exclude); + + // unset system message if empty + if (!systemMessage.length) + systemMessage = undefined; } // Transform the chat messages into Anthropic's format @@ -91,7 +104,7 @@ export function aixToAnthropicMessageCreate(model: AixAPI_Model, chatGenerate: A // Top-P instead of temperature if (model.topP !== undefined) { payload.top_p = model.topP; - delete payload.temperature + delete payload.temperature; } // Preemptive error detection with server-side payload validation before sending it upstream @@ -136,11 +149,11 @@ function* _generateAnthropicMessagesContentBlocks({ parts, role }: AixMessages_C break; case 'doc': - yield { role: 'user', content: AnthropicWire_Blocks.TextBlock('```' + (part.ref || '') + '\n' + part.data.text + '\n```\n') }; + yield { role: 'user', content: AnthropicWire_Blocks.TextBlock(approxDocPart_To_String(part)) }; break; case 'meta_in_reference_to': - const irtXMLString = inReferenceTo_To_XMLString(part); + const irtXMLString = approxInReferenceTo_To_XMLString(part); if (irtXMLString) yield { role: 'user', content: AnthropicWire_Blocks.TextBlock(irtXMLString) }; break; @@ -259,7 +272,22 @@ function _toAnthropicToolChoice(itp: AixTools_ToolsPolicy): NonNullable\n...\n' + // - ```doc id='ref' title='title' version='version'\n...\n``` + // - # Title [id='ref' version='version']\n...\n + // - ...more ideas... + // + return '```' + (ref || '') + '\n' + data.text + '\n```\n'; +} + +export function approxInReferenceTo_To_XMLString(irt: AixParts_MetaInReferenceToPart): string | null { const refs = irt.referTo.map(r => escapeXml(r.mText)); if (!refs.length) return null; // `User provides no specific references`; diff --git a/src/modules/aix/server/dispatch/chatGenerate/adapters/gemini.generateContent.ts b/src/modules/aix/server/dispatch/chatGenerate/adapters/gemini.generateContent.ts index 31b6cdac1..17614dc1b 100644 --- a/src/modules/aix/server/dispatch/chatGenerate/adapters/gemini.generateContent.ts +++ b/src/modules/aix/server/dispatch/chatGenerate/adapters/gemini.generateContent.ts @@ -1,7 +1,7 @@ import type { AixAPI_Model, AixAPIChatGenerate_Request, AixMessages_ChatMessage, AixParts_DocPart, AixTools_ToolDefinition, AixTools_ToolsPolicy } from '../../../api/aix.wiretypes'; import { GeminiWire_API_Generate_Content, GeminiWire_ContentParts, GeminiWire_Messages, GeminiWire_Safety, GeminiWire_ToolDeclarations } from '../../wiretypes/gemini.wiretypes'; -import { inReferenceTo_To_XMLString } from './anthropic.messageCreate'; +import { approxDocPart_To_String, approxInReferenceTo_To_XMLString } from './anthropic.messageCreate'; // configuration @@ -18,17 +18,26 @@ export function aixToGeminiGenerateContent(model: AixAPI_Model, chatGenerate: Ai if (chatGenerate.systemMessage?.parts.length) { systemInstruction = chatGenerate.systemMessage.parts.reduce((acc, part) => { switch (part.pt) { - case 'meta_cache_control': - // ignore - we implement caching in the Anthropic way for now - break; + case 'text': acc.parts.push(GeminiWire_ContentParts.TextPart(part.text)); break; + + case 'doc': + acc.parts.push(GeminiWire_ContentParts.TextPart(approxDocPart_To_String(part))); + break; + + case 'meta_cache_control': + // ignore - we implement caching in the Anthropic way for now + break; + + default: + throw new Error(`Unsupported part type in System message: ${(part as any).pt}`); } return acc; }, { parts: [] } as Exclude); - // unset system instructions with no parts + // unset system instruction if empty if (!systemInstruction.parts.length) systemInstruction = undefined; } @@ -120,7 +129,7 @@ function _toGeminiContents(chatSequence: AixMessages_ChatMessage[]): GeminiWire_ break; case 'meta_in_reference_to': - const irtXMLString = inReferenceTo_To_XMLString(part); + const irtXMLString = approxInReferenceTo_To_XMLString(part); if (irtXMLString) parts.push(GeminiWire_ContentParts.TextPart(irtXMLString)); break; @@ -291,5 +300,6 @@ function _toGeminiSafetySettings(threshold: GeminiWire_Safety.HarmBlockThreshold // Approximate conversions - alternative approaches should be tried until we find the best one function _toApproximateGeminiDocPart(aixPartsDocPart: AixParts_DocPart): GeminiWire_ContentParts.ContentPart { - return GeminiWire_ContentParts.TextPart(`\`\`\`${aixPartsDocPart.ref || ''}\n${aixPartsDocPart.data.text}\n\`\`\`\n`); + // NOTE: we keep this function because we could use Gemini's different way to represent documents in the future... + return GeminiWire_ContentParts.TextPart(approxDocPart_To_String(aixPartsDocPart)); } diff --git a/src/modules/aix/server/dispatch/chatGenerate/adapters/openai.chatCompletions.ts b/src/modules/aix/server/dispatch/chatGenerate/adapters/openai.chatCompletions.ts index 0eba2eec1..c2560e38b 100644 --- a/src/modules/aix/server/dispatch/chatGenerate/adapters/openai.chatCompletions.ts +++ b/src/modules/aix/server/dispatch/chatGenerate/adapters/openai.chatCompletions.ts @@ -3,6 +3,8 @@ import type { OpenAIDialects } from '~/modules/llms/server/openai/openai.router' import type { AixAPI_Model, AixAPIChatGenerate_Request, AixMessages_ChatMessage, AixMessages_SystemMessage, AixParts_DocPart, AixParts_MetaInReferenceToPart, AixTools_ToolDefinition, AixTools_ToolsPolicy } from '../../../api/aix.wiretypes'; import { OpenAIWire_API_Chat_Completions, OpenAIWire_ContentParts, OpenAIWire_Messages } from '../../wiretypes/openai.wiretypes'; +import { approxDocPart_To_String } from './anthropic.messageCreate'; + // // OpenAI API - Chat Adapter - Implementation Notes @@ -458,17 +460,11 @@ function _toApproximateOpanAIFlattenSystemMessage(texts: OpenAIWire_ContentParts return texts.map(text => text.text).join(approxSystemMessageJoiner); } -function _toApproximateOpenAIDocPart({ data, ref }: AixParts_DocPart): OpenAIWire_ContentParts.TextContentPart { +function _toApproximateOpenAIDocPart(part: AixParts_DocPart): OpenAIWire_ContentParts.TextContentPart { // Corner case, low probability: if the content is already enclosed in triple-backticks, return it as-is - if (data.text.startsWith('```')) - return OpenAIWire_ContentParts.TextContentPart(data.text); - - // TODO: consider a better representation here - we use the 'legacy' markdown encoding - // but we may as well support different ones in the future, such as: - // - '\n...\n' - // - ```doc id='ref' title='title' version='version'\n...\n``` - // - etc. + if (part.data.text.startsWith('```')) + return OpenAIWire_ContentParts.TextContentPart(part.data.text); - return OpenAIWire_ContentParts.TextContentPart(`\`\`\`${ref || ''}\n${data.text}\n\`\`\`\n`); + return OpenAIWire_ContentParts.TextContentPart(approxDocPart_To_String(part)); }