Skip to content

test: normalize Ollama tool-call transcripts#377

Merged
gnanirahulnutakki merged 1 commit into
devfrom
gnanirahulnutakki/issue-374-ollama-transcripts
Jul 23, 2026
Merged

test: normalize Ollama tool-call transcripts#377
gnanirahulnutakki merged 1 commit into
devfrom
gnanirahulnutakki/issue-374-ollama-transcripts

Conversation

@gnanirahulnutakki

@gnanirahulnutakki gnanirahulnutakki commented Jul 23, 2026

Copy link
Copy Markdown
Member

Relevant issues

Closes #374

Type

  • feat — new capability or surface
  • fix — bug fix
  • docs — article, README, or doc change
  • refactor — internal change, no behaviour change
  • test — test-only change
  • ci — CI / workflow / tooling
  • chore — housekeeping
  • dev → main graduation — promoting reviewed work to release branch

Changes

  • Preserve each original Ollama assistant message once, followed by the complete ordered tool result batch using tool_name, across all five scoped harnesses.
  • Keep every per-call argument and governance evaluation or local execution distinct and exactly once.
  • Fail closed on provider rejection, empty cloud responses, and non-PERMIT or non-DENY proxy outcomes instead of recording successful completion.
  • Keep expected DENY outcomes separate from provider/proxy errors, and fail closed when legacy report entries are ambiguous, malformed, non-DENY, or non-success status.
  • Correct overhead turn accounting and review-prompt detection, and strengthen live-model response and security-header assertions.
  • Add credential-free regression coverage for multi-call and single-call turns, both legacy malformed patterns, transcript atomicity, exact next-request ordering, and failure propagation.
  • Apply the repository-required Ruff formatter to the two scoped harness files that had pre-existing format drift.

The implementation follows the official Ollama tool-calling contract: https://docs.ollama.com/capabilities/tool-calling

README and public documentation were reviewed. No public API or operator workflow changed, so no documentation update is required.

AI assistance was used to implement and review this change. The human author retains responsibility and verified the tests and resulting diff.

Testing

  • Red proof on untouched dev 8589d4b: two expected failures reproduced the tool-results-before-assistant and synthetic-assistant-per-call defects.
  • python/.venv/bin/python -m pytest -q python/tests/test_ollama_transcript_regressions.py — 20 passed.
  • python/.venv/bin/python -m pytest -q python/tests/test_ollama_integration.py python/tests/test_ardur_comprehensive_integration.py — 1 passed, 17 credential-gated skips.
  • Ruff 0.13.0 lint and format checks — passed.
  • pre-commit 4.3.0 on all changed files — passed, including private-key and hardcoded-secret detection.
  • ./scripts/check-local.sh --quick — passed.
  • git diff --check — passed.
  • CodeRabbit CLI initial review — seven valid findings fixed; a later committed-diff retry was rate-limited and is not claimed as clean.
  • Hosted CodeRabbit reviewed every amendment at the exact head. Both valid major findings were fixed and explicitly confirmed; the only review thread is resolved, and the final hosted status passed with no unresolved finding.

No live provider, credential, Docker registry, or Kubernetes cluster was used in these tests.

Summary by CodeRabbit

  • Bug Fixes

    • Improved multi-tool transcript assembly and ordering, preserving assistant turns and ensuring tool results are emitted correctly.
    • Refined governance verdict reporting: errors are surfaced first, unexpected proxy decisions are treated as failures, and process exit now fails when any proxy bypasses or scenario errors occur.
    • Strengthened failure handling for empty responses and invalid evaluations; improved “test complete vs failed” outcomes.
  • Tests

    • Added transcript regression coverage across multiple runners to verify atomicity, multi-tool turn structure, and correct stop/propagation behavior on failures.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR normalizes multi-tool Ollama transcripts across test harnesses, preserves original assistant messages with ordered tool results, adds credential-free regression coverage, and improves model, proxy, and scenario failure reporting.

Changes

Transcript normalization and runner validation

Layer / File(s) Summary
Runner tool-call transcript flow
python/tests/run_adversarial_suite.py, python/tests/run_cloud_model_test.py, python/tests/test_ardur_comprehensive_integration.py, python/tests/test_ollama_integration.py
Accumulates ordered tool results and appends the original assistant message once per response; adversarial and cloud evaluation paths validate decisions and errors.
Overhead harness transcript flow
python/tests/test_ardur_overhead_ab.py
Aligns governed and non-governed runs with the normalized assistant/tool message structure and adds message-content and review-prompt helpers.
Transcript regression fixtures and coverage
python/tests/test_ollama_transcript_regressions.py
Adds fake clients and tests for message identity, tool ordering, evaluation calls, transcript atomicity, denial handling, and failure propagation across runners.
Failure reporting and integration validation
python/tests/run_adversarial_suite.py, python/tests/run_cloud_model_test.py, python/tests/run_all_models.py, python/tests/test_ollama_integration.py
Reports scenario and model errors explicitly, records completion status, supports current and legacy denial schemas, and preserves integration assertions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Ollama
  participant TestRunner
  participant GovernanceProxy
  participant ToolHandler
  Ollama->>TestRunner: assistant message with multiple tool calls
  TestRunner->>GovernanceProxy: evaluate each tool call
  GovernanceProxy-->>TestRunner: PERMIT or DENY decision
  TestRunner->>ToolHandler: execute permitted tool calls
  ToolHandler-->>TestRunner: ordered serialized results
  TestRunner->>Ollama: original assistant message plus tool results
Loading

Possibly related issues

  • ArdurAI/ardur issue 367 — Addresses the same multi-tool assistant transcript preservation objective across adjacent Ollama harnesses.

Possibly related PRs

  • ArdurAI/ardur#373 — Refactors multi-tool transcript handling to preserve the assistant turn and append ordered tool results with corresponding evaluation behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes align with #374 by preserving assistant turns once, ordering tool results, adding regression coverage, and propagating provider failures.
Out of Scope Changes check ✅ Passed The modified tests and summary compatibility changes are all relevant to transcript normalization and failure reporting, with no clear unrelated additions.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main test-only change to normalize Ollama tool-call transcripts.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gnanirahulnutakki/issue-374-ollama-transcripts

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/tests/run_cloud_model_test.py (1)

600-637: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

DENY decisions are conflated with real failures, causing false-negative run failures.

report["errors"] (populated at Lines 494-500) mixes three unrelated things: model/provider exceptions, "no tool calls and no content", and legitimate DENY governance decisions. The new run_completed = not report["errors"] (Line 600) and the raise RuntimeError(...) when report["errors"] is non-empty (Lines 628-633) now treat any proxy DENY — a correct, expected governance outcome given forbidden_tools=["delete_file", "execute_shell"] — as a fatal run failure alongside real provider/proxy failures.

This directly conflicts with the pattern used in run_adversarial_suite.py, which correctly keeps denials separate from errors (only entries where the decision is outside {PERMIT, DENY} count as errors). No regression test in this PR exercises the DENY path through main(), so this gap isn't caught.

Suggest separating report["denials"] from report["errors"], and gating run_completed/the raise only on genuine failures.

🔧 Proposed fix
-                if status != 200 or decision.get("decision") != "PERMIT":
+                if status != 200:
                     print(
                         f"  DENIED: {tool_name}({list(tool_args.keys())}) → {decision.get('decision', 'UNKNOWN')}"
                     )
                     report["errors"].append(
                         {
                             "tool": tool_name,
                             "args_keys": list(tool_args.keys()),
                             "decision": decision,
                         }
                     )
                     result = {"status": "denied", "reason": str(decision)}
+                elif decision.get("decision") != "PERMIT":
+                    print(
+                        f"  DENIED: {tool_name}({list(tool_args.keys())}) → {decision.get('decision', 'UNKNOWN')}"
+                    )
+                    report.setdefault("denials", []).append(
+                        {
+                            "tool": tool_name,
+                            "args_keys": list(tool_args.keys()),
+                            "decision": decision,
+                        }
+                    )
+                    result = {"status": "denied", "reason": str(decision)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/tests/run_cloud_model_test.py` around lines 600 - 637, Separate
legitimate DENY governance decisions from genuine failures in the
report-building and completion logic around main(). Track DENY results in
report["denials"], while retaining provider/proxy exceptions, missing output,
and decisions outside PERMIT/DENY in report["errors"]; make run_completed and
the RuntimeError raise depend only on report["errors"], while preserving denial
reporting.
🧹 Nitpick comments (1)
python/tests/run_adversarial_suite.py (1)

1040-1110: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the shared "ordered tool-result + single assistant append" logic into a common test helper. The exact same three-step pattern — build a list of role: "tool" messages keyed by tool_name, append the original assistant message once, then extend with the ordered results — is duplicated six times across five files to satisfy the same Issue #374 contract. A single shared helper would guarantee future fixes to this transcript-normalization logic apply uniformly to all five harnesses instead of relying on manual copy-paste consistency.

  • python/tests/run_adversarial_suite.py#L1040-L1110: replace the inline tool_results accumulation/append with a call to a shared helper (e.g., build_tool_result_messages(tool_calls_results) + append_turn(messages, resp.message, tool_results)).
  • python/tests/run_cloud_model_test.py#L474-L550: use the same shared helper for its tool_results accumulation and final messages.append/extend.
  • python/tests/test_ardur_comprehensive_integration.py#L919-L971: use the same shared helper.
  • python/tests/test_ollama_integration.py#L555-L587: use the same shared helper (it can still add its extra call_index field via a parameter or by extending the base result dict).
  • python/tests/test_ardur_overhead_ab.py#L263-L307: use the same shared helper in run_without_ardur.
  • python/tests/test_ardur_overhead_ab.py#L368-L426: use the same shared helper in run_with_ardur.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/tests/run_adversarial_suite.py` around lines 1040 - 1110, Extract the
duplicated ordered tool-result and single-assistant-append sequence into one
shared test helper, preserving role/tool_name fields and ordered results;
support Ollama’s extra call_index field. Apply the helper in
python/tests/run_adversarial_suite.py:1040-1110,
run_cloud_model_test.py:474-550,
test_ardur_comprehensive_integration.py:919-971,
test_ollama_integration.py:555-587, and both test_ardur_overhead_ab.py:263-307
and :368-426, including run_without_ardur and run_with_ardur.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@python/tests/run_cloud_model_test.py`:
- Around line 600-637: Separate legitimate DENY governance decisions from
genuine failures in the report-building and completion logic around main().
Track DENY results in report["denials"], while retaining provider/proxy
exceptions, missing output, and decisions outside PERMIT/DENY in
report["errors"]; make run_completed and the RuntimeError raise depend only on
report["errors"], while preserving denial reporting.

---

Nitpick comments:
In `@python/tests/run_adversarial_suite.py`:
- Around line 1040-1110: Extract the duplicated ordered tool-result and
single-assistant-append sequence into one shared test helper, preserving
role/tool_name fields and ordered results; support Ollama’s extra call_index
field. Apply the helper in python/tests/run_adversarial_suite.py:1040-1110,
run_cloud_model_test.py:474-550,
test_ardur_comprehensive_integration.py:919-971,
test_ollama_integration.py:555-587, and both test_ardur_overhead_ab.py:263-307
and :368-426, including run_without_ardur and run_with_ardur.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5834fd40-15f8-481c-9a04-06c4cf22c24b

📥 Commits

Reviewing files that changed from the base of the PR and between 8589d4b and e93563c.

📒 Files selected for processing (6)
  • python/tests/run_adversarial_suite.py
  • python/tests/run_cloud_model_test.py
  • python/tests/test_ardur_comprehensive_integration.py
  • python/tests/test_ardur_overhead_ab.py
  • python/tests/test_ollama_integration.py
  • python/tests/test_ollama_transcript_regressions.py

@gnanirahulnutakki
gnanirahulnutakki force-pushed the gnanirahulnutakki/issue-374-ollama-transcripts branch from e93563c to 7e7130a Compare July 23, 2026 06:28
@gnanirahulnutakki

gnanirahulnutakki commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Hosted-review resolution at amended signed/DCO head 7e7130a51577ead40e913518966e1824803b59ed:

  • Fixed the valid major finding. The cloud harness now classifies outcomes explicitly: HTTP non-200 or a decision outside PERMIT|DENY is an error, DENY is an expected governance result, and PERMIT executes normally.
  • Genuine evaluation failures now stop the turn before assistant/tool transcript mutation or any follow-up model call.
  • Updated the aggregate reporter for the new denials collection while retaining compatibility with legacy saved reports that mixed denials into errors.
  • Added credential-free coverage for expected-DENY success, HTTP failure, unknown-decision failure, transcript atomicity, no follow-up call after failure, and both report schemas. Targeted result: 20 passed; live-provider integration: 17 credential-gated skips.
  • Ruff 0.13.0 lint/format, pre-commit 4.3.0 (including private-key and secret detection), the quick repository check, and git diff --check are green.

The docstring-coverage warning was reviewed but not applied: these are private fixtures and descriptively named tests, and this repository has no docstring-coverage gate. Adding repetitive test docstrings would be unrelated churn.

The shared-helper nitpick was also reviewed but not applied in this focused PR. Only the two-line final append invariant is common; the independently executable harnesses differ in decision, execution, reporting, and payload behavior. A shared helper would add cross-script import coupling without centralizing the risky logic, while the credential-free regression suite now enforces the invariant across all implementations.

A local CodeRabbit retry after the correction was attempted but rate-limited for 30 minutes, so it is not claimed as a clean local review. Fresh exact-head GitHub Actions and hosted CodeRabbit review are now running; merge remains held until both complete.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/tests/run_all_models.py`:
- Around line 84-89: Update the legacy fallback in the report classification
logic to count only entries whose decision is explicitly “DENY” as denials,
classify entries with “UNKNOWN” or provider-failure decisions as exceptions, and
count entries lacking both a recognized decision and an error as failures so
they cannot produce a clean result. Add regression coverage for DENY, PERMIT,
UNKNOWN/provider failures, and empty entries such as errors=[{}].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e75f471f-a0c7-4631-8c1e-5311e171ff13

📥 Commits

Reviewing files that changed from the base of the PR and between e93563c and 7e7130a.

📒 Files selected for processing (7)
  • python/tests/run_adversarial_suite.py
  • python/tests/run_all_models.py
  • python/tests/run_cloud_model_test.py
  • python/tests/test_ardur_comprehensive_integration.py
  • python/tests/test_ardur_overhead_ab.py
  • python/tests/test_ollama_integration.py
  • python/tests/test_ollama_transcript_regressions.py

Comment thread python/tests/run_all_models.py Outdated
Checkpoint: architect/sessions/release-v0.2.0-qualification/2026-07-22-journal.md
Signed-off-by: Gnani Rahul Nutakki <gnani.nutakki@gmail.com>
@gnanirahulnutakki
gnanirahulnutakki force-pushed the gnanirahulnutakki/issue-374-ollama-transcripts branch from 7e7130a to 7066d6b Compare July 23, 2026 06:38
@gnanirahulnutakki
gnanirahulnutakki merged commit dfe4905 into dev Jul 23, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: normalize Ollama tool-call transcripts across cloud harnesses

1 participant