@arizeai/phoenix-client
Version:
A client for the Phoenix API
326 lines • 11.3 kB
JavaScript
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