Skip to content

Commit 293e26e

Browse files
MH4GFclaude
andcommitted
refactor: restructure PM Analysis Agent as a LangGraph subgraph
- Replace PMAnalysisAgent class with functional invokePmAnalysisAgent - Create dedicated PM Agent subgraph with createPmAgentGraph - Move PM Agent nodes to pm-agent/nodes directory for better organization - Add proper retry policies for PM Agent nodes - Simplify main graph by using PM Agent as a subgraph - Update tests and documentation to reflect new architecture This change aligns the PM Agent architecture with the DB Agent pattern, making the codebase more consistent and maintainable. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c6eeb83 commit 293e26e

File tree

11 files changed

+173
-115
lines changed

11 files changed

+173
-115
lines changed

frontend/internal-packages/agent/src/chat/workflow/README.md

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,19 @@ A **LangGraph implementation** for processing chat messages in the LIAM applicat
88
%%{init: {'flowchart': {'curve': 'linear'}}}%%
99
graph TD;
1010
__start__([<p>__start__</p>]):::first
11-
analyzeRequirements(analyzeRequirements)
12-
invokeSaveArtifactTool(invokeSaveArtifactTool)
11+
pmAgent(pmAgent)
1312
dbAgent(dbAgent)
1413
generateUsecase(generateUsecase)
1514
prepareDML(prepareDML)
1615
validateSchema(validateSchema)
1716
finalizeArtifacts(finalizeArtifacts)
1817
__end__([<p>__end__</p>]):::last
19-
__start__ --> analyzeRequirements;
18+
__start__ --> pmAgent;
2019
dbAgent --> generateUsecase;
2120
finalizeArtifacts --> __end__;
2221
generateUsecase --> prepareDML;
23-
invokeSaveArtifactTool --> analyzeRequirements;
22+
pmAgent --> dbAgent;
2423
prepareDML --> validateSchema;
25-
analyzeRequirements -.-> invokeSaveArtifactTool;
26-
analyzeRequirements -.-> dbAgent;
2724
validateSchema -.-> dbAgent;
2825
validateSchema -.-> finalizeArtifacts;
2926
classDef default fill:#f2f0ff,line-height:1.2;
@@ -76,13 +73,53 @@ interface WorkflowState {
7673

7774
## Nodes
7875

79-
1. **analyzeRequirements**: Organizes and clarifies requirements from user input (performed by pmAnalysisAgent)
80-
2. **saveRequirementToArtifact**: Processes analyzed requirements, saves artifacts to database, and syncs timeline (performed by pmAgent)
81-
3. **dbAgent**: DB Agent subgraph that handles database schema design - contains designSchema and invokeSchemaDesignTool nodes (performed by dbAgent)
82-
4. **generateUsecase**: Creates use cases for testing with automatic timeline sync (performed by qaAgent)
83-
5. **prepareDML**: Generates DML statements for testing (performed by qaAgent)
84-
6. **validateSchema**: Executes DML and validates schema (performed by qaAgent)
85-
7. **finalizeArtifacts**: Generates and saves comprehensive artifacts to database, handles error timeline items (performed by dbAgentArtifactGen)
76+
1. **pmAgent**: PM Agent subgraph that handles requirements analysis - contains analyzeRequirements and invokeSaveArtifactTool nodes
77+
2. **dbAgent**: DB Agent subgraph that handles database schema design - contains designSchema and invokeSchemaDesignTool nodes (performed by dbAgent)
78+
3. **generateUsecase**: Creates use cases for testing with automatic timeline sync (performed by qaAgent)
79+
4. **prepareDML**: Generates DML statements for testing (performed by qaAgent)
80+
5. **validateSchema**: Executes DML and validates schema (performed by qaAgent)
81+
6. **finalizeArtifacts**: Generates and saves comprehensive artifacts to database, handles error timeline items (performed by dbAgentArtifactGen)
82+
83+
## PM Agent Subgraph
84+
85+
The `pmAgent` node is implemented as a **LangGraph subgraph** that encapsulates all requirements analysis and artifact management logic as an independent, reusable component following multi-agent system best practices.
86+
87+
### PM Agent Architecture
88+
89+
```mermaid
90+
%%{init: {'flowchart': {'curve': 'linear'}}}%%
91+
graph TD;
92+
__start__([<p>__start__</p>]):::first
93+
analyzeRequirements(analyzeRequirements)
94+
invokeSaveArtifactTool(invokeSaveArtifactTool)
95+
__end__([<p>__end__</p>]):::last
96+
__start__ --> analyzeRequirements;
97+
invokeSaveArtifactTool --> analyzeRequirements;
98+
analyzeRequirements -.-> invokeSaveArtifactTool;
99+
analyzeRequirements -.-> __end__;
100+
classDef default fill:#f2f0ff,line-height:1.2;
101+
classDef first fill-opacity:0;
102+
classDef last fill:#bfb6fc;
103+
```
104+
105+
### PM Agent Components
106+
107+
#### 1. analyzeRequirements Node
108+
- **Purpose**: Analyzes and structures user requirements into BRDs
109+
- **Performed by**: PM Analysis Agent with GPT-5
110+
- **Retry Policy**: maxAttempts: 3 (internal to subgraph)
111+
- **Timeline Sync**: Automatic message synchronization
112+
113+
#### 2. invokeSaveArtifactTool Node
114+
- **Purpose**: Saves analyzed requirements as artifacts to database
115+
- **Performed by**: saveRequirementsToArtifactTool
116+
- **Retry Policy**: maxAttempts: 3 (internal to subgraph)
117+
- **Tool Integration**: Direct database artifact storage
118+
119+
### PM Agent Flow Patterns
120+
121+
1. **Simple Analysis**: `START → analyzeRequirements → END` (when requirements are fully analyzed)
122+
2. **Iterative Saving**: `START → analyzeRequirements → invokeSaveArtifactTool → analyzeRequirements → ... → END`
86123

87124
## DB Agent Subgraph
88125

frontend/internal-packages/agent/src/chat/workflow/nodes/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
export { invokeSaveArtifactToolNode } from '../../../pm-agent/nodes/invokeSaveArtifactToolNode'
2-
export { analyzeRequirementsNode } from './analyzeRequirementsNode'
31
export { finalizeArtifactsNode } from './finalizeArtifactsNode'
42
export { generateUsecaseNode } from './generateUsecaseNode'
53
export { prepareDmlNode } from './prepareDmlNode'

frontend/internal-packages/agent/src/createGraph.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,19 @@ describe('createGraph', () => {
77
const expectedMermaidDiagram = `%%{init: {'flowchart': {'curve': 'linear'}}}%%
88
graph TD;
99
__start__([<p>__start__</p>]):::first
10-
analyzeRequirements(analyzeRequirements)
11-
invokeSaveArtifactTool(invokeSaveArtifactTool)
10+
pmAgent(pmAgent)
1211
dbAgent(dbAgent)
1312
generateUsecase(generateUsecase)
1413
prepareDML(prepareDML)
1514
validateSchema(validateSchema)
1615
finalizeArtifacts(finalizeArtifacts)
1716
__end__([<p>__end__</p>]):::last
18-
__start__ --> analyzeRequirements;
17+
__start__ --> pmAgent;
1918
dbAgent --> generateUsecase;
2019
finalizeArtifacts --> __end__;
2120
generateUsecase --> prepareDML;
22-
invokeSaveArtifactTool --> analyzeRequirements;
21+
pmAgent --> dbAgent;
2322
prepareDML --> validateSchema;
24-
analyzeRequirements -.-> invokeSaveArtifactTool;
25-
analyzeRequirements -.-> dbAgent;
2623
validateSchema -.-> dbAgent;
2724
validateSchema -.-> finalizeArtifacts;
2825
classDef default fill:#f2f0ff,line-height:1.2;

frontend/internal-packages/agent/src/createGraph.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import { END, START, StateGraph } from '@langchain/langgraph'
22
import type { BaseCheckpointSaver } from '@langchain/langgraph-checkpoint'
33
import {
4-
analyzeRequirementsNode,
54
finalizeArtifactsNode,
65
generateUsecaseNode,
7-
invokeSaveArtifactToolNode,
86
prepareDmlNode,
97
validateSchemaNode,
108
} from './chat/workflow/nodes'
119
import { createAnnotations } from './chat/workflow/shared/langGraphUtils'
1210
import { createDbAgentGraph } from './db-agent/createDbAgentGraph'
13-
import { routeAfterAnalyzeRequirements } from './pm-agent/routing/routeAfterAnalyzeRequirements'
11+
import { createPmAgentGraph } from './pm-agent/createPmAgentGraph'
1412
import { RETRY_POLICY } from './shared/errorHandling'
1513

1614
/**
@@ -21,17 +19,11 @@ import { RETRY_POLICY } from './shared/errorHandling'
2119
export const createGraph = (checkpointer?: BaseCheckpointSaver) => {
2220
const ChatStateAnnotation = createAnnotations()
2321
const graph = new StateGraph(ChatStateAnnotation)
24-
25-
// Create DB Agent subgraph with checkpoint support
22+
const pmAgentSubgraph = createPmAgentGraph(checkpointer)
2623
const dbAgentSubgraph = createDbAgentGraph(checkpointer)
2724

2825
graph
29-
.addNode('analyzeRequirements', analyzeRequirementsNode, {
30-
retryPolicy: RETRY_POLICY,
31-
})
32-
.addNode('invokeSaveArtifactTool', invokeSaveArtifactToolNode, {
33-
retryPolicy: RETRY_POLICY,
34-
})
26+
.addNode('pmAgent', pmAgentSubgraph)
3527
.addNode('dbAgent', dbAgentSubgraph)
3628
.addNode('generateUsecase', generateUsecaseNode, {
3729
retryPolicy: RETRY_POLICY,
@@ -46,19 +38,13 @@ export const createGraph = (checkpointer?: BaseCheckpointSaver) => {
4638
retryPolicy: RETRY_POLICY,
4739
})
4840

49-
.addEdge(START, 'analyzeRequirements')
50-
.addEdge('invokeSaveArtifactTool', 'analyzeRequirements')
41+
.addEdge(START, 'pmAgent')
42+
.addEdge('pmAgent', 'dbAgent')
5143
.addEdge('dbAgent', 'generateUsecase')
5244
.addEdge('generateUsecase', 'prepareDML')
5345
.addEdge('prepareDML', 'validateSchema')
5446
.addEdge('finalizeArtifacts', END)
5547

56-
// Conditional edges for requirements analysis
57-
.addConditionalEdges('analyzeRequirements', routeAfterAnalyzeRequirements, {
58-
invokeSaveArtifactTool: 'invokeSaveArtifactTool',
59-
dbAgent: 'dbAgent',
60-
})
61-
6248
// Conditional edges for validation results
6349
.addConditionalEdges(
6450
'validateSchema',
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export { PMAnalysisAgent } from './pmAnalysisAgent'
21
export { QAGenerateUsecaseAgent } from './qaGenerateUsecaseAgent'

frontend/internal-packages/agent/src/langchain/agents/pmAnalysisAgent/agent.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

frontend/internal-packages/agent/src/langchain/agents/pmAnalysisAgent/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { END, START, StateGraph } from '@langchain/langgraph'
2+
import type { BaseCheckpointSaver } from '@langchain/langgraph-checkpoint'
3+
import { createAnnotations } from '../chat/workflow/shared/langGraphUtils'
4+
import { analyzeRequirementsNode } from './nodes/analyzeRequirementsNode'
5+
import { invokeSaveArtifactToolNode } from './nodes/invokeSaveArtifactToolNode'
6+
import { routeAfterAnalyzeRequirements } from './routing/routeAfterAnalyzeRequirements'
7+
8+
/**
9+
* Retry policy configuration for PM Agent nodes
10+
*/
11+
const RETRY_POLICY = {
12+
maxAttempts: process.env['NODE_ENV'] === 'test' ? 1 : 3,
13+
}
14+
15+
/**
16+
* Create and configure the PM Agent subgraph for requirements analysis
17+
*
18+
* The PM Agent handles the requirements analysis process:
19+
* 1. analyzeRequirements - Analyzes and structures user requirements
20+
* 2. invokeSaveArtifactTool - Saves requirements as artifacts using tools
21+
* 3. Loop between analysis and tool invocation until requirements are saved
22+
*
23+
* @param checkpointer - Optional checkpoint saver for persistent state management
24+
*/
25+
export const createPmAgentGraph = (checkpointer?: BaseCheckpointSaver) => {
26+
const ChatStateAnnotation = createAnnotations()
27+
const pmAgentGraph = new StateGraph(ChatStateAnnotation)
28+
29+
pmAgentGraph
30+
.addNode('analyzeRequirements', analyzeRequirementsNode, {
31+
retryPolicy: RETRY_POLICY,
32+
})
33+
.addNode('invokeSaveArtifactTool', invokeSaveArtifactToolNode, {
34+
retryPolicy: RETRY_POLICY,
35+
})
36+
37+
.addEdge(START, 'analyzeRequirements')
38+
.addEdge('invokeSaveArtifactTool', 'analyzeRequirements')
39+
.addConditionalEdges('analyzeRequirements', routeAfterAnalyzeRequirements, {
40+
invokeSaveArtifactTool: 'invokeSaveArtifactTool',
41+
dbAgent: END,
42+
})
43+
44+
return checkpointer
45+
? pmAgentGraph.compile({ checkpointer })
46+
: pmAgentGraph.compile()
47+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
type AIMessage,
3+
type BaseMessage,
4+
SystemMessage,
5+
} from '@langchain/core/messages'
6+
import { ChatOpenAI } from '@langchain/openai'
7+
import { ok, ResultAsync } from 'neverthrow'
8+
import * as v from 'valibot'
9+
import type { WorkflowConfigurable } from '../chat/workflow/types'
10+
import { reasoningSchema } from '../langchain/utils/schema'
11+
import type { Reasoning } from '../langchain/utils/types'
12+
import { removeReasoningFromMessages } from '../utils/messageCleanup'
13+
import { PM_ANALYSIS_SYSTEM_MESSAGE } from './prompts/pmAnalysisPrompts'
14+
import { saveRequirementsToArtifactTool } from './tools/saveRequirementsToArtifactTool'
15+
16+
type AnalysisWithReasoning = {
17+
response: AIMessage
18+
reasoning: Reasoning | null
19+
}
20+
21+
/**
22+
* Invoke PM Analysis Agent to analyze user requirements and extract structured BRDs
23+
* This function replaces the PMAnalysisAgent class with a simpler functional approach
24+
*/
25+
export const invokePmAnalysisAgent = (
26+
messages: BaseMessage[],
27+
configurable: WorkflowConfigurable,
28+
): ResultAsync<AnalysisWithReasoning, Error> => {
29+
const cleanedMessages = removeReasoningFromMessages(messages)
30+
31+
const allMessages: BaseMessage[] = [
32+
new SystemMessage(PM_ANALYSIS_SYSTEM_MESSAGE),
33+
...cleanedMessages,
34+
]
35+
36+
const model = new ChatOpenAI({
37+
model: 'gpt-5',
38+
reasoning: { effort: 'high', summary: 'detailed' },
39+
useResponsesApi: true,
40+
}).bindTools(
41+
[{ type: 'web_search_preview' }, saveRequirementsToArtifactTool],
42+
{
43+
parallel_tool_calls: false,
44+
},
45+
)
46+
47+
return ResultAsync.fromPromise(
48+
model.invoke(allMessages, { configurable }),
49+
(error) => (error instanceof Error ? error : new Error(String(error))),
50+
).andThen((response) => {
51+
const parsedReasoning = v.safeParse(
52+
reasoningSchema,
53+
response.additional_kwargs['reasoning'],
54+
)
55+
const reasoning = parsedReasoning.success ? parsedReasoning.output : null
56+
57+
return ok({
58+
response,
59+
reasoning,
60+
})
61+
})
62+
}

frontend/internal-packages/agent/src/chat/workflow/nodes/analyzeRequirementsNode.ts renamed to frontend/internal-packages/agent/src/pm-agent/nodes/analyzeRequirementsNode.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { RunnableConfig } from '@langchain/core/runnables'
22
import type { Database } from '@liam-hq/db'
3-
import { PMAnalysisAgent } from '../../../langchain/agents'
4-
import { WorkflowTerminationError } from '../../../shared/errorHandling'
5-
import { getConfigurable } from '../shared/getConfigurable'
6-
import type { WorkflowState } from '../types'
7-
import { logAssistantMessage } from '../utils/timelineLogger'
3+
import { getConfigurable } from '../../chat/workflow/shared/getConfigurable'
4+
import type { WorkflowState } from '../../chat/workflow/types'
5+
import { logAssistantMessage } from '../../chat/workflow/utils/timelineLogger'
6+
import { WorkflowTerminationError } from '../../shared/errorHandling'
7+
import { invokePmAnalysisAgent } from '../invokePmAnalysisAgent'
88

99
/**
1010
* Analyze Requirements Node - Requirements Organization
@@ -31,9 +31,7 @@ export async function analyzeRequirementsNode(
3131
assistantRole,
3232
)
3333

34-
const pmAnalysisAgent = new PMAnalysisAgent()
35-
36-
const analysisResult = await pmAnalysisAgent.generate(
34+
const analysisResult = await invokePmAnalysisAgent(
3735
state.messages,
3836
configurableResult.value,
3937
)

0 commit comments

Comments
 (0)