copilot-swe-agent[bot] CatoG commited on
Commit ·
3e3d4be
1
Parent(s): a94c9f8
Add iterative QA feedback: QA provides per-role targeted feedback to enable role refinement
Browse files
app.py
CHANGED
|
@@ -558,6 +558,7 @@ class WorkflowState(TypedDict):
|
|
| 558 |
synthesis_output: str # unified summary produced by the Synthesizer after all specialists
|
| 559 |
draft_output: str # latest specialist/synthesis output forwarded to QA
|
| 560 |
qa_report: str
|
|
|
|
| 561 |
qa_passed: bool
|
| 562 |
revision_count: int
|
| 563 |
final_answer: str
|
|
@@ -618,10 +619,15 @@ _TECHNICAL_SYSTEM = (
|
|
| 618 |
|
| 619 |
_QA_SYSTEM = (
|
| 620 |
"You are the QA Tester in a multi-role AI workflow.\n"
|
| 621 |
-
"Check whether the
|
|
|
|
|
|
|
| 622 |
"Respond in this exact format:\n"
|
| 623 |
"REQUIREMENTS CHECKED:\n<list each requirement and whether it was met>\n\n"
|
| 624 |
"ISSUES FOUND:\n<defects or gaps — or 'None' if all requirements are met>\n\n"
|
|
|
|
|
|
|
|
|
|
| 625 |
"RESULT: <PASS | FAIL>\n\n"
|
| 626 |
"RECOMMENDED FIXES:\n<specific improvements — or 'None' if result is PASS>"
|
| 627 |
)
|
|
@@ -936,6 +942,40 @@ def _qa_passed_check(qa_text: str) -> bool:
|
|
| 936 |
return False
|
| 937 |
|
| 938 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
# --- Workflow step functions ---
|
| 940 |
# Each step receives the shared state and an append-only trace list,
|
| 941 |
# updates state in place, appends log lines, and returns updated state.
|
|
@@ -966,7 +1006,11 @@ def _step_creative(chat_model, state: WorkflowState, trace: List[str]) -> Workfl
|
|
| 966 |
f"Planner instructions:\n{state['plan']}"
|
| 967 |
)
|
| 968 |
if state["revision_count"] > 0:
|
| 969 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 970 |
text = _llm_call(chat_model, _CREATIVE_SYSTEM, content)
|
| 971 |
state["creative_output"] = text
|
| 972 |
state["draft_output"] = text
|
|
@@ -983,7 +1027,11 @@ def _step_technical(chat_model, state: WorkflowState, trace: List[str]) -> Workf
|
|
| 983 |
f"Planner instructions:\n{state['plan']}"
|
| 984 |
)
|
| 985 |
if state["revision_count"] > 0:
|
| 986 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 987 |
text = _llm_call(chat_model, _TECHNICAL_SYSTEM, content)
|
| 988 |
state["technical_output"] = text
|
| 989 |
state["draft_output"] = text
|
|
@@ -992,19 +1040,46 @@ def _step_technical(chat_model, state: WorkflowState, trace: List[str]) -> Workf
|
|
| 992 |
return state
|
| 993 |
|
| 994 |
|
| 995 |
-
def _step_qa(
|
| 996 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 997 |
trace.append("\n╔══ [QA TESTER] Reviewing output... ══╗")
|
| 998 |
content = (
|
| 999 |
f"Original user request: {state['user_request']}\n\n"
|
| 1000 |
f"Planner's plan and success criteria:\n{state['plan']}\n\n"
|
| 1001 |
-
f"Specialist output to review:\n{state['draft_output']}"
|
| 1002 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1003 |
text = _llm_call(chat_model, _QA_SYSTEM, content)
|
| 1004 |
state["qa_report"] = text
|
|
|
|
| 1005 |
state["qa_passed"] = _qa_passed_check(text)
|
| 1006 |
result_label = "✅ PASS" if state["qa_passed"] else "❌ FAIL"
|
| 1007 |
trace.append(text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1008 |
trace.append(f"╚══ [QA TESTER] Result: {result_label} ══╝")
|
| 1009 |
return state
|
| 1010 |
|
|
@@ -1054,7 +1129,11 @@ def _step_research(chat_model, state: WorkflowState, trace: List[str]) -> Workfl
|
|
| 1054 |
f"Planner instructions:\n{state['plan']}"
|
| 1055 |
)
|
| 1056 |
if state["revision_count"] > 0:
|
| 1057 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1058 |
text = _llm_call(chat_model, _RESEARCH_SYSTEM, content)
|
| 1059 |
state["research_output"] = text
|
| 1060 |
state["draft_output"] = text
|
|
@@ -1071,7 +1150,11 @@ def _step_security(chat_model, state: WorkflowState, trace: List[str]) -> Workfl
|
|
| 1071 |
f"Planner instructions:\n{state['plan']}"
|
| 1072 |
)
|
| 1073 |
if state["revision_count"] > 0:
|
| 1074 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1075 |
text = _llm_call(chat_model, _SECURITY_SYSTEM, content)
|
| 1076 |
state["security_output"] = text
|
| 1077 |
state["draft_output"] = text
|
|
@@ -1088,7 +1171,11 @@ def _step_data_analyst(chat_model, state: WorkflowState, trace: List[str]) -> Wo
|
|
| 1088 |
f"Planner instructions:\n{state['plan']}"
|
| 1089 |
)
|
| 1090 |
if state["revision_count"] > 0:
|
| 1091 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1092 |
text = _llm_call(chat_model, _DATA_ANALYST_SYSTEM, content)
|
| 1093 |
state["data_analyst_output"] = text
|
| 1094 |
state["draft_output"] = text
|
|
@@ -1105,7 +1192,11 @@ def _step_mad_professor(chat_model, state: WorkflowState, trace: List[str]) -> W
|
|
| 1105 |
f"Planner instructions:\n{state['plan']}"
|
| 1106 |
)
|
| 1107 |
if state["revision_count"] > 0:
|
| 1108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1109 |
text = _llm_call(chat_model, _MAD_PROFESSOR_SYSTEM, content)
|
| 1110 |
state["mad_professor_output"] = text
|
| 1111 |
state["draft_output"] = text
|
|
@@ -1122,7 +1213,11 @@ def _step_accountant(chat_model, state: WorkflowState, trace: List[str]) -> Work
|
|
| 1122 |
f"Planner instructions:\n{state['plan']}"
|
| 1123 |
)
|
| 1124 |
if state["revision_count"] > 0:
|
| 1125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1126 |
text = _llm_call(chat_model, _ACCOUNTANT_SYSTEM, content)
|
| 1127 |
state["accountant_output"] = text
|
| 1128 |
state["draft_output"] = text
|
|
@@ -1139,7 +1234,11 @@ def _step_artist(chat_model, state: WorkflowState, trace: List[str]) -> Workflow
|
|
| 1139 |
f"Planner instructions:\n{state['plan']}"
|
| 1140 |
)
|
| 1141 |
if state["revision_count"] > 0:
|
| 1142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1143 |
text = _llm_call(chat_model, _ARTIST_SYSTEM, content)
|
| 1144 |
state["artist_output"] = text
|
| 1145 |
state["draft_output"] = text
|
|
@@ -1156,7 +1255,11 @@ def _step_lazy_slacker(chat_model, state: WorkflowState, trace: List[str]) -> Wo
|
|
| 1156 |
f"Planner instructions:\n{state['plan']}"
|
| 1157 |
)
|
| 1158 |
if state["revision_count"] > 0:
|
| 1159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1160 |
text = _llm_call(chat_model, _LAZY_SLACKER_SYSTEM, content)
|
| 1161 |
state["lazy_slacker_output"] = text
|
| 1162 |
state["draft_output"] = text
|
|
@@ -1173,7 +1276,11 @@ def _step_black_metal_fundamentalist(chat_model, state: WorkflowState, trace: Li
|
|
| 1173 |
f"Planner instructions:\n{state['plan']}"
|
| 1174 |
)
|
| 1175 |
if state["revision_count"] > 0:
|
| 1176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1177 |
text = _llm_call(chat_model, _BLACK_METAL_FUNDAMENTALIST_SYSTEM, content)
|
| 1178 |
state["black_metal_fundamentalist_output"] = text
|
| 1179 |
state["draft_output"] = text
|
|
@@ -1190,7 +1297,11 @@ def _step_labour_union_rep(chat_model, state: WorkflowState, trace: List[str]) -
|
|
| 1190 |
f"Planner instructions:\n{state['plan']}"
|
| 1191 |
)
|
| 1192 |
if state["revision_count"] > 0:
|
| 1193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1194 |
text = _llm_call(chat_model, _LABOUR_UNION_REP_SYSTEM, content)
|
| 1195 |
state["labour_union_rep_output"] = text
|
| 1196 |
state["draft_output"] = text
|
|
@@ -1207,7 +1318,11 @@ def _step_ux_designer(chat_model, state: WorkflowState, trace: List[str]) -> Wor
|
|
| 1207 |
f"Planner instructions:\n{state['plan']}"
|
| 1208 |
)
|
| 1209 |
if state["revision_count"] > 0:
|
| 1210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1211 |
text = _llm_call(chat_model, _UX_DESIGNER_SYSTEM, content)
|
| 1212 |
state["ux_designer_output"] = text
|
| 1213 |
state["draft_output"] = text
|
|
@@ -1224,7 +1339,11 @@ def _step_doris(chat_model, state: WorkflowState, trace: List[str]) -> WorkflowS
|
|
| 1224 |
f"Planner instructions:\n{state['plan']}"
|
| 1225 |
)
|
| 1226 |
if state["revision_count"] > 0:
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
text = _llm_call(chat_model, _DORIS_SYSTEM, content)
|
| 1229 |
state["doris_output"] = text
|
| 1230 |
state["draft_output"] = text
|
|
@@ -1241,7 +1360,11 @@ def _step_chairman_of_board(chat_model, state: WorkflowState, trace: List[str])
|
|
| 1241 |
f"Planner instructions:\n{state['plan']}"
|
| 1242 |
)
|
| 1243 |
if state["revision_count"] > 0:
|
| 1244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1245 |
text = _llm_call(chat_model, _CHAIRMAN_SYSTEM, content)
|
| 1246 |
state["chairman_of_board_output"] = text
|
| 1247 |
state["draft_output"] = text
|
|
@@ -1258,7 +1381,11 @@ def _step_maga_appointee(chat_model, state: WorkflowState, trace: List[str]) ->
|
|
| 1258 |
f"Planner instructions:\n{state['plan']}"
|
| 1259 |
)
|
| 1260 |
if state["revision_count"] > 0:
|
| 1261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1262 |
text = _llm_call(chat_model, _MAGA_APPOINTEE_SYSTEM, content)
|
| 1263 |
state["maga_appointee_output"] = text
|
| 1264 |
state["draft_output"] = text
|
|
@@ -1275,7 +1402,11 @@ def _step_lawyer(chat_model, state: WorkflowState, trace: List[str]) -> Workflow
|
|
| 1275 |
f"Planner instructions:\n{state['plan']}"
|
| 1276 |
)
|
| 1277 |
if state["revision_count"] > 0:
|
| 1278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1279 |
text = _llm_call(chat_model, _LAWYER_SYSTEM, content)
|
| 1280 |
state["lawyer_output"] = text
|
| 1281 |
state["draft_output"] = text
|
|
@@ -1346,7 +1477,7 @@ _EMPTY_STATE_BASE: WorkflowState = {
|
|
| 1346 |
"labour_union_rep_output": "", "ux_designer_output": "", "doris_output": "",
|
| 1347 |
"chairman_of_board_output": "", "maga_appointee_output": "", "lawyer_output": "",
|
| 1348 |
"synthesis_output": "",
|
| 1349 |
-
"draft_output": "", "qa_report": "", "qa_passed": False,
|
| 1350 |
"revision_count": 0, "final_answer": "",
|
| 1351 |
}
|
| 1352 |
|
|
@@ -1526,9 +1657,11 @@ def run_multi_role_workflow(
|
|
| 1526 |
1. Planner (if active) analyses the task and picks a primary specialist.
|
| 1527 |
2. ALL active specialists run and contribute their own perspective.
|
| 1528 |
3. Synthesizer summarises all perspectives, finds common ground, and produces a unified recommendation.
|
| 1529 |
-
4. QA Tester (if active) reviews the synthesized output
|
|
|
|
| 1530 |
5. Planner (if active) reviews QA result and either approves or requests a revision.
|
| 1531 |
-
6. Repeat from step 2 if QA fails and retries remain
|
|
|
|
| 1532 |
7. If max retries are reached, return best attempt with QA concerns.
|
| 1533 |
|
| 1534 |
Args:
|
|
@@ -1586,6 +1719,7 @@ def run_multi_role_workflow(
|
|
| 1586 |
"synthesis_output": "",
|
| 1587 |
"draft_output": "",
|
| 1588 |
"qa_report": "",
|
|
|
|
| 1589 |
"qa_passed": False,
|
| 1590 |
"revision_count": 0,
|
| 1591 |
"final_answer": "",
|
|
@@ -1649,7 +1783,7 @@ def run_multi_role_workflow(
|
|
| 1649 |
|
| 1650 |
# Step 3: QA reviews the specialist's draft (if enabled)
|
| 1651 |
if qa_active:
|
| 1652 |
-
state = _step_qa(chat_model, state, trace)
|
| 1653 |
else:
|
| 1654 |
state["qa_passed"] = True
|
| 1655 |
state["qa_report"] = "QA Tester is disabled — skipping quality review."
|
|
|
|
| 558 |
synthesis_output: str # unified summary produced by the Synthesizer after all specialists
|
| 559 |
draft_output: str # latest specialist/synthesis output forwarded to QA
|
| 560 |
qa_report: str
|
| 561 |
+
qa_role_feedback: Dict[str, str] # role key → targeted QA feedback for that specific role
|
| 562 |
qa_passed: bool
|
| 563 |
revision_count: int
|
| 564 |
final_answer: str
|
|
|
|
| 619 |
|
| 620 |
_QA_SYSTEM = (
|
| 621 |
"You are the QA Tester in a multi-role AI workflow.\n"
|
| 622 |
+
"Check whether the output satisfies the original request and success criteria.\n"
|
| 623 |
+
"When individual specialist contributions are provided, give targeted feedback for each role\n"
|
| 624 |
+
"so they can refine their specific propositions in the next iteration.\n\n"
|
| 625 |
"Respond in this exact format:\n"
|
| 626 |
"REQUIREMENTS CHECKED:\n<list each requirement and whether it was met>\n\n"
|
| 627 |
"ISSUES FOUND:\n<defects or gaps — or 'None' if all requirements are met>\n\n"
|
| 628 |
+
"ROLE-SPECIFIC FEEDBACK:\n"
|
| 629 |
+
"<one bullet per specialist role that contributed, with targeted feedback on their contribution:\n"
|
| 630 |
+
" • Role Name: <specific feedback for that role to refine their proposition, or 'Satisfactory' if no issues>>\n\n"
|
| 631 |
"RESULT: <PASS | FAIL>\n\n"
|
| 632 |
"RECOMMENDED FIXES:\n<specific improvements — or 'None' if result is PASS>"
|
| 633 |
)
|
|
|
|
| 942 |
return False
|
| 943 |
|
| 944 |
|
| 945 |
+
def _parse_qa_role_feedback(qa_text: str) -> Dict[str, str]:
|
| 946 |
+
"""Extract per-role targeted feedback from a QA report.
|
| 947 |
+
|
| 948 |
+
Looks for the ROLE-SPECIFIC FEEDBACK section produced by the QA Tester
|
| 949 |
+
and parses bullet entries of the form '• Role Name: <feedback>'.
|
| 950 |
+
Returns a dict mapping role keys (e.g. 'creative', 'technical') to the
|
| 951 |
+
feedback string targeted at that role.
|
| 952 |
+
"""
|
| 953 |
+
feedback: Dict[str, str] = {}
|
| 954 |
+
if "ROLE-SPECIFIC FEEDBACK:" not in qa_text:
|
| 955 |
+
return feedback
|
| 956 |
+
|
| 957 |
+
# Extract the section between ROLE-SPECIFIC FEEDBACK: and the next header
|
| 958 |
+
section = qa_text.split("ROLE-SPECIFIC FEEDBACK:", 1)[1]
|
| 959 |
+
for header in ("RESULT:", "RECOMMENDED FIXES:"):
|
| 960 |
+
if header in section:
|
| 961 |
+
section = section.split(header, 1)[0]
|
| 962 |
+
break
|
| 963 |
+
|
| 964 |
+
# Parse bullet lines: • Role Name: <feedback text>
|
| 965 |
+
for line in section.strip().splitlines():
|
| 966 |
+
line = line.strip().lstrip("•-* ")
|
| 967 |
+
if ":" not in line:
|
| 968 |
+
continue
|
| 969 |
+
role_label, _, role_feedback = line.partition(":")
|
| 970 |
+
role_label = role_label.strip()
|
| 971 |
+
role_feedback = role_feedback.strip()
|
| 972 |
+
role_key = _ROLE_LABEL_TO_KEY.get(role_label)
|
| 973 |
+
if role_key and role_feedback:
|
| 974 |
+
feedback[role_key] = role_feedback
|
| 975 |
+
|
| 976 |
+
return feedback
|
| 977 |
+
|
| 978 |
+
|
| 979 |
# --- Workflow step functions ---
|
| 980 |
# Each step receives the shared state and an append-only trace list,
|
| 981 |
# updates state in place, appends log lines, and returns updated state.
|
|
|
|
| 1006 |
f"Planner instructions:\n{state['plan']}"
|
| 1007 |
)
|
| 1008 |
if state["revision_count"] > 0:
|
| 1009 |
+
role_feedback = state["qa_role_feedback"].get("creative", "")
|
| 1010 |
+
if role_feedback:
|
| 1011 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1012 |
+
else:
|
| 1013 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1014 |
text = _llm_call(chat_model, _CREATIVE_SYSTEM, content)
|
| 1015 |
state["creative_output"] = text
|
| 1016 |
state["draft_output"] = text
|
|
|
|
| 1027 |
f"Planner instructions:\n{state['plan']}"
|
| 1028 |
)
|
| 1029 |
if state["revision_count"] > 0:
|
| 1030 |
+
role_feedback = state["qa_role_feedback"].get("technical", "")
|
| 1031 |
+
if role_feedback:
|
| 1032 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1033 |
+
else:
|
| 1034 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1035 |
text = _llm_call(chat_model, _TECHNICAL_SYSTEM, content)
|
| 1036 |
state["technical_output"] = text
|
| 1037 |
state["draft_output"] = text
|
|
|
|
| 1040 |
return state
|
| 1041 |
|
| 1042 |
|
| 1043 |
+
def _step_qa(
|
| 1044 |
+
chat_model,
|
| 1045 |
+
state: WorkflowState,
|
| 1046 |
+
trace: List[str],
|
| 1047 |
+
all_outputs: Optional[List[Tuple[str, str]]] = None,
|
| 1048 |
+
) -> WorkflowState:
|
| 1049 |
+
"""QA Tester: check the draft against the original request and success criteria.
|
| 1050 |
+
|
| 1051 |
+
When *all_outputs* is provided (list of (role_key, output) pairs from this
|
| 1052 |
+
iteration), each specialist's individual contribution is included in the
|
| 1053 |
+
review prompt so the QA can supply targeted, per-role feedback. This
|
| 1054 |
+
feedback is stored in ``state['qa_role_feedback']`` and consumed by the
|
| 1055 |
+
specialist step functions on the next revision pass.
|
| 1056 |
+
"""
|
| 1057 |
trace.append("\n╔══ [QA TESTER] Reviewing output... ══╗")
|
| 1058 |
content = (
|
| 1059 |
f"Original user request: {state['user_request']}\n\n"
|
| 1060 |
f"Planner's plan and success criteria:\n{state['plan']}\n\n"
|
|
|
|
| 1061 |
)
|
| 1062 |
+
if all_outputs:
|
| 1063 |
+
# Include each specialist's individual output so QA can give role-specific feedback
|
| 1064 |
+
content += "Individual specialist contributions:\n\n"
|
| 1065 |
+
for r_key, r_output in all_outputs:
|
| 1066 |
+
r_label = AGENT_ROLES.get(r_key, r_key)
|
| 1067 |
+
content += f"=== {r_label} ===\n{r_output}\n\n"
|
| 1068 |
+
content += f"Synthesized unified output:\n{state['draft_output']}"
|
| 1069 |
+
else:
|
| 1070 |
+
content += f"Specialist output to review:\n{state['draft_output']}"
|
| 1071 |
text = _llm_call(chat_model, _QA_SYSTEM, content)
|
| 1072 |
state["qa_report"] = text
|
| 1073 |
+
state["qa_role_feedback"] = _parse_qa_role_feedback(text)
|
| 1074 |
state["qa_passed"] = _qa_passed_check(text)
|
| 1075 |
result_label = "✅ PASS" if state["qa_passed"] else "❌ FAIL"
|
| 1076 |
trace.append(text)
|
| 1077 |
+
if state["qa_role_feedback"]:
|
| 1078 |
+
feedback_summary = ", ".join(
|
| 1079 |
+
f"{AGENT_ROLES.get(k, k)}: {v[:60]}{'…' if len(v) > 60 else ''}"
|
| 1080 |
+
for k, v in state["qa_role_feedback"].items()
|
| 1081 |
+
)
|
| 1082 |
+
trace.append(f" ℹ Role-specific feedback dispatched → {feedback_summary}")
|
| 1083 |
trace.append(f"╚══ [QA TESTER] Result: {result_label} ══╝")
|
| 1084 |
return state
|
| 1085 |
|
|
|
|
| 1129 |
f"Planner instructions:\n{state['plan']}"
|
| 1130 |
)
|
| 1131 |
if state["revision_count"] > 0:
|
| 1132 |
+
role_feedback = state["qa_role_feedback"].get("research", "")
|
| 1133 |
+
if role_feedback:
|
| 1134 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1135 |
+
else:
|
| 1136 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1137 |
text = _llm_call(chat_model, _RESEARCH_SYSTEM, content)
|
| 1138 |
state["research_output"] = text
|
| 1139 |
state["draft_output"] = text
|
|
|
|
| 1150 |
f"Planner instructions:\n{state['plan']}"
|
| 1151 |
)
|
| 1152 |
if state["revision_count"] > 0:
|
| 1153 |
+
role_feedback = state["qa_role_feedback"].get("security", "")
|
| 1154 |
+
if role_feedback:
|
| 1155 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1156 |
+
else:
|
| 1157 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1158 |
text = _llm_call(chat_model, _SECURITY_SYSTEM, content)
|
| 1159 |
state["security_output"] = text
|
| 1160 |
state["draft_output"] = text
|
|
|
|
| 1171 |
f"Planner instructions:\n{state['plan']}"
|
| 1172 |
)
|
| 1173 |
if state["revision_count"] > 0:
|
| 1174 |
+
role_feedback = state["qa_role_feedback"].get("data_analyst", "")
|
| 1175 |
+
if role_feedback:
|
| 1176 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1177 |
+
else:
|
| 1178 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1179 |
text = _llm_call(chat_model, _DATA_ANALYST_SYSTEM, content)
|
| 1180 |
state["data_analyst_output"] = text
|
| 1181 |
state["draft_output"] = text
|
|
|
|
| 1192 |
f"Planner instructions:\n{state['plan']}"
|
| 1193 |
)
|
| 1194 |
if state["revision_count"] > 0:
|
| 1195 |
+
role_feedback = state["qa_role_feedback"].get("mad_professor", "")
|
| 1196 |
+
if role_feedback:
|
| 1197 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1198 |
+
else:
|
| 1199 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1200 |
text = _llm_call(chat_model, _MAD_PROFESSOR_SYSTEM, content)
|
| 1201 |
state["mad_professor_output"] = text
|
| 1202 |
state["draft_output"] = text
|
|
|
|
| 1213 |
f"Planner instructions:\n{state['plan']}"
|
| 1214 |
)
|
| 1215 |
if state["revision_count"] > 0:
|
| 1216 |
+
role_feedback = state["qa_role_feedback"].get("accountant", "")
|
| 1217 |
+
if role_feedback:
|
| 1218 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1219 |
+
else:
|
| 1220 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1221 |
text = _llm_call(chat_model, _ACCOUNTANT_SYSTEM, content)
|
| 1222 |
state["accountant_output"] = text
|
| 1223 |
state["draft_output"] = text
|
|
|
|
| 1234 |
f"Planner instructions:\n{state['plan']}"
|
| 1235 |
)
|
| 1236 |
if state["revision_count"] > 0:
|
| 1237 |
+
role_feedback = state["qa_role_feedback"].get("artist", "")
|
| 1238 |
+
if role_feedback:
|
| 1239 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1240 |
+
else:
|
| 1241 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1242 |
text = _llm_call(chat_model, _ARTIST_SYSTEM, content)
|
| 1243 |
state["artist_output"] = text
|
| 1244 |
state["draft_output"] = text
|
|
|
|
| 1255 |
f"Planner instructions:\n{state['plan']}"
|
| 1256 |
)
|
| 1257 |
if state["revision_count"] > 0:
|
| 1258 |
+
role_feedback = state["qa_role_feedback"].get("lazy_slacker", "")
|
| 1259 |
+
if role_feedback:
|
| 1260 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1261 |
+
else:
|
| 1262 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1263 |
text = _llm_call(chat_model, _LAZY_SLACKER_SYSTEM, content)
|
| 1264 |
state["lazy_slacker_output"] = text
|
| 1265 |
state["draft_output"] = text
|
|
|
|
| 1276 |
f"Planner instructions:\n{state['plan']}"
|
| 1277 |
)
|
| 1278 |
if state["revision_count"] > 0:
|
| 1279 |
+
role_feedback = state["qa_role_feedback"].get("black_metal_fundamentalist", "")
|
| 1280 |
+
if role_feedback:
|
| 1281 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1282 |
+
else:
|
| 1283 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1284 |
text = _llm_call(chat_model, _BLACK_METAL_FUNDAMENTALIST_SYSTEM, content)
|
| 1285 |
state["black_metal_fundamentalist_output"] = text
|
| 1286 |
state["draft_output"] = text
|
|
|
|
| 1297 |
f"Planner instructions:\n{state['plan']}"
|
| 1298 |
)
|
| 1299 |
if state["revision_count"] > 0:
|
| 1300 |
+
role_feedback = state["qa_role_feedback"].get("labour_union_rep", "")
|
| 1301 |
+
if role_feedback:
|
| 1302 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1303 |
+
else:
|
| 1304 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1305 |
text = _llm_call(chat_model, _LABOUR_UNION_REP_SYSTEM, content)
|
| 1306 |
state["labour_union_rep_output"] = text
|
| 1307 |
state["draft_output"] = text
|
|
|
|
| 1318 |
f"Planner instructions:\n{state['plan']}"
|
| 1319 |
)
|
| 1320 |
if state["revision_count"] > 0:
|
| 1321 |
+
role_feedback = state["qa_role_feedback"].get("ux_designer", "")
|
| 1322 |
+
if role_feedback:
|
| 1323 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1324 |
+
else:
|
| 1325 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1326 |
text = _llm_call(chat_model, _UX_DESIGNER_SYSTEM, content)
|
| 1327 |
state["ux_designer_output"] = text
|
| 1328 |
state["draft_output"] = text
|
|
|
|
| 1339 |
f"Planner instructions:\n{state['plan']}"
|
| 1340 |
)
|
| 1341 |
if state["revision_count"] > 0:
|
| 1342 |
+
role_feedback = state["qa_role_feedback"].get("doris", "")
|
| 1343 |
+
if role_feedback:
|
| 1344 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1345 |
+
else:
|
| 1346 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1347 |
text = _llm_call(chat_model, _DORIS_SYSTEM, content)
|
| 1348 |
state["doris_output"] = text
|
| 1349 |
state["draft_output"] = text
|
|
|
|
| 1360 |
f"Planner instructions:\n{state['plan']}"
|
| 1361 |
)
|
| 1362 |
if state["revision_count"] > 0:
|
| 1363 |
+
role_feedback = state["qa_role_feedback"].get("chairman_of_board", "")
|
| 1364 |
+
if role_feedback:
|
| 1365 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1366 |
+
else:
|
| 1367 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1368 |
text = _llm_call(chat_model, _CHAIRMAN_SYSTEM, content)
|
| 1369 |
state["chairman_of_board_output"] = text
|
| 1370 |
state["draft_output"] = text
|
|
|
|
| 1381 |
f"Planner instructions:\n{state['plan']}"
|
| 1382 |
)
|
| 1383 |
if state["revision_count"] > 0:
|
| 1384 |
+
role_feedback = state["qa_role_feedback"].get("maga_appointee", "")
|
| 1385 |
+
if role_feedback:
|
| 1386 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1387 |
+
else:
|
| 1388 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1389 |
text = _llm_call(chat_model, _MAGA_APPOINTEE_SYSTEM, content)
|
| 1390 |
state["maga_appointee_output"] = text
|
| 1391 |
state["draft_output"] = text
|
|
|
|
| 1402 |
f"Planner instructions:\n{state['plan']}"
|
| 1403 |
)
|
| 1404 |
if state["revision_count"] > 0:
|
| 1405 |
+
role_feedback = state["qa_role_feedback"].get("lawyer", "")
|
| 1406 |
+
if role_feedback:
|
| 1407 |
+
content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
|
| 1408 |
+
else:
|
| 1409 |
+
content += f"\n\nQA feedback to address:\n{state['qa_report']}"
|
| 1410 |
text = _llm_call(chat_model, _LAWYER_SYSTEM, content)
|
| 1411 |
state["lawyer_output"] = text
|
| 1412 |
state["draft_output"] = text
|
|
|
|
| 1477 |
"labour_union_rep_output": "", "ux_designer_output": "", "doris_output": "",
|
| 1478 |
"chairman_of_board_output": "", "maga_appointee_output": "", "lawyer_output": "",
|
| 1479 |
"synthesis_output": "",
|
| 1480 |
+
"draft_output": "", "qa_report": "", "qa_role_feedback": {}, "qa_passed": False,
|
| 1481 |
"revision_count": 0, "final_answer": "",
|
| 1482 |
}
|
| 1483 |
|
|
|
|
| 1657 |
1. Planner (if active) analyses the task and picks a primary specialist.
|
| 1658 |
2. ALL active specialists run and contribute their own perspective.
|
| 1659 |
3. Synthesizer summarises all perspectives, finds common ground, and produces a unified recommendation.
|
| 1660 |
+
4. QA Tester (if active) reviews the synthesized output and provides targeted, per-role feedback
|
| 1661 |
+
so each specialist knows exactly what to improve in the next iteration.
|
| 1662 |
5. Planner (if active) reviews QA result and either approves or requests a revision.
|
| 1663 |
+
6. Repeat from step 2 if QA fails and retries remain — each specialist now receives their own
|
| 1664 |
+
targeted QA feedback and refines their proposition accordingly (iterative approach).
|
| 1665 |
7. If max retries are reached, return best attempt with QA concerns.
|
| 1666 |
|
| 1667 |
Args:
|
|
|
|
| 1719 |
"synthesis_output": "",
|
| 1720 |
"draft_output": "",
|
| 1721 |
"qa_report": "",
|
| 1722 |
+
"qa_role_feedback": {},
|
| 1723 |
"qa_passed": False,
|
| 1724 |
"revision_count": 0,
|
| 1725 |
"final_answer": "",
|
|
|
|
| 1783 |
|
| 1784 |
# Step 3: QA reviews the specialist's draft (if enabled)
|
| 1785 |
if qa_active:
|
| 1786 |
+
state = _step_qa(chat_model, state, trace, all_outputs)
|
| 1787 |
else:
|
| 1788 |
state["qa_passed"] = True
|
| 1789 |
state["qa_report"] = "QA Tester is disabled — skipping quality review."
|