kamaleswar Mohanta commited on
Commit
cbeaa79
·
1 Parent(s): ee98254

Refactor SdlcNode for improved prompt handling and error logging; streamline user story and design document generation processes.

Browse files
src/langgraphagenticai/nodes/sdlc_node.py CHANGED
@@ -6,8 +6,8 @@ import json
6
  from datetime import datetime
7
  from typing import List
8
  from src.langgraphagenticai.logging.logging_utils import logger, log_entry_exit
9
- from src.langgraphagenticai.prompt_library import prompt
10
- import json
11
  from tenacity import retry, stop_after_attempt, wait_exponential
12
  import functools
13
  import time
@@ -16,7 +16,7 @@ import re
16
 
17
  class SdlcNode:
18
  def __init__(self, model):
19
- """Initialize the BlogGenerationNode with an LLM."""
20
  self.llm = model
21
 
22
  @log_entry_exit
@@ -51,21 +51,17 @@ class SdlcNode:
51
  "project_scope": state.project_scope if state.project_scope is not None else "No project scope provided",
52
  "project_objectives": state.project_objectives if state.project_objectives is not None else "No project objectives provided",
53
  }
54
- # Prepare dynamic input
55
  requirements_input_json = json.dumps(requirements_input, indent=2)
56
 
57
- # Render prompt string with runtime data
58
  prompt_string = prompt.REQUIREMENTS_PROMPT_STRING.format(requirements_input=requirements_input_json)
59
- sys_prompt = prompt.REQUIREMENTS_sys_prompt
60
-
61
-
62
 
63
  messages = [
64
- SystemMessage(content=sys_prompt),
65
  HumanMessage(content=prompt_string)
66
  ]
67
 
68
-
69
  response = self.llm.invoke(messages)
70
  state.generated_requirements = response.content if hasattr(response, 'content') else str(response)
71
  return {"generated_requirements": state.generated_requirements}
@@ -77,113 +73,173 @@ class SdlcNode:
77
  @log_entry_exit
78
  def generate_user_stories(self, state: State) -> dict:
79
  """Generate user stories based on the requirements."""
80
-
81
  logger.info("Generating user stories")
82
  if not state.generated_requirements:
83
  state.user_stories = "No requirements generated yet."
84
  logger.warning("Cannot generate user stories without requirements.")
85
  return {"user_stories": state.user_stories}
 
 
 
86
  feedback = state.get_last_feedback_for_stage(SDLCStages.PLANNING)
87
  logger.info(f"Feedback for user stories: {feedback}")
 
 
 
 
 
88
  if feedback:
89
- prompt_string = prompt.USER_STORIES_FEEDBACK_PROMPT_STRING.format(
90
- generated_requirements=state.generated_requirements,
91
- # project_name=state.project_name or 'N/A',
92
- feedback=feedback
93
- )
94
-
95
- sys_prompt = prompt.USER_STORIES_FEEDBACK_SYS_PROMPT.format(
96
- generated_requirements=state.generated_requirements,
97
- project_name=state.project_name or 'N/A',
98
- feedback=feedback
99
- )
100
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  messages = [
102
- SystemMessage(content=sys_prompt),
103
  HumanMessage(content=prompt_string)
104
  ]
105
  response = self.llm.invoke(messages)
106
  state.user_stories = response.content if hasattr(response, 'content') else str(response)
 
 
 
 
 
 
 
 
 
107
  return {"user_stories": state.user_stories}
108
- else:
109
- try:
110
- if state.generated_requirements:
111
- prompt_string = prompt.USER_STORIES_NO_FEEDBACK_PROMPT_STRING.format(
112
- generated_requirements=state.generated_requirements,
113
- project_name=state.project_name or 'N/A'
114
- )
115
-
116
- sys_prompt = prompt.USER_STORIES_NO_FEEDBACK_SYS_PROMPT.format(
117
- generated_requirements=state.generated_requirements,
118
- project_name=state.project_name or 'N/A'
119
- )
120
-
121
- messages = [
122
- SystemMessage(content=sys_prompt),
123
- HumanMessage(content=prompt_string)
124
- ]
125
- response = self.llm.invoke(messages)
126
- state.user_stories = response.content if hasattr(response, 'content') else str(response)
127
- return {"user_stories": state.user_stories}
128
- else:
129
- state.user_stories = "No requirements generated yet."
130
- return {"user_stories": state.user_stories}
131
- except Exception as e:
132
- logger.error(f"Error generating user stories: {e}")
133
- state.user_stories = f"Error generating user stories: {str(e)}"
134
- return {"user_stories": state.user_stories}
135
-
136
 
137
  @log_entry_exit
138
- def design_documents(self, state: State) -> dict:
139
  """Generate design documents based on user stories with robust validation."""
140
  state.feedback_decision = None
141
  logger.info("Generating design documents")
142
- if not state.user_stories:
143
- state.design_documents = "No user stories generated yet."
 
144
  logger.warning("Cannot generate design documents without user stories.")
145
  return {"design_documents": state.design_documents}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  try:
147
- prompt_string = f"Your task is to generate design documents based on the following user stories:\n\n{state.user_stories}\n\nPlease provide detailed design documents that cover the architecture, components, and interactions required to implement these user stories."
148
- sys_prompt = f" You are an expert software architect. Your task is to generate design documents based on the user stories provided. The design documents should be comprehensive and cover all aspects of the architecture, components, and interactions required to implement the user stories.\n\nProject Name: {state.project_name or 'N/A'}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  messages = [
150
- SystemMessage(content=sys_prompt),
151
- HumanMessage(content=prompt_string)
152
  ]
 
153
  response = self.llm.invoke(messages)
154
  state.design_documents = response.content if hasattr(response, 'content') else str(response)
 
 
 
 
 
 
 
 
 
155
  return {"design_documents": state.design_documents}
156
  except Exception as e:
157
- logger.error(f"Error generating design documents: {e}")
158
- state.design_documents = f"Error generating design documents: {str(e)}"
 
159
  return {"design_documents": state.design_documents}
160
-
161
-
162
-
163
 
164
  @log_entry_exit
165
  def development_artifact(self, state: State) -> dict:
166
  """Generate development artifacts based on design documents."""
167
- # state.development_artifact = "DUMMY DEVELOPMENT ARTIFACT: This is a dummy development artifact for testing."
168
- # return {"development_artifact": state.development_artifact}
169
  logger.info("Generating development artifacts")
170
  if not state.design_documents:
171
  state.development_artifact = "No design documents generated yet."
172
  logger.warning("Cannot generate development artifacts without design documents.")
173
  return {"development_artifact": state.development_artifact}
 
 
 
 
 
 
174
  try:
175
  prompt_string = prompt.DEVELOPMENT_ARTIFACT_PROMPT_STRING.format(
176
- design_documents=state.design_documents
 
 
 
177
  )
178
  messages = [
179
- SystemMessage(content=prompt.DEVELOPMENT_ARTIFACT_SYS_PROMPT.format(
180
- project_name=state.project_name or 'N/A'
181
- )),
182
  HumanMessage(content=prompt_string)
183
  ]
184
  response = self.llm.invoke(messages)
185
  state.development_artifact = response.content if hasattr(response, 'content') else str(response)
186
  return {"development_artifact": state.development_artifact}
 
 
 
 
 
187
  except Exception as e:
188
  logger.error(f"Error generating development artifacts: {e}")
189
  state.development_artifact = f"Error generating development artifacts: {str(e)}"
@@ -192,27 +248,38 @@ class SdlcNode:
192
  @log_entry_exit
193
  def testing_artifact(self, state: State) -> dict:
194
  """Generate testing artifacts based on development artifacts."""
195
- # state.testing_artifact = "DUMMY TESTING ARTIFACT: This is a dummy testing artifact for testing."
196
- # return {"testing_artifact": state.testing_artifact}
197
  logger.info("Generating testing artifacts")
198
  if not state.development_artifact:
199
  state.testing_artifact = "No development artifacts generated yet."
200
  logger.warning("Cannot generate testing artifacts without development artifacts.")
201
  return {"testing_artifact": state.testing_artifact}
 
 
 
 
 
 
 
202
  try:
203
  prompt_string = prompt.TESTING_ARTIFACT_PROMPT_STRING.format(
204
- user_stories=state.user_stories,
205
- development_artifact=state.development_artifact
 
 
 
206
  )
207
  messages = [
208
- SystemMessage(content=prompt.TESTING_ARTIFACT_SYS_PROMPT.format(
209
- project_name=state.project_name or 'N/A'
210
- )),
211
  HumanMessage(content=prompt_string)
212
  ]
213
  response = self.llm.invoke(messages)
214
  state.testing_artifact = response.content if hasattr(response, 'content') else str(response)
215
  return {"testing_artifact": state.testing_artifact}
 
 
 
 
 
216
  except Exception as e:
217
  logger.error(f"Error generating testing artifacts: {e}")
218
  state.testing_artifact = f"Error generating testing artifacts: {str(e)}"
@@ -221,19 +288,64 @@ class SdlcNode:
221
  @log_entry_exit
222
  def deployment_artifact(self, state: State) -> dict:
223
  """Generate deployment artifacts based on testing artifacts."""
224
- # state.deployment_artifact = "DUMMY DEPLOYMENT ARTIFACT: This is a dummy deployment artifact for testing."
225
- # return {"deployment_artifact": state.deployment_artifact}
226
  logger.info("Generating deployment artifacts")
227
  if not state.testing_artifact:
228
  state.deployment_artifact = "No testing artifacts generated yet."
229
  logger.warning("Cannot generate deployment artifacts without testing artifacts.")
230
  return {"deployment_artifact": state.deployment_artifact}
 
 
 
 
 
 
 
 
 
231
  try:
232
- prompt_string = prompt.DEPLOYMENT_ARTIFACT_PROMPT_STRING.format(state=state)
233
- messages = [SystemMessage(content=prompt.DEPLOYMENT_ARTIFACT_SYS_PROMPT), HumanMessage(content=prompt_string)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  response = self.llm.invoke(messages)
235
  state.deployment_artifact = response.content if hasattr(response, 'content') else str(response)
236
  return {"deployment_artifact": state.deployment_artifact}
 
 
 
 
 
 
237
  except Exception as e:
238
  logger.error(f"Error generating deployment artifacts: {e}")
239
  state.deployment_artifact = f"Error generating deployment artifacts: {str(e)}"
@@ -243,31 +355,30 @@ class SdlcNode:
243
  def process_feedback(self, state: State) -> dict:
244
  """
245
  Process user feedback and update state with decision.
246
- Only { "current_stage": ["accept"] } ends the process.
247
  """
248
-
249
- # logger.info(f"Input state: {state.to_dict()}")
250
- logger.info(f"Feedback: {state.feedback}")
251
-
252
  current_stage = state.current_stage
253
- feedback_for_stage = state.get_last_feedback_for_stage(current_stage)
254
-
255
- if feedback_for_stage is None:
256
- logger.warning(f"No feedback found for stage {current_stage}. Available feedback: {state.feedback}")
257
-
258
- state.add_feedback(current_stage, str(feedback_for_stage))
259
 
260
- logger.info(f"Processing feedback for stage: {current_stage}")
261
- logger.info(f"Feedback received: {feedback_for_stage}")
262
 
263
- if feedback_for_stage and feedback_for_stage.strip().lower() == "accept":
264
- logger.info(f"Feedback for stage '{current_stage}' is ACCEPT. Ending flow.")
265
- state.feedback_decision = "accept"
266
- else:
267
- logger.info(f"Feedback for stage '{current_stage}' is not accept. Looping back. Feedback is: {feedback_for_stage}")
 
 
 
 
 
 
 
268
  state.feedback_decision = "reject"
269
- logger.info(f"Feedback: {state.feedback}")
270
 
 
271
  return {"feedback_decision": state.feedback_decision}
272
 
273
  @log_entry_exit
@@ -277,26 +388,31 @@ class SdlcNode:
277
 
278
  if not isinstance(state, State):
279
  logger.error(f"Invalid state type: {type(state)}. Routing to reject.")
280
- return "reject"
281
 
282
- if state.feedback_decision == "accept":
 
 
283
  logger.info("Feedback accepted. Routing to next stage.")
284
  next_stage = state.get_next_stage()
285
  if next_stage:
286
- state.update_stage(next_stage)
287
- logger.info(f"Updated state to next stage: {next_stage}")
 
288
  else:
289
  logger.info("No next stage available. Ending process.")
290
-
291
- state.clear_feedback_decision()
292
- st.session_state["feedback_decision"] = None
293
- logger.info(f"Feedback: {state.feedback}")
294
- logger.info(f"Feedback decision: {state.feedback_decision}")
 
 
295
  return "accept"
296
- else:
297
- logger.info("Feedback rejected. Routing back for revision.")
 
298
  state.clear_feedback_decision()
299
  st.session_state["feedback_decision"] = None
300
- return "reject"
301
-
302
-
 
6
  from datetime import datetime
7
  from typing import List
8
  from src.langgraphagenticai.logging.logging_utils import logger, log_entry_exit
9
+ from src.langgraphagenticai.prompt_library import prompt # Assuming your prompts are here
10
+ # import json # Duplicate import, already imported
11
  from tenacity import retry, stop_after_attempt, wait_exponential
12
  import functools
13
  import time
 
16
 
17
  class SdlcNode:
18
  def __init__(self, model):
19
+ """Initialize the SdlcNode with an LLM."""
20
  self.llm = model
21
 
22
  @log_entry_exit
 
51
  "project_scope": state.project_scope if state.project_scope is not None else "No project scope provided",
52
  "project_objectives": state.project_objectives if state.project_objectives is not None else "No project objectives provided",
53
  }
 
54
  requirements_input_json = json.dumps(requirements_input, indent=2)
55
 
 
56
  prompt_string = prompt.REQUIREMENTS_PROMPT_STRING.format(requirements_input=requirements_input_json)
57
+ # Assuming REQUIREMENTS_sys_prompt does not need .format() or is formatted elsewhere if needed
58
+ sys_prompt_content = prompt.REQUIREMENTS_sys_prompt
 
59
 
60
  messages = [
61
+ SystemMessage(content=sys_prompt_content),
62
  HumanMessage(content=prompt_string)
63
  ]
64
 
 
65
  response = self.llm.invoke(messages)
66
  state.generated_requirements = response.content if hasattr(response, 'content') else str(response)
67
  return {"generated_requirements": state.generated_requirements}
 
73
  @log_entry_exit
74
  def generate_user_stories(self, state: State) -> dict:
75
  """Generate user stories based on the requirements."""
 
76
  logger.info("Generating user stories")
77
  if not state.generated_requirements:
78
  state.user_stories = "No requirements generated yet."
79
  logger.warning("Cannot generate user stories without requirements.")
80
  return {"user_stories": state.user_stories}
81
+
82
+ formatted_requirements = str(state.generated_requirements).replace('{', '{{').replace('}', '}}')
83
+
84
  feedback = state.get_last_feedback_for_stage(SDLCStages.PLANNING)
85
  logger.info(f"Feedback for user stories: {feedback}")
86
+
87
+ project_name_val = state.project_name or 'N/A'
88
+ project_name_formatted = str(project_name_val).replace('{', '{{').replace('}', '}}')
89
+
90
+
91
  if feedback:
92
+ formatted_feedback = str(feedback).replace('{', '{{').replace('}', '}}')
93
+ try:
94
+ prompt_string = prompt.USER_STORIES_FEEDBACK_PROMPT_STRING.format(
95
+ generated_requirements=formatted_requirements,
96
+ feedback=formatted_feedback
97
+ )
98
+ sys_prompt_content = prompt.USER_STORIES_FEEDBACK_SYS_PROMPT.format(
99
+ generated_requirements=formatted_requirements,
100
+ project_name=project_name_formatted,
101
+ feedback=formatted_feedback
102
+ )
103
+ except KeyError as e:
104
+ logger.error(f"KeyError during formatting USER_STORIES_FEEDBACK prompts: {e}")
105
+ logger.error(f"Available keys: generated_requirements, project_name, feedback")
106
+ state.user_stories = f"Error formatting user story prompt: {e}"
107
+ return {"user_stories": state.user_stories}
108
+ else:
109
+ try:
110
+ prompt_string = prompt.USER_STORIES_NO_FEEDBACK_PROMPT_STRING.format(
111
+ generated_requirements=formatted_requirements,
112
+ project_name=project_name_formatted
113
+ )
114
+ sys_prompt_content = prompt.USER_STORIES_NO_FEEDBACK_SYS_PROMPT.format(
115
+ generated_requirements=formatted_requirements,
116
+ project_name=project_name_formatted
117
+ )
118
+ except KeyError as e:
119
+ logger.error(f"KeyError during formatting USER_STORIES_NO_FEEDBACK prompts: {e}")
120
+ logger.error(f"Available keys: generated_requirements, project_name")
121
+ state.user_stories = f"Error formatting user story prompt: {e}"
122
+ return {"user_stories": state.user_stories}
123
+
124
+ try:
125
  messages = [
126
+ SystemMessage(content=sys_prompt_content),
127
  HumanMessage(content=prompt_string)
128
  ]
129
  response = self.llm.invoke(messages)
130
  state.user_stories = response.content if hasattr(response, 'content') else str(response)
131
+ st.session_state["user_stories"] = state.user_stories # Update session state
132
+ logger.info(f"--- RAW state.user_stories after generation ---")
133
+ logger.info(state.user_stories)
134
+ logger.info(f"--- END RAW state.user_stories ---")
135
+
136
+ return {"user_stories": state.user_stories}
137
+ except Exception as e:
138
+ logger.error(f"Error generating user stories: {e}")
139
+ state.user_stories = f"Error generating user stories: {str(e)}"
140
  return {"user_stories": state.user_stories}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  @log_entry_exit
143
+ def design_documents(self, state: State) -> dict[str, str]:
144
  """Generate design documents based on user stories with robust validation."""
145
  state.feedback_decision = None
146
  logger.info("Generating design documents")
147
+
148
+ if not state.user_stories or not str(state.user_stories).strip():
149
+ state.design_documents = "No user stories provided for design document generation."
150
  logger.warning("Cannot generate design documents without user stories.")
151
  return {"design_documents": state.design_documents}
152
+
153
+
154
+ user_stories_for_prompt = str(state.user_stories).replace('{', '{{').replace('}', '}}')
155
+
156
+ project_name_val = state.project_name or 'N/A'
157
+ project_name_for_prompt = str(project_name_val).replace('{', '{{').replace('}', '}}')
158
+
159
+ logger.info(f"--- User Stories for TDD Prompt (escaped) ---")
160
+ logger.info(user_stories_for_prompt[:1000] + "..." if len(user_stories_for_prompt) > 1000 else user_stories_for_prompt) # Log a snippet
161
+ logger.info(f"--- END User Stories for TDD Prompt ---")
162
+
163
+
164
+ # Get feedback for design stage (currently not used in these prompts but good to have)
165
+ # feedback = state.get_last_feedback_for_stage(SDLCStages.DESIGN)
166
+ # logger.info(f"Feedback for design documents: {feedback or 'None'}")
167
+
168
  try:
169
+ # Use the detailed prompts from prompt_library
170
+ # Ensure the placeholders in your prompts match what you provide here.
171
+ # The prompts DESIGN_DOCUMENTS_NO_FEEDBACK_... expect {project_name} and {user_stories}
172
+
173
+ sys_prompt_content = prompt.DESIGN_DOCUMENTS_NO_FEEDBACK_SYS_PROMPT.format(
174
+ user_stories=user_stories_for_prompt,
175
+ project_name=project_name_for_prompt
176
+ )
177
+ prompt_string_content = prompt.DESIGN_DOCUMENTS_NO_FEEDBACK_PROMPT_STRING.format(
178
+ user_stories=user_stories_for_prompt,
179
+ project_name=project_name_for_prompt # Assuming this is also needed here
180
+ )
181
+
182
+ logger.debug(f"--- Formatted System Prompt for TDD (first 500 chars) ---")
183
+ logger.debug(sys_prompt_content[:500] + "...")
184
+ logger.debug(f"--- Formatted Human Prompt for TDD (first 500 chars) ---")
185
+ logger.debug(prompt_string_content[:500] + "...")
186
+
187
  messages = [
188
+ SystemMessage(content=sys_prompt_content),
189
+ HumanMessage(content=prompt_string_content)
190
  ]
191
+
192
  response = self.llm.invoke(messages)
193
  state.design_documents = response.content if hasattr(response, 'content') else str(response)
194
+ logger.info("Design documents generated successfully.")
195
+ return {"design_documents": state.design_documents}
196
+
197
+ except KeyError as ke:
198
+ error_msg = f"KeyError during TDD prompt formatting: {str(ke)}. This means a placeholder in your DESIGN_DOCUMENTS_... prompts was not found in the .format() call."
199
+ logger.error(error_msg)
200
+ logger.error(f"User stories (snippet): {user_stories_for_prompt[:200]}")
201
+ logger.error(f"Project name: {project_name_for_prompt}")
202
+ state.design_documents = error_msg
203
  return {"design_documents": state.design_documents}
204
  except Exception as e:
205
+ error_msg = f"Error generating design documents: {type(e).__name__} - {str(e)}"
206
+ state.design_documents = error_msg
207
+ logger.error(f"{error_msg}, Input User Stories (snippet): {user_stories_for_prompt[:200]}...")
208
  return {"design_documents": state.design_documents}
 
 
 
209
 
210
  @log_entry_exit
211
  def development_artifact(self, state: State) -> dict:
212
  """Generate development artifacts based on design documents."""
 
 
213
  logger.info("Generating development artifacts")
214
  if not state.design_documents:
215
  state.development_artifact = "No design documents generated yet."
216
  logger.warning("Cannot generate development artifacts without design documents.")
217
  return {"development_artifact": state.development_artifact}
218
+
219
+ # Escape design_documents and project_name for .format()
220
+ design_documents_for_prompt = str(state.design_documents).replace('{', '{{').replace('}', '}}')
221
+ project_name_val = state.project_name or 'N/A'
222
+ project_name_for_prompt = str(project_name_val).replace('{', '{{').replace('}', '}}')
223
+
224
  try:
225
  prompt_string = prompt.DEVELOPMENT_ARTIFACT_PROMPT_STRING.format(
226
+ design_documents=design_documents_for_prompt
227
+ )
228
+ sys_prompt_content = prompt.DEVELOPMENT_ARTIFACT_SYS_PROMPT.format(
229
+ project_name=project_name_for_prompt
230
  )
231
  messages = [
232
+ SystemMessage(content=sys_prompt_content),
 
 
233
  HumanMessage(content=prompt_string)
234
  ]
235
  response = self.llm.invoke(messages)
236
  state.development_artifact = response.content if hasattr(response, 'content') else str(response)
237
  return {"development_artifact": state.development_artifact}
238
+ except KeyError as ke:
239
+ error_msg = f"KeyError during DEVELOPMENT_ARTIFACT prompt formatting: {str(ke)}."
240
+ logger.error(error_msg)
241
+ state.development_artifact = error_msg
242
+ return {"development_artifact": state.development_artifact}
243
  except Exception as e:
244
  logger.error(f"Error generating development artifacts: {e}")
245
  state.development_artifact = f"Error generating development artifacts: {str(e)}"
 
248
  @log_entry_exit
249
  def testing_artifact(self, state: State) -> dict:
250
  """Generate testing artifacts based on development artifacts."""
 
 
251
  logger.info("Generating testing artifacts")
252
  if not state.development_artifact:
253
  state.testing_artifact = "No development artifacts generated yet."
254
  logger.warning("Cannot generate testing artifacts without development artifacts.")
255
  return {"testing_artifact": state.testing_artifact}
256
+
257
+ # Escape inputs for .format()
258
+ user_stories_for_prompt = str(state.user_stories).replace('{', '{{').replace('}', '}}')
259
+ development_artifact_for_prompt = str(state.development_artifact).replace('{', '{{').replace('}', '}}')
260
+ project_name_val = state.project_name or 'N/A'
261
+ project_name_for_prompt = str(project_name_val).replace('{', '{{').replace('}', '}}')
262
+
263
  try:
264
  prompt_string = prompt.TESTING_ARTIFACT_PROMPT_STRING.format(
265
+ user_stories=user_stories_for_prompt,
266
+ development_artifact=development_artifact_for_prompt
267
+ )
268
+ sys_prompt_content = prompt.TESTING_ARTIFACT_SYS_PROMPT.format(
269
+ project_name=project_name_for_prompt
270
  )
271
  messages = [
272
+ SystemMessage(content=sys_prompt_content),
 
 
273
  HumanMessage(content=prompt_string)
274
  ]
275
  response = self.llm.invoke(messages)
276
  state.testing_artifact = response.content if hasattr(response, 'content') else str(response)
277
  return {"testing_artifact": state.testing_artifact}
278
+ except KeyError as ke:
279
+ error_msg = f"KeyError during TESTING_ARTIFACT prompt formatting: {str(ke)}."
280
+ logger.error(error_msg)
281
+ state.testing_artifact = error_msg
282
+ return {"testing_artifact": state.testing_artifact}
283
  except Exception as e:
284
  logger.error(f"Error generating testing artifacts: {e}")
285
  state.testing_artifact = f"Error generating testing artifacts: {str(e)}"
 
288
  @log_entry_exit
289
  def deployment_artifact(self, state: State) -> dict:
290
  """Generate deployment artifacts based on testing artifacts."""
 
 
291
  logger.info("Generating deployment artifacts")
292
  if not state.testing_artifact:
293
  state.deployment_artifact = "No testing artifacts generated yet."
294
  logger.warning("Cannot generate deployment artifacts without testing artifacts.")
295
  return {"deployment_artifact": state.deployment_artifact}
296
+
297
+ # Escape inputs for .format()
298
+ # The DEPLOYMENT_ARTIFACT_PROMPT_STRING uses {state}, which is not standard for .format()
299
+ # It should be specific fields like {state.testing_artifact} or {testing_artifact}
300
+ # For now, assuming it wants the string representation of the testing_artifact and project_name
301
+ testing_artifact_for_prompt = str(state.testing_artifact).replace('{', '{{').replace('}', '}}')
302
+ project_name_val = state.project_name or 'N/A'
303
+ project_name_for_prompt = str(project_name_val).replace('{', '{{').replace('}', '}}')
304
+
305
  try:
306
+ # --- IMPORTANT: Review prompt.DEPLOYMENT_ARTIFACT_PROMPT_STRING ---
307
+ # It currently has .format(state=state). This is unusual.
308
+ # It should likely be .format(testing_artifact=testing_artifact_for_prompt, project_name=project_name_for_prompt)
309
+ # or similar, depending on its actual placeholders.
310
+ # For now, I'll assume it expects 'testing_artifact' and 'project_name' as keys.
311
+ # You MUST adjust this if your prompt uses different placeholder names.
312
+
313
+ # Tentative formatting based on common patterns, ADJUST IF YOUR PROMPT IS DIFFERENT
314
+ try:
315
+ prompt_string_content = prompt.DEPLOYMENT_ARTIFACT_PROMPT_STRING.format(
316
+ testing_artifact=testing_artifact_for_prompt
317
+ # Add other fields from 'state' if your prompt actually uses them like {state.project_name}
318
+ )
319
+ # Assuming DEPLOYMENT_ARTIFACT_SYS_PROMPT expects project_name
320
+ sys_prompt_content = prompt.DEPLOYMENT_ARTIFACT_SYS_PROMPT.format(
321
+ project_name=project_name_for_prompt
322
+ )
323
+ except KeyError as ke:
324
+ # Fallback if the prompt string uses {state.testing_artifact} directly (less common for general prompts)
325
+ if 'state.testing_artifact' in str(ke): # Check if the error is about 'state.testing_artifact'
326
+ prompt_string_content = prompt.DEPLOYMENT_ARTIFACT_PROMPT_STRING.format(
327
+ state=state # Pass the whole state object if the prompt needs it this way
328
+ )
329
+ sys_prompt_content = prompt.DEPLOYMENT_ARTIFACT_SYS_PROMPT.format(
330
+ project_name=project_name_for_prompt # This part is likely fine
331
+ )
332
+ else: # Re-raise if it's a different KeyError
333
+ raise ke
334
+
335
+
336
+ messages = [
337
+ SystemMessage(content=sys_prompt_content),
338
+ HumanMessage(content=prompt_string_content)
339
+ ]
340
  response = self.llm.invoke(messages)
341
  state.deployment_artifact = response.content if hasattr(response, 'content') else str(response)
342
  return {"deployment_artifact": state.deployment_artifact}
343
+ except KeyError as ke:
344
+ error_msg = f"KeyError during DEPLOYMENT_ARTIFACT prompt formatting: {str(ke)}. Review prompt placeholders."
345
+ logger.error(error_msg)
346
+ logger.error(f"Prompt string might be: {prompt.DEPLOYMENT_ARTIFACT_PROMPT_STRING}")
347
+ state.deployment_artifact = error_msg
348
+ return {"deployment_artifact": state.deployment_artifact}
349
  except Exception as e:
350
  logger.error(f"Error generating deployment artifacts: {e}")
351
  state.deployment_artifact = f"Error generating deployment artifacts: {str(e)}"
 
355
  def process_feedback(self, state: State) -> dict:
356
  """
357
  Process user feedback and update state with decision.
 
358
  """
359
+ logger.info(f"Processing feedback. Current feedback state: {state.feedback}")
 
 
 
360
  current_stage = state.current_stage
361
+ # feedback_decision is set by the UI/controller before calling this node
362
+ feedback_text_from_ui = state.feedback.get(current_stage.value, [None])[-1] # Get latest feedback for current stage
 
 
 
 
363
 
364
+ logger.info(f"Processing feedback for stage: {current_stage}, Decision from UI: {state.feedback_decision}, Text: {feedback_text_from_ui}")
 
365
 
366
+ if state.feedback_decision == "accept":
367
+ logger.info(f"Feedback for stage '{current_stage}' is ACCEPT based on UI decision.")
368
+ # Optionally, store "accept" as a feedback entry if needed for history, though decision is primary
369
+ state.add_feedback(current_stage, "User accepted.")
370
+ elif state.feedback_decision == "reject":
371
+ logger.info(f"Feedback for stage '{current_stage}' is REJECT based on UI decision. Feedback text: {feedback_text_from_ui}")
372
+ if feedback_text_from_ui:
373
+ state.add_feedback(current_stage, str(feedback_text_from_ui)) # Add the actual feedback text
374
+ else:
375
+ state.add_feedback(current_stage, "User rejected, no specific feedback provided.")
376
+ else: # Should not happen if UI sets feedback_decision correctly
377
+ logger.warning(f"Unknown feedback_decision '{state.feedback_decision}' for stage {current_stage}. Defaulting to reject.")
378
  state.feedback_decision = "reject"
379
+ state.add_feedback(current_stage, f"System default to reject due to unknown decision: {state.feedback_decision}")
380
 
381
+ logger.info(f"Updated feedback state: {state.feedback}")
382
  return {"feedback_decision": state.feedback_decision}
383
 
384
  @log_entry_exit
 
388
 
389
  if not isinstance(state, State):
390
  logger.error(f"Invalid state type: {type(state)}. Routing to reject.")
391
+ return "reject" # Or handle as an error state
392
 
393
+ decision = state.feedback_decision # Already processed in process_feedback
394
+
395
+ if decision == "accept":
396
  logger.info("Feedback accepted. Routing to next stage.")
397
  next_stage = state.get_next_stage()
398
  if next_stage:
399
+ state.update_stage(next_stage) # This should update current_stage
400
+ st.session_state["current_stage"] = state.current_stage # Reflect in Streamlit
401
+ logger.info(f"Updated state to next stage: {state.current_stage}")
402
  else:
403
  logger.info("No next stage available. Ending process.")
404
+ # No need to update current_stage if ending
405
+
406
+ state.clear_feedback_decision() # Clear for the next cycle
407
+ st.session_state["feedback_decision"] = None # Reflect in Streamlit
408
+ # Clear specific feedback text from UI for the accepted stage if desired
409
+ # st.session_state["feedback"] = {} # Or selectively clear
410
+ logger.info(f"Feedback decision cleared. Current state feedback: {state.feedback}")
411
  return "accept"
412
+ else: # Covers 'reject' and any other case defaulting to reject
413
+ logger.info(f"Feedback decision is '{decision}'. Routing back for revision of stage {state.current_stage}.")
414
+ # The current stage remains the same for revision.
415
  state.clear_feedback_decision()
416
  st.session_state["feedback_decision"] = None
417
+ logger.info(f"Feedback decision cleared for revision. Current state feedback: {state.feedback}")
418
+ return "reject"