fix(demo): produce a real report instead of hanging, then an empty one - #77
Merged
Conversation
Closes #74. `aicertify demo` did not finish. A run was killed at 1156s still sitting on "Starting evaluation against eu_ai_act". It now completes in about two minutes and writes a report containing actual verdicts. Three separate defects, found in that order because each one hid the next. 1. It blocked indefinitely on an LLM call. py-spy showed both threads idle in select(), the worker inside deepeval/metrics/toxicity/toxicity.py. DeepEval's ToxicityMetric is LLM-judged, so measure() is a network call with no bound. ContentSafetyEvaluator now runs each interaction under a wall-clock bound (llm_timeout_seconds, default 60). A timeout returns the same verdict as the unavailable path: worst-case toxicity and passed=False. A content-safety check that did not complete has established nothing, and reporting it clean would be the fail-open this library has closed three times in the policies. The worker is a daemon thread, not a ThreadPoolExecutor. Executor threads are non-daemon and the interpreter joins them at exit, so a call still blocked in the provider would keep the process alive after we stopped waiting for it, which defeats the bound. Measured: abandoning a 300s call adds no exit delay. 2. It made billable calls uninvited. The LLM-judged evaluators activate off the presence of OPENAI_API_KEY, so the demo advertised as needing "no API keys" silently became a paid, minutes-long run for anyone with a key exported. The demo now hides that variable for its own run unless --with-llm-metrics is passed, restoring it in a finally block, and says which mode it is in. This uses the skip-gracefully path the runner's own docstring already described. 3. Then it produced an empty report and called it success. With the hang gone, the report read "Total Policies: 0" while exiting 0. The cause was mine: `opa eval --data <dir>` parses every .yaml and .json in the tree as a data document, and the GitHub issue-template forms I added to gopal collide: .github/ISSUE_TEMPLATE/new_framework.yml: merge error OPA exited 2 having evaluated nothing, on all eleven policy groups, and the empty result set was rendered as a clean report rather than an error. gopal's own CI passes these ignores to opa check and opa test; the three eval call sites here had none. They now share opa_ignore_flags() so a fourth site cannot silently omit them, and a test asserts that. The report now shows 4 policies rather than 0. That 4 is a pre-existing interface gap, not a remnant of this fix: only 4 of 98 gopal policies define the `report_output` rule AICertify queries, and that was 4 of 87 before the submodule bump too. Most policies expose `compliance_report` instead. Worth its own issue; unblocking it would take the demo from 4 verdicts to 29. Also: the demo now ends by naming the two commands that answer "what would make these pass?", because the bundled contract declares no evidence and a column of denials with no explanation reads like a broken tool. 25 new tests, 88 total. The ignore guard is mutation-verified: removing the flags from one of the three call sites turns it red.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #74.
aicertify demodid not finish. I killed a run at 1156s still sitting onStarting evaluation against eu_ai_act. It now completes in about two minutes and writes a report with actual verdicts.Total Policies: 0Total Policies: 4, Red: 4--with-llm-metricsThree defects, each hiding the next
1. It blocked indefinitely on an LLM call.
py-spyon the live process showed both threads idle inselect(), the worker insidedeepeval/metrics/toxicity/toxicity.py. DeepEval'sToxicityMetricis LLM-judged, someasure()is a network call — with no bound.ContentSafetyEvaluatornow runs each interaction under a wall-clock bound (llm_timeout_seconds, default 60). A timeout fails closed — worst-case toxicity,passed=False— matching the existing unavailable path. A content-safety check that did not complete has established nothing, and reporting it clean would be the same fail-open this library has closed three times in the policies.The worker is a daemon thread, not a
ThreadPoolExecutor, and that distinction is load-bearing: executor threads are non-daemon and the interpreter joins them at exit, so a call still blocked in the provider would keep the process alive after we stopped waiting for it, defeating the bound. Measured — abandoning a 300s call adds no exit delay.2. It made billable calls uninvited. The LLM-judged evaluators activate off the presence of
OPENAI_API_KEY. So the demo advertised as needing "no API keys" silently became a paid, minutes-long run for anyone with a key exported — which is most developers.It now hides that variable for its own run unless
--with-llm-metricsis passed, restores it in afinally, and says which mode it is in. This uses the skip-gracefully path the runner's own docstring already described.3. Then it produced an empty report and called it success. With the hang gone, the report read
Total Policies: 0while exiting 0 — arguably worse than hanging, because it looks like it worked.The cause was mine.
opa eval --data <dir>parses every.yamland.jsonin the tree as a data document, and the GitHub issue-template forms I added to gopal in an earlier session collide:OPA exited 2 having evaluated nothing, on all eleven policy groups, and the empty result set was rendered as a clean report rather than an error. gopal's own CI passes these ignores to
opa checkandopa test; all threeopa evalcall sites here had none. They now shareopa_ignore_flags()so a fourth site cannot silently omit them, and a test asserts exactly that.About that 4
The report shows 4 policies, not 29. That is a pre-existing interface gap, not a remnant of this fix. Only 4 of 98 gopal policies define the
report_outputrule AICertify queries — and it was 4 of 87 before the submodule bump, so the bump did not cause it:Most policies expose
compliance_reportinstead. Worth its own issue: closing it would take the demo from 4 verdicts to 29, and is probably the single largest remaining gap between what gopal encodes and what AICertify can report.Also
The demo now ends by naming the two commands that answer "what would make these pass?":
A column of unexplained denials reads like a broken tool. This connects the demo to the discovery commands from #73.
The README's timing note is corrected to ~2 minutes with an explanation, replacing the "~10 seconds" it claimed before #73.
Tests
25 new, 88 total. The ignore guard is mutation-verified: removing the flags from one of the three call sites turns it red. The daemon-thread property is asserted directly, since a
ThreadPoolExecutorwould pass every other test in that file while silently reintroducing the hang at exit.