Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement interrupts #12

Merged
merged 11 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion langgraph.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"node_version": "20",
"graphs": {
"social_media_agent": "./src/agent/graph.ts:graph"
"social_media_agent": "./src/agent/graph.ts:graph",
"generate_post": "./src/agent/subgraphs/generate-post/graph.ts:generatePostGraph"
},
"env": ".env",
"dependencies": ["."]
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@
"run:eval:youtube": "yarn tsx src/evals/youtube/index.ts"
},
"dependencies": {
"@arcadeai/arcadejs": "^0.1.2",
"@googleapis/youtube": "^20.0.0",
"@langchain/anthropic": "^0.3.8",
"@langchain/community": "^0.3.15",
"@langchain/core": "^0.3.18",
"@langchain/google-vertexai-web": "^0.1.2",
"@langchain/langgraph": "^0.2.22",
"@langchain/langgraph": "^0.2.23",
"@langchain/langgraph-sdk": "^0.0.29",
"@mendable/firecrawl-js": "0.0.36",
"@slack/web-api": "^7.7.0",
"cheerio": "^1.0.0",
"date-fns": "^4.1.0",
"google-auth-library": "^9.15.0",
"langsmith": "^0.2.7",
"moment": "^2.30.1",
Expand Down
64 changes: 64 additions & 0 deletions slack_message_inputs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[
{
"id": "e4fd6d66-7a47-4457-b532-14facfe93bb0",
"text": "<https://github.com/elizabethsiegle/wnba-analytics-dash-ai-insights/tree/main>",
"type": "message",
"user": "U04N0HGF869",
"links": [
"https://github.com/elizabethsiegle/wnba-analytics-dash-ai-insights/tree/main"
],
"timestamp": "1729953413.037949"
},
{
"id": "e4fd6d66-7a47-4457-b532-14facfe93bb0",
"text": "<https://github.com/ahmad2b/postbot3000>",
"type": "message",
"user": "U04N0HGF869",
"links": ["https://github.com/ahmad2b/postbot3000"],
"timestamp": "1729953413.037949"
},
{
"id": "e4fd6d66-7a47-4457-b532-14facfe93bb0",
"text": "<https://github.com/kaarthik108/snowChat>",
"type": "message",
"user": "U04N0HGF869",
"links": ["https://github.com/kaarthik108/snowChat"],
"timestamp": "1729953413.037949"
},
{
"id": "e4fd6d66-7a47-4457-b532-14facfe93bb0",
"text": "<https://github.com/samwit/agent_tutorials/tree/main/agent_write>",
"type": "message",
"user": "U04N0HGF869",
"links": [
"https://github.com/samwit/agent_tutorials/tree/main/agent_write"
],
"timestamp": "1729953413.037949"
},
{
"id": "e4fd6d66-7a47-4457-b532-14facfe93bb0",
"text": "<https://github.com/starpig1129/AI-Data-Analysis-MultiAgent>",
"type": "message",
"user": "U04N0HGF869",
"links": ["https://github.com/starpig1129/AI-Data-Analysis-MultiAgent"],
"timestamp": "1729953413.037949"
},
{
"id": "e4fd6d66-7a47-4457-b532-14facfe93bb0",
"text": "<https://x.com/eitanblumin/status/1861001933294653890>",
"type": "message",
"user": "U04N0HGF869",
"links": ["https://x.com/eitanblumin/status/1861001933294653890"],
"timestamp": "1729953413.037949"
},
{
"id": "e4fd6d66-7a47-4457-b532-14facfe93bb0",
"text": "<https://medium.com/@zallesov/stateful-routing-with-langgraph-6dc8edc798bd>",
"type": "message",
"user": "U04N0HGF869",
"links": [
"https://medium.com/@zallesov/stateful-routing-with-langgraph-6dc8edc798bd"
],
"timestamp": "1729953413.037949"
}
]
50 changes: 29 additions & 21 deletions src/agent/graph.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { END, Send, START, StateGraph } from "@langchain/langgraph";
import {
END,
LangGraphRunnableConfig,
START,
StateGraph,
} from "@langchain/langgraph";
import { ConfigurableAnnotation, GraphAnnotation } from "./state.js";
import { ingestData } from "./nodes/ingest-data.js";
import { generatePostGraph } from "./subgraphs/generate-post/graph.js";
import { Client } from "@langchain/langgraph-sdk";

/**
* Other thinking
* 1. ingest data
* 2. invoke a subgraph for each message
* inside the subgraph, identify the content, generate a report, generate a post, and schedule a post (interrupt beforehand)
*/

function routeAfterIdentifyContent(
async function generatePostFromMessages(
state: typeof GraphAnnotation.State,
): Array<Send> {
return state.slackMessages.flatMap((m) => {
config: LangGraphRunnableConfig,
) {
const client = new Client({
apiUrl: `http://localhost:${process.env.PORT}`,
});
for await (const m of state.slackMessages) {
if (m.links.length) {
return new Send("generatePostGraph", {
slackMessage: m,
const thread = await client.threads.create();
await client.runs.create(thread.thread_id, "generate_post", {
input: {
slackMessage: m,
},
config: {
configurable: {
twitterUserId: config.configurable?.twitterUserId,
linkedInUserId: config.configurable?.linkedInUserId,
},
},
});
}
return [];
});
}
return {};
}

const builder = new StateGraph(GraphAnnotation, ConfigurableAnnotation)
Expand All @@ -30,14 +41,11 @@ const builder = new StateGraph(GraphAnnotation, ConfigurableAnnotation)
// This subgraph will verify content is relevant to
// LangChain, generate a report on the content, and
// finally generate and schedule a post.
.addNode("generatePostGraph", generatePostGraph)

.addNode("generatePostGraph", generatePostFromMessages)
// Start node
.addEdge(START, "ingestData")
// After ingesting data, route to the subgraph for each message.
.addConditionalEdges("ingestData", routeAfterIdentifyContent, [
"generatePostGraph",
])
.addEdge("ingestData", "generatePostGraph")
// Finish after generating the Twitter post.
.addEdge("generatePostGraph", END);

Expand Down
9 changes: 8 additions & 1 deletion src/agent/nodes/ingest-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ const getChannelIdFromConfig = async (
};

export async function ingestData(
_state: typeof GraphAnnotation.State,
state: typeof GraphAnnotation.State,
config: LangGraphRunnableConfig,
): Promise<Partial<typeof GraphAnnotation.State>> {
if (config.configurable?.skipIngest) {
if (state.slackMessages.length === 0) {
throw new Error("Can not skip ingest with no messages");
}
return {};
}

const channelId = await getChannelIdFromConfig(config);
if (!channelId) {
throw new Error("Channel ID not found");
Expand Down
16 changes: 16 additions & 0 deletions src/agent/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,20 @@ export const ConfigurableAnnotation = Annotation.Root({
}),
slackChannelName: Annotation<string | undefined>,
slackChannelId: Annotation<string | undefined>,
/**
* Whether or not to skip ingesting messages from Slack.
* This will throw an error if slack messages are not
* pre-provided in state.
*/
skipIngest: Annotation<boolean>,
/**
* The user ID or email of the user to use for fetching
* & posting Tweets.
*/
twitterUserId: Annotation<string>,
/**
* The user ID or email of the user to use for fetching
* content & posting on LinkedIn.
*/
linkedInUserId: Annotation<string>,
});
67 changes: 67 additions & 0 deletions src/agent/subgraphs/generate-post/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
export const ALLOWED_DAYS = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
];

export const ALLOWED_TIMES = [
"8:00 AM",
"8:10 AM",
"8:20 AM",
"8:30 AM",
"8:40 AM",
"8:50 AM",
"9:00 AM",
"9:10 AM",
"9:20 AM",
"9:30 AM",
"9:40 AM",
"9:50 AM",
"10:00 AM",
"10:10 AM",
"10:20 AM",
"10:30 AM",
"10:40 AM",
"10:50 AM",
"11:00 AM",
"11:10 AM",
"11:20 AM",
"11:30 AM",
"11:40 AM",
"11:50 AM",
"12:00 PM",
"12:10 PM",
"12:20 PM",
"12:30 PM",
"12:40 PM",
"12:50 PM",
"1:00 PM",
"1:10 PM",
"1:20 PM",
"1:30 PM",
"1:40 PM",
"1:50 PM",
"2:00 PM",
"2:10 PM",
"2:20 PM",
"2:30 PM",
"2:40 PM",
"2:50 PM",
"3:00 PM",
"3:10 PM",
"3:20 PM",
"3:30 PM",
"3:40 PM",
"3:50 PM",
"4:00 PM",
"4:10 PM",
"4:20 PM",
"4:30 PM",
"4:40 PM",
"4:50 PM",
"5:00 PM",
];
31 changes: 28 additions & 3 deletions src/agent/subgraphs/generate-post/generate-post-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Annotation } from "@langchain/langgraph";
import { Annotation, END } from "@langchain/langgraph";
import {
GraphAnnotation as MainGraphAnnotation,
SimpleSlackMessageWithLinks,
Expand Down Expand Up @@ -48,7 +48,32 @@ export const GraphAnnotation = Annotation.Root({
default: () => [],
}),
/**
* The generated posts for LinkedIn/Twitter. Contains an array of different posts to choose from.
* The generated post for LinkedIn/Twitter.
*/
posts: Annotation<string[]>,
post: Annotation<string>,
/**
* The date to schedule the post for.
*/
scheduleDate: Annotation<Date>,
/**
* Response from the user for the post. Typically used to request
* changes to be made to the post.
*/
userResponse: Annotation<string>,
/**
* The node to execute next.
*/
next: Annotation<"schedulePost" | "rewritePost" | typeof END | undefined>,
});

export const ConfigurableAnnotation = Annotation.Root({
/**
* The user ID or email of the user to use for fetching & posting Tweets.
*/
twitterUserId: Annotation<string>,
/**
* The user ID or email of the user to use for fetching
* content & posting on LinkedIn.
*/
linkedInUserId: Annotation<string>,
});
Loading
Loading