Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ also holds a PR to:
constraint or the reason, and end with an issue or PR URL when a reader needs
the backstory. A block of three or more `#` lines fails once the PR adds or
edits a line in it, so shorten an old block when you touch it.
- No silent `except` (ruff BLE001, S110, S112): catch the exception you expect, or
log it with its traceback, or re-raise. A site that must catch everything says
what happens to the failure in `# noqa: BLE001 - <reason>`
(`tests/test_noqa_reasons.py` fails a bare code).
- User outcomes (`tests/outcomes/*.json`, checked by `pytest --outcomes`): a
test marked `@pytest.mark.outcome(...)` is evidence for a promise in the docs;
weaken or drop it only together with the doc and the contract. The same
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
### Changed

- A library id that cannot be resolved for an arm now logs a warning before the arm
falls back to the product name, and a failed MCP tool listing or GitHub issue fetch
is logged instead of dropped silently. JSON extraction from LLM replies catches only
parse errors, so a bug in it is no longer read as "no JSON here".
2 changes: 1 addition & 1 deletion dokimi/dokimi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def main() -> None:
# 2 is the one code for bad input: 1 is the verdict of `eval judge validate` and of the
# --strict gates, and a malformed file must not read as a failed gate.
sys.exit(2)
except Exception as exc:
except Exception as exc: # noqa: BLE001 - printed with its traceback, exit 5
# A crash is an infra failure, exit 5 for every command (§2.6): it never reads as a failed verdict.
traceback.print_exc()
if _emits_cli_output(args): # a --json leaf still prints its one document on a crash
Expand Down
8 changes: 4 additions & 4 deletions dokimi/dokimi/commands/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def _check_questions_from(questions_from: str, key: str) -> tuple[Path, list, st
return qf, [], str(exc)
try:
return qf, load_questions_payload(qf), None
except Exception as exc:
except Exception as exc: # noqa: BLE001 - any load failure is reported as an invalid source
return qf, [], f"questions source invalid: {qf} ({exc})"


Expand All @@ -230,7 +230,7 @@ def _check_golden_set(questions_root: str, key: str, refuse: Callable[[str], Non
bad = next((i for i, q in enumerate(cast(List[Any], golden_questions)) if not isinstance(q, dict)), None)
if bad is not None:
refuse(f"golden set item {bad} is not a dict: {golden}")
except Exception as exc:
except Exception as exc: # noqa: BLE001 - any load failure refuses the run, with its message
refuse(f"golden set invalid: {golden} ({exc})")


Expand All @@ -254,7 +254,7 @@ def _check_doc_source(plan: dict, entry: Any, refuse: Callable[[str], None], err
warnings.append("context7 id not pinned; client will resolve by target name")
except ValueError as exc:
refuse(f"doc source invalid: {plan['doc_source']} ({exc})")
except Exception as exc: # the source cannot be reached or built: a failed check
except Exception as exc: # noqa: BLE001 - the source cannot be reached or built: a failed check
errors.append(f"doc source check failed: {plan['doc_source']} ({exc})")


Expand Down Expand Up @@ -442,7 +442,7 @@ def cmd_benchmark_batch(args: argparse.Namespace) -> None:
registry=registry,
)
results.append(r)
except Exception as exc:
except Exception as exc: # noqa: BLE001 - one failed library is recorded; --fail-fast stops
msg = f"FAILED: {entry.key} — {exc}"
print(f"\n❌ {msg}", file=sys.stderr)
failed.append({"library": entry.key, "name": entry.name, "status": "failed", "error": str(exc)})
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/commands/eval_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def _author(name: str, description: str, args: argparse.Namespace,
raise
# The call is paid for and its reply is the graph, as measure.py keeps such a reply.
return _graph_from_unmetered(name, description, reply), True
except Exception as exc: # an unreachable or unauthenticated model: not "an empty graph"
except Exception as exc: # noqa: BLE001 - an unreachable model exits non-zero, not "an empty graph"
print(f"Error: the graph-author call failed: {exc}; nothing was written", file=sys.stderr)
sys.exit(EXIT_FAILURE)

Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/commands/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def _run_ragas_eval(answers, output_path, args) -> None:
raise # a bad input record is a refusal, not a RAGAS failure: cli.main reports it (exit 2)
except ImportError as exc:
sys.exit(f"RAGAS meta-evaluation was requested but cannot run: {exc}")
except Exception as exc:
except Exception as exc: # noqa: BLE001 - RAGAS is an optional extra pass; its failure is printed
print(f"\n⚠️ RAGAS evaluation failed: {exc}")


Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/commands/orchestrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def cmd_evaluate(args: argparse.Namespace) -> None:

print(f"\n📊 View full report: cat {results['steps']['report']['path']}")

except Exception as e:
except Exception as e: # noqa: BLE001 - top-level: prints the traceback and exits 1
print(f"\n❌ Pipeline failed: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/commands/subjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def cmd_subjects_list(args: argparse.Namespace) -> None:

descriptor = load_subject(path)
print(f"{descriptor.id}\t{descriptor.subject.kind}\t{path}")
except Exception as exc:
except Exception as exc: # noqa: BLE001 - one bad descriptor is listed as ERROR, the rest still print
print(f"{path}\tERROR\t{exc}")


Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/dashboard/aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def aggregate(self) -> DashboardData:
def _load_snapshot(self, path: Path) -> Optional[ProductSnapshot]:
try:
data = json.loads(path.read_text(encoding="utf-8"))
except Exception as e:
except Exception as e: # noqa: BLE001 - an unreadable snapshot is logged and skipped
logger.warning(f"Cannot read {path}: {e}")
return None

Expand Down
4 changes: 2 additions & 2 deletions dokimi/dokimi/eval/answerer.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def __init__(
try:
self.reranker = SentenceTransformerReranker(threshold=rerank_threshold)
logger.info(f"Semantic reranker initialized with threshold={rerank_threshold:.2f}")
except Exception as e:
except Exception as e: # noqa: BLE001 - logged; the lexical reranker is the documented fallback
logger.warning(f"Semantic reranker failed ({e}); fallback to lexical reranker")
self.reranker = SimpleReranker(threshold=rerank_threshold)
else:
Expand Down Expand Up @@ -164,7 +164,7 @@ def _process(idx: int, q: Dict) -> None:
question=q,
max_tokens=max_tokens_per_question
)
except Exception as e:
except Exception as e: # noqa: BLE001 - the failure is stored on the pair and reported
failure = e
pair = {
"question_id": q_id,
Expand Down
5 changes: 5 additions & 0 deletions dokimi/dokimi/eval/arms_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from __future__ import annotations

import functools
import logging
from collections.abc import Callable
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING
Expand All @@ -35,6 +36,8 @@
from dokimi.eval import Judge, JudgePanel
from dokimi.eval.arm_runner import ArmRunner

logger = logging.getLogger(__name__)


class _DefaultJudgeProvider(str):
"""The judge provider left out: equal to the default name, told apart from a named one by identity."""
Expand Down Expand Up @@ -222,6 +225,8 @@ def resolve(arm):
except ValueError:
raise
except Exception:
logger.warning("library id for %r not resolved; using the product name", product,
Comment thread
napetrov marked this conversation as resolved.
Resolved
exc_info=True)
arm.library_id = product
return arm.library_id

Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/eval/cross_judge.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ def one(job):
return (qid, arm), float(
scorer(library, question, answer, (ground_truth or {}).get(qid), context)
), None
except Exception as exc: # an unscored answer drops out of the pairing, it does not kill the run
except Exception as exc: # noqa: BLE001 - an unscored answer drops out of the pairing, it does not kill the run
return (qid, arm), None, _reason(exc)

if not stored_flags:
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/eval/pairwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def one(job):
arm, qid, question, answer, base = job
try:
return arm, qid, judge.compare(library, question, answer, base)
except Exception as exc: # one failed question must not lose the run
except Exception as exc: # noqa: BLE001 - one failed question must not lose the run
return arm, qid, exc

report = ArmPairwiseReport(judge=label, baseline_arm=baseline_arm)
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/eval/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ def send(text: str, *, draw: int, cache_if: Callable[[str], bool]) -> str:
caps=capped.get("caps"), unread_claims=capped.get("unread_claims"),
numeric_claims=claims if isinstance(claims := data.get("numeric_claims"), list) else None,
)
except Exception as exc:
except Exception as exc: # noqa: BLE001 - logged; the vote carries the error and drops out
logger.warning(f"Judge '{config.role}' ({config.model}) failed: {exc}")
return JudgeVote(
role=config.role, model=config.model, provider=config.provider,
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/eval/ragas_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def _score(self, Dataset, ragas_evaluate, rows: List[Dict[str, Any]], metrics: l
}
summary = {n: round(sum(vals) / len(vals), 4)
for n in names if (vals := df[n].dropna().tolist())}
except Exception as exc:
except Exception as exc: # noqa: BLE001 - logged; RAGAS is optional, the run keeps its own scores
logger.error("RAGAS %s evaluation failed: %s", arm, exc)
return {}, {}
return scores, summary
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/eval/reranker.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
try:
from sentence_transformers import SentenceTransformer, util
SENTENCE_TRANSFORMERS_AVAILABLE = True
except Exception:
except Exception: # noqa: BLE001 - importing torch can raise more than ImportError
SENTENCE_TRANSFORMERS_AVAILABLE = False


Expand Down
6 changes: 3 additions & 3 deletions dokimi/dokimi/eval/validity_scoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def intact(pair: Dict[str, Any]) -> Tuple[tuple, Any]:
for _ in range(INTACT_ATTEMPTS):
try:
return key, _call(scorer, pair, pair["intact"])
except Exception as exc: # one bad pair must not lose the whole run
except Exception as exc: # noqa: BLE001 - one bad pair must not lose the whole run
failure = exc
return key, failure

Expand All @@ -152,7 +152,7 @@ def degraded(pair: Dict[str, Any]) -> ValidityRow:
if pair_scorer is not None:
try:
row.intact_score, row.degraded_score = pair_scorer(pair)
except Exception as exc: # one bad pair must not lose the whole run
except Exception as exc: # noqa: BLE001 - one bad pair must not lose the whole run
row.error = f"{type(exc).__name__}: {exc}"[:200]
return row
base = intact_cache[(pair["base_id"], pair.get("arm", ""))]
Expand All @@ -161,7 +161,7 @@ def degraded(pair: Dict[str, Any]) -> ValidityRow:
raise base
row.intact_score = base
row.degraded_score = _call(scorer, pair, pair["degraded"])
except Exception as exc: # one bad pair must not lose the whole run
except Exception as exc: # noqa: BLE001 - one bad pair must not lose the whole run
row.error = f"{type(exc).__name__}: {exc}"[:200]
return row

Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/harnesses/egress_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def _handle(self, client: socket.socket) -> None:
client.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
state["established"] = True
_relay(client, remote)
except Exception as exc: # one bad tunnel must not take the proxy down
except Exception as exc: # noqa: BLE001 - one bad tunnel must not take the proxy down
self._record(state["host"], state["port"], "error", f"{type(exc).__name__}: {exc}")
if not state["established"]: # the client learns the route failed, not just a closed socket
try:
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/judged.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _accepts(parse: Callable[[str], Any]) -> Callable[[str], bool]:
def parses(raw: str) -> bool:
try:
return parse(raw) is not None
except Exception:
except Exception: # noqa: BLE001 - a cache predicate: any parse failure means do not cache
return False
return parses

Expand Down
8 changes: 4 additions & 4 deletions dokimi/dokimi/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def unsupported_sampling_params(
return [n for n in requested if n not in cli.supports]
try:
supported = _litellm().get_supported_openai_params(model=resolve_model(model, provider)) or []
except Exception:
except Exception: # noqa: BLE001 - litellm raises many types for an unknown model; [] drops nothing
return []
if not supported:
return []
Expand Down Expand Up @@ -352,7 +352,7 @@ def _safe_completion_cost(resp: Any, model: str, litellm_model: Optional[str] =
if cost == 0.0 and not _has_price_entry(litellm_model or model, model):
raise ValueError("model is not in litellm's price map")
return cost
except Exception as exc:
except Exception as exc: # noqa: BLE001 - logged once per model; cost is null, never a fake $0
if model not in _COST_UNKNOWN_LOGGED:
_COST_UNKNOWN_LOGGED.add(model)
logger.info("No litellm pricing for model %r; cost_usd will be null (%s)", model, exc)
Expand Down Expand Up @@ -891,7 +891,7 @@ def ask_parsed(ask: Ask, prompt: str, parse: Callable[[str], Optional[T]], *,
def parses(raw: str) -> bool:
try:
return parse(raw) is not None
except Exception:
except Exception: # noqa: BLE001 - a cache predicate: any parse failure means do not cache
return False

raw, error = "", ""
Expand All @@ -900,7 +900,7 @@ def parses(raw: str) -> bool:
raw = ask(asked, draw=draw * attempts + attempt, cache_if=parses)
try:
value = parse(raw)
except Exception as exc:
except Exception as exc: # noqa: BLE001 - the parse error is kept and re-asked, then raised
error = str(exc)
else:
if value is not None:
Expand Down
8 changes: 4 additions & 4 deletions dokimi/dokimi/llm_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def extract_json_object(text: str) -> dict:
result = json.loads(text)
if isinstance(result, dict):
return result
except Exception:
except (ValueError, RecursionError): # not JSON here: try the next form
Comment thread
napetrov marked this conversation as resolved.
Resolved
pass
# Markdown fence
fence = re.search(r"```(?:json)?\s*([\s\S]*?)```", text)
Expand All @@ -34,7 +34,7 @@ def extract_json_object(text: str) -> dict:
result = json.loads(fence.group(1).strip())
if isinstance(result, dict):
return result
except Exception:
except (ValueError, RecursionError):
pass
# First { ... } block
m = re.search(r"\{[\s\S]*\}", text)
Expand All @@ -58,7 +58,7 @@ def extract_json_array(text: str) -> list:
result = json.loads(text)
if isinstance(result, list):
return result
except Exception:
except (ValueError, RecursionError): # not JSON here: try the next form
pass
# Markdown fence
fence = re.search(r"```(?:json)?\s*([\s\S]*?)```", text)
Expand All @@ -67,7 +67,7 @@ def extract_json_array(text: str) -> list:
result = json.loads(fence.group(1).strip())
if isinstance(result, list):
return result
except Exception:
except (ValueError, RecursionError):
pass
# First [ ... ] block
m = re.search(r"\[[\s\S]*\]", text)
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/llm_pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def list_cost_usd(model: str, provider: str, prompt_tokens: int,
try:
prompt, completion = cost_per_token(model=litellm_model, prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens)
except Exception:
except Exception: # noqa: BLE001 - litellm raises many types for an unpriced model; None = unknown
return None
if prompt + completion == 0.0 and not _has_price_entry(litellm_model, model):
return None
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/mcp/context7.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def check_connection(self, library_id: str, query: str = "overview") -> bool: #
max_tokens=500
)
return len(docs) > 0 and len(docs[0]["content"]) > 0
except Exception as e:
except Exception as e: # noqa: BLE001 - logged; a connection check answers False
logger.error(f"Context7 connection check failed: {e}")
return False

Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/mcp/local_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def get_library_docs(
for fp in files:
try:
content = self._read_file(fp, char_limit)
except Exception as exc:
except Exception as exc: # noqa: BLE001 - logged at debug; one unreadable file is skipped
logger.debug(f"Skipping {fp}: {exc}")
continue

Expand Down
3 changes: 2 additions & 1 deletion dokimi/dokimi/mcp/mcp_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def resolve_library_id(self, library_name: str) -> str:
tools = self._list_tool_names()
resolve = self._pick_tool(tools, self.resolve_tool, _RESOLVE_TOOL_HINTS)
except Exception:
logger.exception("listing MCP tools failed; resolving by name")
resolve = None
if not resolve:
return library_name
Expand Down Expand Up @@ -127,7 +128,7 @@ def check_connection(self) -> bool:
try:
self._list_tool_names()
return True
except Exception:
except Exception: # noqa: BLE001 - a connection check answers False on any failure
return False

# ── MCP session plumbing ────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/mcp/okf_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def get_library_docs(
for fp in files:
try:
raw = fp.read_text(encoding=self.encoding)
except Exception as exc:
except Exception as exc: # noqa: BLE001 - logged at debug; one unreadable file is skipped
logger.debug("OKFClient: skipping %s: %s", fp, exc)
continue

Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/mcp/url_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def check_connection(self) -> bool:
try:
resp = httpx.head(self.url, timeout=self.timeout, follow_redirects=True)
return 200 <= resp.status_code < 300
except Exception as exc:
except Exception as exc: # noqa: BLE001 - logged at debug; a connection check answers False
logger.debug(f"URLClient.check_connection failed: {exc}")
return False

Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/metrics/example_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def _run_docker(lang: str, code: str, policy: ExecutionPolicy,
result = runner(cmd, timeout=policy.timeout + 30, env=process.Env.inherit())
except FileNotFoundError:
return False, "docker backend selected but docker is not installed"
except Exception as exc: # noqa: BLE001
except Exception as exc: # noqa: BLE001 - reported as the run's execution error
return False, f"Execution error: {type(exc).__name__}: {exc}"

if result.timed_out:
Expand Down
Loading
Loading