Skip to content

Commit

Permalink
Merge pull request #48 from langchain-ai/brace/handle-invalid-responses
Browse files Browse the repository at this point in the history
fix: Handle invalid human responses
  • Loading branch information
bracesproul authored Jan 7, 2025
2 parents e1af37d + 662c621 commit 1ce791d
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 5 deletions.
12 changes: 11 additions & 1 deletion src/agents/generate-post/generate-post-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ function routeAfterGeneratingReport(

function rewriteOrEndConditionalEdge(
state: typeof GeneratePostAnnotation.State,
): "rewritePost" | "schedulePost" | "updateScheduleDate" | typeof END {
):
| "rewritePost"
| "schedulePost"
| "updateScheduleDate"
| "humanNode"
| typeof END {
if (state.next) {
if (state.next === "unknownResponse") {
// If the user's response is unknown, we should route back to the human node.
return "humanNode";
}
return state.next;
}
return END;
Expand Down Expand Up @@ -120,6 +129,7 @@ const generatePostBuilder = new StateGraph(
"rewritePost",
"schedulePost",
"updateScheduleDate",
"humanNode",
END,
])
// Always end after scheduling the post.
Expand Down
1 change: 1 addition & 0 deletions src/agents/generate-post/generate-post-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const GeneratePostAnnotation = Annotation.Root({
| "schedulePost"
| "rewritePost"
| "updateScheduleDate"
| "unknownResponse"
| typeof END
| undefined
>,
Expand Down
34 changes: 32 additions & 2 deletions src/agents/generate-post/nodes/human-node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { routeResponse } from "./route-response.js";

interface ConstructDescriptionArgs {
unknownResponseDescription: string;
report: string;
originalLink: string;
relevantLinks: string[];
Expand All @@ -18,6 +19,7 @@ interface ConstructDescriptionArgs {
}

function constructDescription({
unknownResponseDescription,
report,
originalLink,
relevantLinks,
Expand All @@ -29,7 +31,10 @@ function constructDescription({
? `## Image Options\n\nThe following image options are available. Select one by copying and pasting the URL into the 'image' field.\n\n${imageOptions.map((url) => `URL: ${url}\nImage: <details><summary>Click to view image</summary>\n\n![](${url})\n</details>\n`).join("\n")}`
: "";

return `# Schedule post
const unknownResponseString = unknownResponseDescription
? `${unknownResponseDescription}\n\n`
: "";
return `${unknownResponseString}# Schedule post
Using these URL(s), a post was generated for Twitter/LinkedIn:
${linksText}
Expand All @@ -48,6 +53,7 @@ There are a few different actions which can be taken:\n
- **Response**: If a response is sent, it will be sent to a router which can be routed to either
1. A node which will be used to rewrite the post. Please note, the response will be used as the 'user' message in an LLM call to rewrite the post, so ensure your response is properly formatted.
2. A node which will be used to update the scheduled date for the post.
If an unknown/invalid response is sent, nothing will happen, and it will be routed back to the human node.
- **Accept**: If 'accept' is selected, the post will be scheduled for Twitter/LinkedIn.
- **Ignore**: If 'ignore' is selected, this post will not be scheduled, and the thread will end.
Expand Down Expand Up @@ -76,6 +82,21 @@ Here is the report that was generated for the posts:\n${report}
`;
}

const getUnknownResponseDescription = (
state: typeof GeneratePostAnnotation.State,
) => {
if (state.next === "unknownResponse" && state.userResponse) {
return `# <div style="color: red;">UNKNOWN/INVALID RESPONSE RECEIVED: '${state.userResponse}'</div>
<div style="color: red;">Please respond with either a request to update/rewrite the post, or a valid priority level or a date to schedule the post.</div>
<div style="color: red;">See the \`Schedule Date\`, or \`Instructions\` sections for more information.</div>
<hr />`;
}
return "";
};

export async function humanNode(
state: typeof GeneratePostAnnotation.State,
_config: LangGraphRunnableConfig,
Expand All @@ -84,6 +105,7 @@ export async function humanNode(
throw new Error("No post found");
}

const unknownResponseDescription = getUnknownResponseDescription(state);
const defaultDate = state.scheduleDate || getNextSaturdayDate();
let defaultDateString = "";
if (
Expand Down Expand Up @@ -121,6 +143,7 @@ export async function humanNode(
relevantLinks: state.relevantLinks,
post: state.post,
imageOptions: state.imageOptions,
unknownResponseDescription,
}),
};

Expand Down Expand Up @@ -161,10 +184,16 @@ export async function humanNode(
next: "rewritePost",
};
}
if (route === "update_date") {
return {
userResponse: response.args,
next: "updateScheduleDate",
};
}

return {
userResponse: response.args,
next: "updateScheduleDate",
next: "unknownResponse",
};
}

Expand Down Expand Up @@ -213,5 +242,6 @@ export async function humanNode(
next: "schedulePost",
scheduleDate: postDate,
image: imageState,
userResponse: undefined,
};
}
16 changes: 14 additions & 2 deletions src/agents/generate-post/nodes/human-node/route-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ Carefully analyze the user's response:
Based on the user's response, determine which of the two routes they intend to take. Consider the following:
1. If the user mentions editing, changing, or rewriting the content of the post, choose the "rewrite_post" route.
2. If the user mentions changing the date, time, or priority level of the post, choose the "update_date" route.
2. If the user mentions changing the date, time, or priority level of the post, choose the "update_date" route. Ensure you only call this if the user mentions a date, or one of P1, P2 or P3.
If the user's response can not be handled by one of the two routes, choose the "unknown_response" route.
Provide your answer in the following format:
<explanation>
Expand All @@ -49,6 +51,16 @@ User: "This should be a P1 priority."
Route: update_date
Explanation: The user wants to change the priority level of the post.
Example 4:
User: "This should be a P0 priority."
Route: unknown_response
Explanation: P0 is not a valid priority level.
Example 5:
User: "Hi! How are you?"
Route: unknown_response
Explanation: The user is engaging in general conversation, not a request to change the post.
Remember to always base your decision on the actual content of the user's response, not on these examples.`;

interface RouteResponseArgs {
Expand All @@ -68,7 +80,7 @@ export async function routeResponse({
});

const routeSchema = z.object({
route: z.enum(["rewrite_post", "update_date"]),
route: z.enum(["rewrite_post", "update_date", "unknown_response"]),
});
const modelWithSchema = model.withStructuredOutput(routeSchema, {
name: "route",
Expand Down
1 change: 1 addition & 0 deletions src/agents/generate-post/nodes/rewrite-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,6 @@ export async function rewritePost(
return {
post: revisePostResponse.content as string,
next: undefined,
userResponse: undefined,
};
}

0 comments on commit 1ce791d

Please sign in to comment.