Skip to content

Commit

Permalink
Implement anthropic parsing / conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
cephalization committed Jan 24, 2025
1 parent 38a88c7 commit d518d54
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 9 deletions.
124 changes: 124 additions & 0 deletions js/packages/phoenix-client/examples/apply_prompt_anthropic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/* eslint-disable no-console */
import { createClient, getPrompt, toSDK } from "../src";
import { Anthropic } from "@anthropic-ai/sdk";
import { PromptLike } from "../src/types/prompts";

const PROMPT_NAME = process.env.PROMPT_NAME!;
const PROMPT_TAG = process.env.PROMPT_TAG!;
const PROMPT_VERSION_ID = process.env.PROMPT_VERSION_ID!;
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY!;

// get first argument from command line
const question = process.argv[2];

if (!question) {
throw new Error(
"Usage: pnpx tsx examples/apply_prompt_anthropic.ts 'What is the capital of France?'\nAssumes that the prompt has a variable named 'question'"
);
}

if (!ANTHROPIC_API_KEY) {
throw new Error("ANTHROPIC_API_KEY must be provided in the environment");
}

const client = createClient({
options: {
baseUrl: "http://localhost:6006",
},
});

const anthropic = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
});

const main = async () => {
const promptArgument: PromptLike | null = PROMPT_VERSION_ID
? { versionId: PROMPT_VERSION_ID }
: PROMPT_TAG && PROMPT_NAME
? { name: PROMPT_NAME, tag: PROMPT_TAG }
: PROMPT_NAME
? { name: PROMPT_NAME }
: null;
if (!promptArgument) {
throw new Error(
`Either PROMPT_VERSION_ID, PROMPT_TAG and PROMPT_NAME, or PROMPT_NAME must be provided in the environment`
);
}
console.log(`Getting prompt ${PROMPT_VERSION_ID}`);

// TODO: Apply variable replacement to the prompt
const prompt = await getPrompt({
client,
prompt: promptArgument,
});

if (!prompt) {
throw new Error("Prompt not found");
}

console.log(
`Loaded prompt: ${prompt.id}\n${prompt.description ? `\n${prompt.description}` : ""}`
);

console.log(`Converting prompt to OpenAI params`);

const anthropicParams = toSDK({
prompt,
sdk: "anthropic",
variables: {
question,
},
});

if (!anthropicParams) {
throw new Error("Prompt could not be converted to Anthropic params");
}

// @ts-expect-error Anthropic doesn't support these parameters
delete anthropicParams.frequency_penalty;
// @ts-expect-error Anthropic doesn't support these parameters
delete anthropicParams.presence_penalty;

console.log(`Applying prompt to Anthropic`);
const response = await anthropic.messages.create({
...anthropicParams,
// we may not have an anthropic model saved in the prompt
model: "claude-3-5-sonnet-20240620",
// TODO: should this be strongly typed inside of toSDK results if sdk: "anthropic"?
stream: true,
});

console.log(`Streaming response from OpenAI:\n\n`);

let responseText = "";
let responseJson = "";
for await (const chunk of response) {
if (chunk.type === "message_delta") {
console.clear();
console.log("Input:\n");
console.log(JSON.stringify(anthropicParams.messages, null, 2));
console.log("\nOutput:\n");
try {
console.log(JSON.stringify(JSON.parse(responseText), null, 2));
console.log(JSON.stringify(JSON.parse(responseJson), null, 2));
} catch {
console.log(responseText);
console.log(responseJson);
}
} else if (chunk.type === "content_block_delta") {
console.clear();
if (chunk.delta.type === "text_delta") {
responseText += String(chunk.delta.text);
}
if (chunk.delta.type === "input_json_delta") {
responseJson += chunk.delta.partial_json;
}
console.log(responseText);
}
}

console.log("\n\n");
console.log(`Done!`);
};

main();
82 changes: 79 additions & 3 deletions js/packages/phoenix-client/src/prompts/sdks/toAnthropic.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,88 @@
import type { MessageCreateParams } from "@anthropic-ai/sdk/resources/messages/messages";
import type {
MessageCreateParams,
MessageParam,
} from "@anthropic-ai/sdk/resources/messages/messages";
import type { toSDKParamsBase } from "./types";
import { promptMessageToAnthropic } from "../../schemas/llm/messageSchemas";
import { promptMessageFormatter } from "../../utils/promptMessageFormatter";
import {
AnthropicToolChoice,
safelyConvertToolChoiceToProvider,
} from "../../schemas/llm/toolChoiceSchemas";
import {
fromOpenAIToolDefinition,
toOpenAIToolDefinition,
} from "../../schemas/llm/toolSchemas";
import invariant from "tiny-invariant";

export type { MessageCreateParams };

export type ToAnthropicParams = toSDKParamsBase;

export const toAnthropic = ({
prompt: _prompt,
prompt,
variables,
}: ToAnthropicParams): MessageCreateParams | null => {
return null;
try {
const { tool_choice: initialToolChoice, ...invocationParameters } =
prompt.invocation_parameters as unknown as Record<string, unknown> & {
tool_choice?: AnthropicToolChoice;
max_tokens: number;
};
// parts of the prompt that can be directly converted to Anthropic params
const baseCompletionParams = {
model: prompt.model_name,
...invocationParameters,
} satisfies Partial<MessageCreateParams>;

if (!("messages" in prompt.template)) {
return null;
}

let formattedMessages = prompt.template.messages;

if (variables) {
formattedMessages = promptMessageFormatter(
prompt.template_format,
formattedMessages,
variables
);
}

const messages = formattedMessages.map((message) =>
promptMessageToAnthropic.parse(message)
) as MessageParam[];

const tools = prompt.tools?.tool_definitions.map((tool) => {
const openaiDefinition = toOpenAIToolDefinition(tool.definition);
invariant(openaiDefinition, "Tool definition is not valid");
return fromOpenAIToolDefinition({
toolDefinition: openaiDefinition,
targetProvider: "ANTHROPIC",
});
});

const tool_choice = initialToolChoice
? (safelyConvertToolChoiceToProvider({
toolChoice: initialToolChoice,
targetProvider: "ANTHROPIC",
}) ?? undefined)
: undefined;

// combine base and computed params
const completionParams = {
...baseCompletionParams,
messages,
tools: (tools?.length ?? 0) > 0 ? tools : undefined,
tool_choice,
} satisfies Partial<MessageCreateParams>;

return completionParams;
} catch (e) {
// eslint-disable-next-line no-console
console.warn(`Failed to convert prompt to Anthropic params`);
// eslint-disable-next-line no-console
console.error(e);
return null;
}
};
22 changes: 16 additions & 6 deletions js/packages/phoenix-client/src/schemas/llm/toolChoiceSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@ export type OpenaiToolChoice = z.infer<typeof openAIToolChoiceSchema>;
*
* @see https://docs.anthropic.com/en/api/messages
*/
export const anthropicToolChoiceSchema = z.object({
type: z.union([z.literal("auto"), z.literal("any"), z.literal("tool")]),
disable_parallel_tool_use: z.boolean().optional(),
name: z.string().optional(),
});
export const anthropicToolChoiceSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("tool"),
name: z.string(),
disable_parallel_tool_use: z.boolean().optional(),
}),
z.object({
type: z.literal("auto"),
disable_parallel_tool_use: z.boolean().optional(),
}),
z.object({
type: z.literal("any"),
disable_parallel_tool_use: z.boolean().optional(),
}),
]);

export type AnthropicToolChoice = z.infer<typeof anthropicToolChoiceSchema>;

Expand Down Expand Up @@ -58,7 +68,7 @@ export const openAIToolChoiceToAnthropicToolChoice =
}
return {
type: "tool",
name: openAI.function.name,
name: openAI.function.name ?? "",
};
});

Expand Down

0 comments on commit d518d54

Please sign in to comment.