[GSoC 2026] chatbot: report IntelOwl's own verdict on a job - #3898
Merged
Conversation
mlodic
approved these changes
Aug 3, 2026
Member
|
if there's nothing to add, I'll merge this |
Contributor
Author
Nothing to add, thanks. |
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.
Description
summarize_jobnow returns IntelOwl's own reconciled verdict on the job's observable alongsidethe metadata it already reported, plus the analyzer evidence behind it. This closes the third of the
bugs reported on Discord in July: "Summarize job" and "Evaluate job" returned identical output
because no chatbot tool ever read the findings.
The verdict is not a chatbot-side scale. The headline is obtained by running the platform's own
EvaluationEngineModule(job).run()and mapping it through the sharedclassify()helper, so thechatbot says exactly the word the job-page badge shows. The LLM is never involved in producing it.
How it works
api_app/chatbot_manager/evaluation.py— a pure functionevaluate_job(job)returning a
JobEvaluationdataclass. It performs zeroJobqueries: the caller passes a jobalready scoped through
visible_for_user, so the tool keeps the single tenancy boundary it had.pipeline saves a transient un-reconciled merge, so
job.data_modelcan be wrong for a window.Recomputing removes that race by construction and guarantees the chatbot cannot diverge from the badge.
supporting/contradicting/silent, so "we don't know" is always attributable to named analyzers instead of an opaque shrug.Attribution goes through
data_model_object_idrather than the report's GenericForeignKey, whichwould be a per-report N+1 — pinned by a query-count guard.
produced an evaluation both return
no evaluationwith an explicit reason, never a fabricated verdict.summarize_job, so no new tool-routing decision isintroduced (and no repeat of [GSoC 2026] Chatbot: warm KV-cache tool-selection flip picks the wrong tool ('show my recent jobs' → list_investigations) #3843). The two job chips collapse into one, "Summarize & evaluate",
which keeps sending the already-validated
"Summarize job #{id}"wording.deliberate duplication: a live smoke against
qwen2.5:3bshowed the model reproduces the prosefields verbatim but paraphrases the structured object away — dropping the reliability and
reporting contradicting analyzers as silent. A test pins the echo so it cannot silently regress.
Reliability is truncated, not rounded
The engine averages reliability into a float and stores it through an integer column, and Django's
IntegerField.get_prep_valuetruncates. Avg(5, 6) = 5.5 is persisted as 5 (suspicious);rounding would have made the chatbot say
maliciouswhile the badge saidsuspicious. The readertruncates to match, and a test pins it by replaying
merge()and reading the value back from the DB.Why the prompt word cap moves from 500 to 600
test_prompt_under_token_limitcounts whitespace-separated words, not tokens.developwasalready at 492/500 before this PR: the guard had shrunk to 8 words of headroom and was rejecting
further rules rather than protecting the context window. At ~1.3-1.4 tokens per word here, 600 words
is roughly 850 tokens — about a tenth of Ollama's 8192 window, alongside the tool schemas and the
history. The prompt ships at 552. The docstring now states what is actually being measured.
Multi-tenancy
Job.get_user_events_data_model()resolves user events with the job owner's visibility, whilethe requester may merely be an org-mate or a viewer of a CLEAR/GREEN job. The reader therefore
exposes only the boolean
analyst_override— never the event's author, reason or tags. Nothing inthe verdict exposes more than
get_job_detailsalready does (it returns each report's full body).Tests
tests/api_app/chatbot_manager/test_evaluation.py— reader unit tests: partitioning, GENERIC,analyst override, boundary bucket, evidence caps, equality with the engine, and purity (no writes).
tests/api_app/chatbot_manager/tools/test_summarize_job.py— envelope, no-evaluation, GENERIC, andthe tenancy case (an invisible job is indistinguishable from a missing one — no verdict leaks).
tests/api_app/chatbot_manager/test_query_counts.py— N+1 guard in the DataModel dimension.tests/api_app/chatbot_manager/test_agent.py— pins both former chip intents to the one tool.QuickActions.test.jsxupdated; the collapse is pinned by a negative assertion.200 chatbot tests green,
makemigrations --checkclean,ruff+prettierclean, frontendchat suite 65/65 green.
The narration of the structured
verdictbyqwen2.5:3bwas measured end to end against a seededjob (report and raw logs kept out of the repo): "summarize job #N" and "evaluate the results of job
#N" relay the verdict, the reliability, the named analyzers and the silent count correctly. On the
terse "is job #N malicious?" the model relays the verdict, the reliability and all three counts
correctly but does not enumerate the analyzer names — it never substitutes placeholder names. That
is the prompt's own "one paragraph unless the user asks for a list" rule winning over enumeration.
Refs #3895
(#3893 is merged — this branch is based on
developafter that merge.)