Merge pull request #38 from langchain-ai/brace/custom-parsing-anthropic

fix: Run formatting
This commit is contained in:
Brace Sproul
2025-03-07 16:58:45 -08:00
committed by GitHub
4 changed files with 4378 additions and 2169 deletions

View File

@@ -23,7 +23,13 @@ ${allToolDescriptions}
`;
const routerSchema = z.object({
route: z
.enum(["stockbroker", "tripPlanner", "openCode", "orderPizza", "generalInput"])
.enum([
"stockbroker",
"tripPlanner",
"openCode",
"orderPizza",
"generalInput",
])
.describe(routerDescription),
});
const routerTool = {

View File

@@ -7,7 +7,7 @@ import { v4 as uuidv4 } from "uuid";
const PizzaOrdererAnnotation = Annotation.Root({
messages: GenerativeUIAnnotation.spec.messages,
})
});
async function sleep(ms = 5000) {
return new Promise((resolve) => setTimeout(resolve, ms));
@@ -15,71 +15,99 @@ async function sleep(ms = 5000) {
const workflow = new StateGraph(PizzaOrdererAnnotation)
.addNode("findStore", async (state) => {
const findShopSchema = z.object({
location: z.string().describe("The location the user is in. E.g. 'San Francisco' or 'New York'"),
pizza_company: z.string().optional().describe("The name of the pizza company. E.g. 'Dominos' or 'Papa John's'. Optional, if not defined it will search for all pizza shops"),
}).describe("The schema for finding a pizza shop for the user")
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest", temperature: 0 }).withStructuredOutput(findShopSchema, {
const findShopSchema = z
.object({
location: z
.string()
.describe(
"The location the user is in. E.g. 'San Francisco' or 'New York'",
),
pizza_company: z
.string()
.optional()
.describe(
"The name of the pizza company. E.g. 'Dominos' or 'Papa John's'. Optional, if not defined it will search for all pizza shops",
),
})
.describe("The schema for finding a pizza shop for the user");
const model = new ChatAnthropic({
model: "claude-3-5-sonnet-latest",
temperature: 0,
}).withStructuredOutput(findShopSchema, {
name: "find_pizza_shop",
includeRaw: true,
})
});
const response = await model.invoke([
{
role: "system",
content: "You are a helpful AI assistant, tasked with extracting information from the conversation between you, and the user, in order to find a pizza shop for them."
content:
"You are a helpful AI assistant, tasked with extracting information from the conversation between you, and the user, in order to find a pizza shop for them.",
},
...state.messages,
])
]);
await sleep();
const toolResponse: ToolMessage = {
type: "tool",
id: uuidv4(),
content: "I've found a pizza shop at 1119 19th St, San Francisco, CA 94107. The phone number for the shop is 415-555-1234.",
tool_call_id: (response.raw as unknown as AIMessage).tool_calls?.[0].id ?? "",
}
content:
"I've found a pizza shop at 1119 19th St, San Francisco, CA 94107. The phone number for the shop is 415-555-1234.",
tool_call_id:
(response.raw as unknown as AIMessage).tool_calls?.[0].id ?? "",
};
return {
messages: [response.raw, toolResponse]
}
messages: [response.raw, toolResponse],
};
})
.addNode("orderPizza", async (state) => {
await sleep(1500);
const placeOrderSchema = z.object({
address: z.string().describe("The address of the store to order the pizza from"),
phone_number: z.string().describe("The phone number of the store to order the pizza from"),
order: z.string().describe("The full pizza order for the user"),
}).describe("The schema for ordering a pizza for the user")
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest", temperature: 0 }).withStructuredOutput(placeOrderSchema, {
const placeOrderSchema = z
.object({
address: z
.string()
.describe("The address of the store to order the pizza from"),
phone_number: z
.string()
.describe("The phone number of the store to order the pizza from"),
order: z.string().describe("The full pizza order for the user"),
})
.describe("The schema for ordering a pizza for the user");
const model = new ChatAnthropic({
model: "claude-3-5-sonnet-latest",
temperature: 0,
}).withStructuredOutput(placeOrderSchema, {
name: "place_pizza_order",
includeRaw: true,
})
});
const response = await model.invoke([
{
role: "system",
content: "You are a helpful AI assistant, tasked with placing an order for a pizza for the user."
content:
"You are a helpful AI assistant, tasked with placing an order for a pizza for the user.",
},
...state.messages,
])
]);
const toolResponse: ToolMessage = {
type: "tool",
id: uuidv4(),
content: "Pizza order successfully placed.",
tool_call_id: (response.raw as unknown as AIMessage).tool_calls?.[0].id ?? "",
}
tool_call_id:
(response.raw as unknown as AIMessage).tool_calls?.[0].id ?? "",
};
return {
messages: [response.raw, toolResponse]
}
messages: [response.raw, toolResponse],
};
})
.addEdge(START, "findStore")
.addEdge("findStore", "orderPizza")
.addEdge("orderPizza", END)
.addEdge("orderPizza", END);
export const graph = workflow.compile()
export const graph = workflow.compile();
graph.name = "Order Pizza Graph";

6411
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -46,15 +46,17 @@ function CustomComponent({
);
}
function parseAnthropicStreamedToolCalls(content: MessageContentComplex[]): AIMessage["tool_calls"] {
function parseAnthropicStreamedToolCalls(
content: MessageContentComplex[],
): AIMessage["tool_calls"] {
const toolCallContents = content.filter((c) => c.type === "tool_use" && c.id);
return toolCallContents.map((tc) => {
const toolCall = tc as Record<string, any>
const toolCall = tc as Record<string, any>;
let json: Record<string, any> = {};
if (toolCall?.input) {
try {
json = parsePartialJson(toolCall.input) ?? {}
json = parsePartialJson(toolCall.input) ?? {};
} catch {
// Pass
}
@@ -64,8 +66,8 @@ function parseAnthropicStreamedToolCalls(content: MessageContentComplex[]): AIMe
id: toolCall.id ?? "",
args: json,
type: "tool_call",
}
})
};
});
}
export function AssistantMessage({
@@ -82,13 +84,19 @@ export function AssistantMessage({
const thread = useStreamContext();
const meta = thread.getMessagesMetadata(message);
const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
const anthropicStreamedToolCalls = Array.isArray(message.content) ? parseAnthropicStreamedToolCalls(message.content) : undefined;
const anthropicStreamedToolCalls = Array.isArray(message.content)
? parseAnthropicStreamedToolCalls(message.content)
: undefined;
const hasToolCalls =
("tool_calls" in message &&
message.tool_calls &&
message.tool_calls.length > 0);
const toolCallsHaveContents = hasToolCalls && message.tool_calls?.some((tc) => tc.args && Object.keys(tc.args).length > 0);
"tool_calls" in message &&
message.tool_calls &&
message.tool_calls.length > 0;
const toolCallsHaveContents =
hasToolCalls &&
message.tool_calls?.some(
(tc) => tc.args && Object.keys(tc.args).length > 0,
);
const hasAnthropicToolCalls = !!anthropicStreamedToolCalls?.length;
const isToolResult = message.type === "tool";
@@ -103,9 +111,13 @@ export function AssistantMessage({
<MarkdownText>{contentString}</MarkdownText>
</div>
)}
{(hasToolCalls && toolCallsHaveContents && <ToolCalls toolCalls={message.tool_calls} />) ||
(hasAnthropicToolCalls && <ToolCalls toolCalls={anthropicStreamedToolCalls} />) ||
(hasToolCalls && <ToolCalls toolCalls={message.tool_calls} />)}
{(hasToolCalls && toolCallsHaveContents && (
<ToolCalls toolCalls={message.tool_calls} />
)) ||
(hasAnthropicToolCalls && (
<ToolCalls toolCalls={anthropicStreamedToolCalls} />
)) ||
(hasToolCalls && <ToolCalls toolCalls={message.tool_calls} />)}
<CustomComponent message={message} thread={thread} />
<div
className={cn(