Skip to content

Commit 8b48991

Browse files
committed
fix: address human review feedback on PR #166
- Remove redundant isinstance checks in _resolve_model_config; Pydantic ModelConfigDocument validates dict[str, dict[str, Any]] at parse time, making runtime type checks dead code. Update test to verify Pydantic validation instead of testing the removed checks. - Remove shadowing local imports of os and typer in cli.py that duplicate top-level imports. - Don't inject default temperature (0.0) when api_type is 'responses'; the responses API rejects unsupported parameters. MODEL_TEMP env var and YAML model_settings still override for both API types. Preserves backward-compatible 0.0 default for chat_completions. - Narrow task-level retry to transient network exceptions only (APIConnectionError, APITimeoutError, ConnectionError, TimeoutError). Non-retriable errors (ValueError, RuntimeError, etc.) now break immediately instead of burning through retry attempts. Prevents blind retries of deterministic failures and side-effectful repeat_prompt re-execution on non-transient errors.
1 parent a147e9e commit 8b48991

3 files changed

Lines changed: 23 additions & 22 deletions

File tree

src/seclab_taskflow_agent/cli.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def _parse_global(value: str) -> tuple[str, str]:
4343

4444
def _setup_logging() -> None:
4545
"""Configure root logger: file (DEBUG) + console (ERROR)."""
46-
import os
4746
from logging.handlers import RotatingFileHandler
4847

4948
root = logging.getLogger("")
@@ -68,8 +67,6 @@ def _print_concise_error(exc: BaseException) -> None:
6867
Walks the exception cause chain and prints each error on a single
6968
line. Use ``--debug`` or ``TASK_AGENT_DEBUG=1`` for full tracebacks.
7069
"""
71-
import typer
72-
7370
seen: set[int] = set()
7471
current: BaseException | None = exc
7572
while current and id(current) not in seen:

src/seclab_taskflow_agent/runner.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from agents.agent import ModelSettings
3232
from agents.exceptions import AgentsException, MaxTurnsExceeded
3333
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
34-
from openai import APITimeoutError, BadRequestError, RateLimitError
34+
from openai import APIConnectionError, APITimeoutError, BadRequestError, RateLimitError
3535
from openai.types.responses import ResponseTextDeltaEvent
3636

3737
from .agent import DEFAULT_MODEL, TaskAgent, TaskAgentHooks, TaskRunHooks
@@ -74,20 +74,13 @@ def _resolve_model_config(
7474
"""
7575
m_config: ModelConfigDocument = available_tools.get_model_config(model_config_ref)
7676
model_dict: dict[str, str] = m_config.models or {}
77-
if model_dict and not isinstance(model_dict, dict):
78-
raise ValueError(f"Models section of the model_config file {model_config_ref} must be a dictionary")
7977
model_keys: list[str] = list(model_dict.keys())
8078
models_params: dict[str, dict[str, Any]] = m_config.model_settings or {}
81-
if models_params and not isinstance(models_params, dict):
82-
raise ValueError(f"Settings section of model_config file {model_config_ref} must be a dictionary")
8379
unknown = set(models_params) - set(model_keys)
8480
if unknown:
8581
raise ValueError(
8682
f"Settings section of model_config file {model_config_ref} contains models not in the model section: {unknown}"
8783
)
88-
for k, v in models_params.items():
89-
if not isinstance(v, dict):
90-
raise ValueError(f"Settings for model {k} in model_config file {model_config_ref} is not a dictionary")
9184
return model_keys, model_dict, models_params, m_config.api_type
9285

9386

@@ -299,10 +292,17 @@ async def deploy_task_agents(
299292
# Model settings
300293
parallel_tool_calls = bool(os.getenv("MODEL_PARALLEL_TOOL_CALLS"))
301294
model_params: dict[str, Any] = {
302-
"temperature": os.getenv("MODEL_TEMP", default=0.0),
303295
"tool_choice": "auto" if toolboxes else None,
304296
"parallel_tool_calls": parallel_tool_calls if toolboxes else None,
305297
}
298+
# Only inject a default temperature for chat_completions; the responses
299+
# API rejects unsupported parameters. MODEL_TEMP env override applies
300+
# to both API types.
301+
model_temp = os.getenv("MODEL_TEMP")
302+
if model_temp is not None:
303+
model_params["temperature"] = model_temp
304+
elif api_type != "responses":
305+
model_params["temperature"] = 0.0
306306
model_params.update(model_par)
307307
model_settings = ModelSettings(**model_params)
308308

@@ -660,7 +660,11 @@ async def _deploy(ra: dict, pp: str) -> bool:
660660
complete = result and complete
661661
return complete
662662

663-
# Execute the task with auto-retry on failure
663+
# Execute the task with auto-retry on transient failures.
664+
# Only retry on network/API errors — deterministic failures
665+
# and errors after side-effectful work should not be retried
666+
# blindly (e.g. repeat_prompt tasks may have already written
667+
# data to external systems).
664668
task_name = task.name or f"task-{task_index}"
665669
task_complete = False
666670
last_task_error: BaseException | None = None
@@ -675,7 +679,7 @@ async def _deploy(ra: dict, pp: str) -> bool:
675679
break
676680
except (KeyboardInterrupt, SystemExit):
677681
raise
678-
except Exception as exc:
682+
except (APIConnectionError, APITimeoutError, ConnectionError, TimeoutError) as exc:
679683
last_task_error = exc
680684
remaining = TASK_RETRY_LIMIT - attempt - 1
681685
if remaining > 0:
@@ -688,6 +692,10 @@ async def _deploy(ra: dict, pp: str) -> bool:
688692
await asyncio.sleep(backoff)
689693
else:
690694
logging.error(f"Task {task_name!r} failed after {TASK_RETRY_LIMIT} attempts: {exc}")
695+
except Exception as exc:
696+
last_task_error = exc
697+
logging.error(f"Task {task_name!r} failed (non-retriable): {exc}")
698+
break
691699

692700
# If all retries exhausted with an exception, save and re-raise
693701
if last_task_error is not None:

tests/test_runner.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,10 @@ def test_model_settings_extraction(self):
107107
assert params == {"m1": {"temperature": 0.5}}
108108

109109
def test_validation_error_on_non_dict_settings(self):
110-
"""Non-dict model_settings raises ValueError."""
111-
at = _mock_available_tools()
112-
cfg = _make_model_config(models={"m1": "p-m1"})
113-
# Manually set model_settings to a non-dict after construction
114-
object.__setattr__(cfg, "model_settings", "not-a-dict")
115-
at.get_model_config.return_value = cfg
116-
with pytest.raises(ValueError, match="must be a dictionary"):
117-
_resolve_model_config(at, "ref")
110+
"""Pydantic rejects non-dict model_settings at parse time."""
111+
from pydantic import ValidationError
112+
with pytest.raises(ValidationError):
113+
_make_model_config(models={"m1": "p-m1"}, model_settings="not-a-dict")
118114

119115

120116
# ===================================================================

0 commit comments

Comments
 (0)