Task Decomposition
Many real-world tasks are too complex for a single LLM call. The solution is to decompose them into multiple calls as a Flow of Nodes.
Example: Article Writing
This example demonstrates how to break down the task of writing an article into smaller, manageable steps using TypeScript and the pocket.ts
framework.
import { BaseNode, Flow, DEFAULT_ACTION } from "../src/pocket";
// Placeholder for an asynchronous LLM call
async function callLLM(prompt: string): Promise<string> {
// Replace with actual implementation, e.g., API call to an LLM service
return `Generated response based on prompt: ${prompt}`;
}
export class GenerateOutlineNode extends BaseNode {
// The 'prepAsync' method prepares the topic for generating an outline
public async prepAsync(sharedState: any): Promise<string> {
return sharedState.topic;
}
// The 'execAsync' method generates a detailed outline for the article
public async execAsync(topic: string): Promise<string> {
const prompt = `Create a detailed outline for an article about ${topic}`;
const outline = await callLLM(prompt);
return outline;
}
// The 'postAsync' method stores the generated outline in the shared store
public async postAsync(sharedState: any, _: string, execResult: string): Promise<string> {
sharedState.outline = execResult;
return DEFAULT_ACTION; // Proceed to the next node
}
}
export class WriteSectionNode extends BaseNode {
// The 'prepAsync' method retrieves the outline from the shared store
public async prepAsync(sharedState: any): Promise<string> {
return sharedState.outline;
}
// The 'execAsync' method writes content based on the outline
public async execAsync(outline: string): Promise<string> {
const prompt = `Write content based on this outline: ${outline}`;
const draft = await callLLM(prompt);
return draft;
}
// The 'postAsync' method stores the draft in the shared store
public async postAsync(sharedState: any, _: string, execResult: string): Promise<string> {
sharedState.draft = execResult;
return DEFAULT_ACTION; // Proceed to the next node
}
}
export class ReviewAndRefineNode extends BaseNode {
// The 'prepAsync' method retrieves the draft from the shared store
public async prepAsync(sharedState: any): Promise<string> {
return sharedState.draft;
}
// The 'execAsync' method reviews and improves the draft
public async execAsync(draft: string): Promise<string> {
const prompt = `Review and improve this draft: ${draft}`;
const finalArticle = await callLLM(prompt);
return finalArticle;
}
// The 'postAsync' method stores the final article in the shared store
public async postAsync(sharedState: any, _: string, execResult: string): Promise<string> {
sharedState.final_article = execResult;
return DEFAULT_ACTION; // Flow is complete
}
}
// Instantiate nodes
const generateOutline = new GenerateOutlineNode();
const writeSection = new WriteSectionNode();
const reviewAndRefine = new ReviewAndRefineNode();
// Connect nodes to form the flow: GenerateOutline -> WriteSection -> ReviewAndRefine
generateOutline.addSuccessor(writeSection, DEFAULT_ACTION);
writeSection.addSuccessor(reviewAndRefine, DEFAULT_ACTION);
// Create the flow starting with the GenerateOutline node
const writingFlow = new Flow(generateOutline);
// Define the shared state with the article topic
const shared = { topic: "AI Safety" };
// Run the flow
writingFlow.runAsync(shared).then(() => {
console.log("Final Article:", shared.final_article);
}).catch(error => {
console.error("Flow execution failed:", error);
});
Explanation:
- GenerateOutlineNode:
- prepAsync(sharedState):
- Retrieves the
topic
from thesharedState
to use in generating the outline.
- Retrieves the
- execAsync(topic):
- Calls the
callLLM
function with a prompt to create a detailed outline for the given topic.
- Calls the
-
postAsync(sharedState, _, execResult):
- Stores the generated outline in
sharedState.outline
. - Returns
DEFAULT_ACTION
to proceed to the next node in the flow.
- Stores the generated outline in
-
WriteSectionNode:
- prepAsync(sharedState):
- Retrieves the
outline
from thesharedState
.
- Retrieves the
- execAsync(outline):
- Calls the
callLLM
function with a prompt to write content based on the provided outline.
- Calls the
-
postAsync(sharedState, _, execResult):
- Stores the draft content in
sharedState.draft
. - Returns
DEFAULT_ACTION
to proceed to the next node.
- Stores the draft content in
-
ReviewAndRefineNode:
- prepAsync(sharedState):
- Retrieves the
draft
from thesharedState
.
- Retrieves the
- execAsync(draft):
- Calls the
callLLM
function with a prompt to review and improve the draft.
- Calls the
-
postAsync(sharedState, _, execResult):
- Stores the final article in
sharedState.final_article
. - Returns
DEFAULT_ACTION
, indicating that the flow is complete.
- Stores the final article in
-
Flow Setup:
- Node Instantiation:
- Creates instances of each node.
- Connecting Nodes:
- Sets up the flow by connecting the nodes in the order:
GenerateOutlineNode
→WriteSectionNode
→ReviewAndRefineNode
.
- Sets up the flow by connecting the nodes in the order:
- Running the Flow:
- Initializes the
shared
state with the article topic. - Runs the flow asynchronously and logs the final article upon completion.
- Initializes the
Notes:
- Asynchronous Operations:
-
All node methods (
prepAsync
,execAsync
,postAsync
) are asynchronous to handle operations like API calls to LLMs efficiently. -
Shared Store Usage:
-
The
sharedState
object serves as a global store, allowing nodes to share data seamlessly. -
Flow Execution:
-
The flow begins with generating an outline, proceeds to writing sections based on the outline, and culminates in reviewing and refining the draft to produce the final article.
-
Error Handling:
- Ensure that the
callLLM
function and other asynchronous operations include proper error handling to manage potential failures gracefully.
Feel free to adjust the code to fit your actual implementations and expand upon the task decomposition capabilities as required!