UNPKG

@arizeai/phoenix-client

Version:

A client for the Phoenix API

326 lines 11.3 kB
import { assertUnreachable } from "../../../utils/assertUnreachable.js"; import { isObject } from "../../../utils/isObject.js"; import { makeTextPart, makeToolResultPart, } from "../phoenixPrompt/messagePartSchemas.js"; import { openaiChatPartSchema } from "./messagePartSchemas.js"; import { openAIMessageSchema } from "./messageSchemas.js"; import { openAIToolCallSchema } from "./toolCallSchemas.js"; import { openAIToolChoiceSchema } from "./toolChoiceSchemas.js"; import { openAIToolDefinitionSchema } from "./toolSchemas.js"; import invariant from "tiny-invariant"; export const openAIChatPartToAnthropic = openaiChatPartSchema.transform((openai) => { const type = openai.type; switch (type) { case "text": return { type: "text", text: openai.text }; case "image_url": { if (!openai.image_url.url.startsWith("data:image/")) { return null; } let mediaType = openai.image_url.url?.split(";")?.[0]?.split("/")[1]; if (mediaType !== "jpeg" && mediaType !== "jpg" && mediaType !== "png" && mediaType !== "gif" && mediaType !== "webp") { return null; } if (mediaType === "jpg") { mediaType = "jpeg"; } return { type: "image", source: { data: openai.image_url.url, media_type: `image/${mediaType}`, type: "base64", }, }; } default: return assertUnreachable(type); } }); /** * Hub → Spoke: Convert an OpenAI message to Anthropic format */ export const openAIMessageToAnthropic = openAIMessageSchema.transform((openai) => { let role = openai.role; const content = []; // convert all roles except assistant to user if (openai.role !== "assistant") { role = "user"; } invariant(role === "user" || role === "assistant", `Unexpected openai role: ${role}`); if (typeof openai.content === "string" && openai.role !== "tool") { content.push({ type: "text", text: openai.content }); } else if (Array.isArray(openai.content)) { openai.content.forEach((part) => { if (part.type === "text" || part.type === "image_url") { const parsedPart = openAIChatPartToAnthropic.parse(part); if (parsedPart) { content.push(parsedPart); } } }); } let toolCallParts = []; if (openai.role === "assistant" && "tool_calls" in openai) { toolCallParts = openai.tool_calls?.map((tc) => openAIToolCallToAnthropic.parse(tc)) ?? []; } if (toolCallParts.length > 0) { toolCallParts.forEach((tc) => { content.push(tc); }); } if (openai.role === "tool") { content.push({ type: "tool_result", tool_use_id: openai.tool_call_id, content: openai.content, }); } return { role, content, }; }); /** * Hub → Spoke: Convert an OpenAI message to Prompt format */ export const openAIMessageToPhoenixPrompt = openAIMessageSchema.transform((openai) => { const content = []; // Special handling for tool messages if (openai.role === "tool" && openai.tool_call_id) { const toolResultPart = makeToolResultPart(openai.tool_call_id, openai.content); if (toolResultPart) { content.push(toolResultPart); } return { role: "tool", content, }; } // Convert content to text part if it exists if (typeof openai.content === "string") { const textPart = makeTextPart(openai.content); if (textPart) { content.push(textPart); } } else if (Array.isArray(openai.content)) { openai.content.forEach((part) => { if (part.type === "text") { const textPart = makeTextPart(part.text); if (textPart) { content.push(textPart); } } }); } // Convert tool calls if they exist if (openai.role === "assistant" && openai.tool_calls) { openai.tool_calls.forEach((tc) => { const toolCallPart = { type: "tool_call", tool_call_id: tc.id, tool_call: { type: "function", name: tc.function.name, arguments: tc.function.arguments, }, }; if (toolCallPart) { content.push(toolCallPart); } }); } // Map roles const roleMap = { system: "SYSTEM", user: "USER", assistant: "AI", tool: "TOOL", developer: "SYSTEM", // Map developer to SYSTEM function: "TOOL", // Map function to TOOL }; return { role: roleMap[openai.role], content, }; }); /** * Spoke → Hub: Convert a Prompt message to AI format */ export const openAIMessageToVercelAI = openAIMessageSchema.transform((openai) => { const role = openai.role; switch (role) { case "developer": case "system": // take the first text part, or use string content if it exists return { role: "system", content: typeof openai.content === "string" ? openai.content : (openai.content.find((part) => part.type === "text")?.text ?? ""), }; case "user": // take text and image parts, ignore other parts return { role: "user", content: typeof openai.content === "string" ? openai.content : openai.content .filter((part) => part.type === "text" || part.type === "image_url") .map((part) => { if (part.type === "text") { return { type: "text", text: part.text, }; } if (part.type === "image_url") { return { type: "image", image: part.image_url.url, }; } return assertUnreachable(part); }), }; case "assistant": { // take text any parts, convert tool calls to tool call parts, ignore other parts const newContent = []; // take all text parts from openai message if (typeof openai.content === "string") { newContent.push({ type: "text", text: openai.content }); } else { openai.content.forEach((part) => { if (part.type === "text") { newContent.push({ type: "text", text: part.text }); } }); } // add any tool calls if (openai.tool_calls) { openai.tool_calls.forEach((tc) => { newContent.push({ type: "tool-call", toolCallId: tc.id, toolName: tc.function.name, input: tc.function.arguments, }); }); } return { role: "assistant", content: newContent, }; } case "tool": { const newContent = []; if (typeof openai.content === "string") { newContent.push({ type: "tool-result", toolCallId: openai.tool_call_id, toolName: "", // We don't have this?? output: { type: "text", value: openai.content }, }); } else { openai.content.forEach((part) => { if (part.type === "text") { newContent.push({ type: "tool-result", toolCallId: openai.tool_call_id, toolName: "", // We don't have this?? output: { type: "text", value: part.text, }, }); return; } assertUnreachable(part.type); }); } return { role: "tool", content: newContent, }; } case "function": // eslint-disable-next-line no-console console.warn("Function role not supported in Vercel AI SDK"); return { role: "tool", content: [], }; default: assertUnreachable(role); } }); /** * Parse incoming object as an OpenAI tool call and immediately convert to Anthropic format */ export const openAIToolCallToAnthropic = openAIToolCallSchema.transform((openai) => ({ id: openai.id, type: "tool_use", name: openai.function.name, input: openai.function.arguments, })); export const openAIToolChoiceToAnthropic = openAIToolChoiceSchema.transform((openAI) => { if (isObject(openAI)) { return { type: "tool", name: openAI.function.name }; } switch (openAI) { case "auto": return { type: "auto" }; case "none": return { type: "auto" }; case "required": return { type: "any" }; default: assertUnreachable(openAI); } }); export const openAIToolChoiceToVercelAI = openAIToolChoiceSchema.transform((openAI) => { if (isObject(openAI)) { return { type: "tool", toolName: openAI.function.name }; } switch (openAI) { case "auto": return "auto"; case "none": return "none"; case "required": return "required"; default: assertUnreachable(openAI); } }); /** * Parse incoming object as an OpenAI tool call and immediately convert to Anthropic format */ export const openAIToolDefinitionToAnthropic = openAIToolDefinitionSchema.transform((openai) => ({ name: openai.function.name, description: openai.function.description ?? openai.function.name, input_schema: openai.function.parameters, })); /** * Parse incoming object as an OpenAI tool definition and immediately convert to Vercel AI format */ export const openAIToolDefinitionToVercelAI = openAIToolDefinitionSchema.transform((openai) => ({ type: "function", description: openai.function.description, inputSchema: { _type: undefined, jsonSchema: openai.function.parameters, validate: undefined, }, })); //# sourceMappingURL=converters.js.map