Skip to content

Commit 1899eb4

Browse files
committed
fix(sonar): reduce complexity in runs.py/synthesis.py, fix S1192/S7503
1 parent caa6f0c commit 1899eb4

3 files changed

Lines changed: 82 additions & 51 deletions

File tree

src/certamen/application/workflow/nodes/synthesis.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@
1616
from certamen.infrastructure.llm import ensure_single_model_instance
1717

1818

19+
async def _resolve_model(model_input: Any, context: ExecutionContext) -> Any:
20+
if isinstance(model_input, dict):
21+
model_input = await ensure_single_model_instance(
22+
model_input, "synthesizer"
23+
)
24+
if model_input is None:
25+
model_keys = list(context.models.keys())
26+
if not model_keys:
27+
return None
28+
return context.models[model_keys[0]]
29+
return model_input
30+
31+
1932
def _build_prompt_builder(
2033
instruction: str | None = None,
2134
) -> PromptBuilder:
@@ -93,20 +106,9 @@ async def execute(
93106
if not responses or not question:
94107
return {"synthesis": ""}
95108

109+
synth_model = await _resolve_model(synth_model, context)
96110
if synth_model is None:
97-
model_keys = list(context.models.keys())
98-
if not model_keys:
99-
return {"synthesis": "[No model available for synthesis]"}
100-
synth_model = context.models[model_keys[0]]
101-
elif isinstance(synth_model, dict):
102-
synth_model = await ensure_single_model_instance(
103-
synth_model, "synthesizer"
104-
)
105-
if synth_model is None:
106-
model_keys = list(context.models.keys())
107-
if not model_keys:
108-
return {"synthesis": "[No model available for synthesis]"}
109-
synth_model = context.models[model_keys[0]]
111+
return {"synthesis": "[No model available for synthesis]"}
110112

111113
instruction = self.node_properties.get("instruction") or None
112114
prompt_builder = _build_prompt_builder(instruction)

src/certamen/interfaces/cli/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,9 @@ async def _execute_workflow_dict(
380380
},
381381
)
382382

383-
async def broadcast_event(message_str: str) -> None:
383+
async def broadcast_event(
384+
message_str: str,
385+
) -> None: # NOSONAR - awaited by AsyncExecutor._broadcast
384386
try:
385387
msg = json.loads(message_str)
386388
except (json.JSONDecodeError, TypeError):

src/certamen/interfaces/web/runs.py

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
logger = get_contextual_logger(__name__)
1212

1313
EVENTS_FILENAME = "events.jsonl"
14+
_RUN_NOT_FOUND = "run not found"
1415

1516

1617
def _runs_root() -> Path:
@@ -39,6 +40,28 @@ def _read_events_from_offset(
3940
return events, size
4041

4142

43+
def _apply_event_to_run_summary(
44+
summary: dict[str, Any],
45+
et: str,
46+
p: dict[str, Any],
47+
ts: float,
48+
flags: dict[str, bool],
49+
) -> None:
50+
if et == "tournament_started":
51+
flags["started"] = True
52+
summary["question"] = p.get("question")
53+
summary["model_count"] = len(p.get("models", []))
54+
summary["started_at"] = ts
55+
elif et == "llm_response":
56+
summary["total_cost"] += float(p.get("cost") or 0.0)
57+
elif et == "tournament_ended":
58+
flags["ended"] = True
59+
summary["champion"] = p.get("champion")
60+
summary["ended_at"] = ts
61+
if p.get("total_cost") is not None:
62+
summary["total_cost"] = float(p["total_cost"])
63+
64+
4265
def _summarize_run(run_dir: Path) -> dict[str, Any]:
4366
events_path = run_dir / EVENTS_FILENAME
4467
summary: dict[str, Any] = {
@@ -56,8 +79,7 @@ def _summarize_run(run_dir: Path) -> dict[str, Any]:
5679
summary["status"] = "missing"
5780
return summary
5881

59-
started = False
60-
ended = False
82+
flags = {"started": False, "ended": False}
6183
for raw in events_path.read_text(encoding="utf-8").splitlines():
6284
line = raw.strip()
6385
if not line:
@@ -67,26 +89,17 @@ def _summarize_run(run_dir: Path) -> dict[str, Any]:
6789
except json.JSONDecodeError:
6890
continue
6991
summary["event_count"] += 1
70-
et = event.get("event_type", "")
71-
p = event.get("payload", {})
72-
ts = event.get("ts", 0)
73-
if et == "tournament_started":
74-
started = True
75-
summary["question"] = p.get("question")
76-
summary["model_count"] = len(p.get("models", []))
77-
summary["started_at"] = ts
78-
elif et == "llm_response":
79-
summary["total_cost"] += float(p.get("cost") or 0.0)
80-
elif et == "tournament_ended":
81-
ended = True
82-
summary["champion"] = p.get("champion")
83-
summary["ended_at"] = ts
84-
if p.get("total_cost") is not None:
85-
summary["total_cost"] = float(p["total_cost"])
86-
87-
if ended:
92+
_apply_event_to_run_summary(
93+
summary,
94+
event.get("event_type", ""),
95+
event.get("payload", {}),
96+
event.get("ts", 0),
97+
flags,
98+
)
99+
100+
if flags["ended"]:
88101
summary["status"] = "completed"
89-
elif started:
102+
elif flags["started"]:
90103
summary["status"] = "running"
91104
else:
92105
summary["status"] = "empty"
@@ -111,15 +124,15 @@ def get_run(request: web.Request) -> web.Response:
111124
run_id = request.match_info["run_id"]
112125
run_dir = _runs_root() / run_id
113126
if not run_dir.is_dir():
114-
return web.json_response({"error": "run not found"}, status=404)
127+
return web.json_response({"error": _RUN_NOT_FOUND}, status=404)
115128
return web.json_response(_summarize_run(run_dir))
116129

117130

118-
async def get_run_events(request: web.Request) -> web.Response:
131+
def get_run_events(request: web.Request) -> web.Response:
119132
run_id = request.match_info["run_id"]
120133
run_dir = _runs_root() / run_id
121134
if not run_dir.is_dir():
122-
return web.json_response({"error": "run not found"}, status=404)
135+
return web.json_response({"error": _RUN_NOT_FOUND}, status=404)
123136

124137
from_seq = int(request.query.get("from_seq", "0"))
125138
limit = int(request.query.get("limit", "10000"))
@@ -144,6 +157,26 @@ async def _replay_events(
144157
return last_seq, size
145158

146159

160+
async def _consume_ws_messages(ws: web.WebSocketResponse) -> None:
161+
async for msg in ws:
162+
if msg.type == WSMsgType.ERROR:
163+
break
164+
165+
166+
async def _forward_new_events(
167+
ws: web.WebSocketResponse,
168+
new_events: list[dict[str, Any]],
169+
last_seq: int,
170+
) -> tuple[int, bool]:
171+
for event in new_events:
172+
try:
173+
await ws.send_json({"type": "event", "event": event})
174+
last_seq = event.get("seq", last_seq)
175+
except ConnectionResetError:
176+
return last_seq, True
177+
return last_seq, False
178+
179+
147180
async def _tail_events(
148181
ws: web.WebSocketResponse,
149182
events_path: Path,
@@ -154,12 +187,7 @@ async def _tail_events(
154187
idle_timeout = 600
155188
idle_seconds = 0.0
156189

157-
async def consumer() -> None:
158-
async for msg in ws:
159-
if msg.type == WSMsgType.ERROR:
160-
break
161-
162-
consumer_task = asyncio.create_task(consumer())
190+
consumer_task = asyncio.create_task(_consume_ws_messages(ws))
163191
try:
164192
while not ws.closed:
165193
await asyncio.sleep(poll_interval)
@@ -178,12 +206,11 @@ async def consumer() -> None:
178206
new_events, last_size = _read_events_from_offset(
179207
events_path, last_seq
180208
)
181-
for event in new_events:
182-
try:
183-
await ws.send_json({"type": "event", "event": event})
184-
last_seq = event.get("seq", last_seq)
185-
except ConnectionResetError:
186-
break
209+
last_seq, disconnected = await _forward_new_events(
210+
ws, new_events, last_seq
211+
)
212+
if disconnected:
213+
break
187214
if any(
188215
e.get("event_type") == "tournament_ended" for e in new_events
189216
):
@@ -206,7 +233,7 @@ async def attach_run_websocket(
206233
await ws.prepare(request)
207234

208235
if not run_dir.is_dir():
209-
await ws.send_json({"type": "error", "message": "run not found"})
236+
await ws.send_json({"type": "error", "message": _RUN_NOT_FOUND})
210237
await ws.close()
211238
return ws
212239

0 commit comments

Comments
 (0)