Skip to content

feat(sdk): middleware adapters for OpenAI / Anthropic / LangChain / CrewAI#32

Merged
Gradata merged 10 commits intomainfrom
feat/sdk-middleware-adapter
Apr 15, 2026
Merged

feat(sdk): middleware adapters for OpenAI / Anthropic / LangChain / CrewAI#32
Gradata merged 10 commits intomainfrom
feat/sdk-middleware-adapter

Conversation

@Gradata
Copy link
Copy Markdown
Owner

@Gradata Gradata commented Apr 13, 2026

Summary

Ships gradata.middleware — runtime rule injection + enforcement for direct-SDK agents outside Claude Code.

Until now, Gradata's hooks only fired inside Claude Code; agents built directly on the Anthropic, OpenAI, LangChain, or CrewAI SDKs had no runtime enforcement. This PR adds that layer as a thin, additive subpackage.

  • AnthropicMiddleware / wrap_anthropic(Anthropic()) — injects <brain-rules> into messages.create(system=...), post-checks response text.
  • OpenAIMiddleware / wrap_openai(OpenAI()) — same shape for chat.completions.create(messages=...) (prepends/extends the leading system message).
  • LangChainCallbackBaseCallbackHandler that injects at on_llm_start / on_chat_model_start and checks at on_llm_end.
  • CrewAIGuard — callable for CrewAI's guardrails=[...] list.

Design

  • One source of truth: RuleSource reads from the same <brain_path>/lessons.md Claude Code's SessionStart hook parses (via parse_lessons).
  • Identical injection format: reuses the existing <brain-rules> XML block shape (RULE > PATTERN, confidence-ranked, max 10).
  • RULE-tier enforcement only: regex patterns derived from rule_to_hook.classify_rule() — no new classifier, no duplicate logic. Current deterministic template: em-dash detection; others (file size, secret scan, etc.) stay as hooks.
  • strict=False (default): violations log; strict=True: raises gradata.middleware.RuleViolation so callers can retry.
  • GRADATA_BYPASS=1 env var: kill switch disables all injection and enforcement.
  • Optional deps: importing an adapter without its SDK raises a clear ImportError with an install hint.

Constraint check

  • Zero changes to rule_engine / rule_to_hook — middleware is purely additive.
  • No real API calls in tests; SDKs are stubbed via sys.modules monkey-patching.
  • Matches existing tests/test_rule_to_hook.py style.
  • docs/middleware.md has one example per adapter + advanced RuleSource usage.

Test plan

  • pytest tests/test_middleware*.py -v38/38 pass (10 core, 8 anthropic, 7 openai, 7 langchain, 6 crewai).
  • Full suite pytest -x -q2108 passed, 23 skipped (no regressions).
  • ruff check src/gradata/middleware/ tests/test_middleware*.py — clean.
  • pyright src/gradata/0 errors, 2 reportMissingImports warnings for langchain (expected; optional dep).

Generated with Gradata

Co-Authored-By: Gradata noreply@gradata.ai

Oliver Le added 6 commits April 13, 2026 13:42
Adds the shared primitives for runtime middleware adapters:
- RuleSource reads lessons.md from a brain directory (same path Claude Code
  hooks use) and selects top-N RULE/PATTERN lessons.
- build_brain_rules_block() renders the same <brain-rules> XML the
  SessionStart hook emits, for consistency across environments.
- check_output() scans text against RULE-tier regex patterns derived from
  rule_to_hook.classify_rule (reuses existing classifier).
- RuleViolation exception + GRADATA_BYPASS=1 kill switch env var.

Zero changes to existing rule_engine / rule_to_hook — middleware is additive.
Wraps anthropic.Anthropic() so every client.messages.create() call gets
Gradata's <brain-rules> appended to the system prompt and its response
text post-checked against RULE-tier patterns.

- strict=False (default) logs violations; strict=True raises RuleViolation
- Handles both string and content-block-list system prompts
- Lazy ImportError with 'pip install anthropic' hint when dep missing
- All other client attributes delegate to the underlying client
Wraps openai.OpenAI() so every client.chat.completions.create() call
gets a <brain-rules> system message prepended (or merged into an existing
system message) and its response text post-checked.

- Same strict / bypass semantics as AnthropicMiddleware
- Lazy ImportError with 'pip install openai' hint when dep missing
Implements LangChain's BaseCallbackHandler:
- on_llm_start: prepends <brain-rules> to the first prompt
- on_chat_model_start: extends/inserts a SystemMessage with the block
- on_llm_end: post-checks the LLMResult text against RULE-tier patterns

Gracefully handles missing langchain-core — raises ImportError with
'pip install langchain-core' hint at instantiation time via __new__.
Callable that CrewAI agents register in their guardrails=[...] list.
Returns (True, output) for clean text; when strict=True (default),
returns (False, 'Gradata rule violation(s): ...') so CrewAI can retry.

Text-coercion handles CrewAI output objects (raw/output/text/content
attrs) as well as plain strings and dicts. No hard crewai dependency.
Covers common behavior (rule source, strict mode, GRADATA_BYPASS kill
switch, optional-deps ImportError contract) and per-adapter usage for
Anthropic, OpenAI, LangChain, CrewAI, plus the advanced custom
RuleSource pattern.
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Warning

Rate limit exceeded

@Gradata has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 34 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 34 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a29f85dd-25db-4983-a2fa-fae2935d7fc4

📥 Commits

Reviewing files that changed from the base of the PR and between e57267d and 25afe9d.

📒 Files selected for processing (8)
  • src/gradata/middleware/__init__.py
  • src/gradata/middleware/_core.py
  • src/gradata/middleware/anthropic_adapter.py
  • src/gradata/middleware/crewai_adapter.py
  • src/gradata/middleware/langchain_adapter.py
  • src/gradata/middleware/openai_adapter.py
  • tests/test_middleware_core.py
  • tests/test_middleware_crewai.py
📝 Walkthrough

Walkthrough

Adds a runtime middleware subsystem: core utilities for sourcing/injecting <brain-rules> and enforcing them, plus adapters for Anthropic, OpenAI, LangChain, and CrewAI and accompanying docs and tests. All adapters support a global bypass and surface informative ImportError for missing optional deps.

Changes

Cohort / File(s) Summary
Documentation
docs/middleware.md
New docs describing middleware adapters, shared rule sourcing, injection/enforcement behaviors, strict vs non-strict modes, bypass env var, and usage examples.
Core middleware & public entrypoint
src/gradata/middleware/_core.py, src/gradata/middleware/__init__.py
New core: RuleSource, RuleViolation, is_bypassed(), build_brain_rules_block(), inject_into_system(), check_output(); package init re-exports core symbols and provides lazy-loading for adapters.
Anthropic adapter
src/gradata/middleware/anthropic_adapter.py
Client wrapper that injects <brain-rules> into Anthropic system payloads, extracts response text, and enforces rules (configurable strict). Includes wrap_anthropic.
OpenAI adapter
src/gradata/middleware/openai_adapter.py
Client wrapper for OpenAI chat/completions: prepends or extends system message with rules block, extracts text from choices, enforces rules, and exposes wrap_openai.
LangChain adapter
src/gradata/middleware/langchain_adapter.py
LangChainCallback that injects rules on on_llm_start/on_chat_model_start and enforces on on_llm_end; import-safe with informative ImportError.
CrewAI guard
src/gradata/middleware/crewai_adapter.py
CrewAIGuard callable that validates agent output and returns (ok, output) tuple; configurable strict behavior and bypass-aware.
Tests
tests/test_middleware_core.py, tests/test_middleware_anthropic.py, tests/test_middleware_openai.py, tests/test_middleware_langchain.py, tests/test_middleware_crewai.py
New pytest suites covering rule sourcing, block formatting, injection semantics per adapter, enforcement modes, bypass behavior, and missing-dependency import errors.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Agent/Client
    participant MW as Middleware Adapter
    participant RS as RuleSource
    participant LLM as LLM Service

    Client->>MW: request(create/chat/llm_start, prompts/messages)
    MW->>RS: select() / fetch eligible rules
    RS-->>MW: ranked RULE/PATTERN list
    MW->>MW: build_brain_rules_block()
    MW->>MW: inject_into_system(modified messages)
    MW->>LLM: send(modified messages)
    LLM-->>MW: response
    MW->>MW: extract_text(response)
    MW->>RS: check_output(text, strict?)
    RS-->>MW: [] or [RuleViolation]
    alt violations & strict=True
        MW-->>Client: raise/return failure (RuleViolation)
    else clean or non-strict
        MW-->>Client: return original response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #23: introduces/determines classify_rule and rule-to-hook mappings relied upon by RuleSource.rule_tier_blockers() and enforcement logic.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding middleware adapters for four major SDK platforms (OpenAI, Anthropic, LangChain, CrewAI). This directly summarizes the PR's primary objective.
Description check ✅ Passed The description is comprehensive and closely related to the changeset, detailing the architecture, design decisions, constraint adherence, and test results. It thoroughly explains what the PR delivers and how it aligns with existing systems.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sdk-middleware-adapter

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gradata/middleware/_core.py`:
- Around line 121-137: The code in _load_from_dicts allows confidence values
outside [0.0,1.0]; clamp or validate the parsed conf before creating the
_ScoredLesson (e.g. conf = max(0.0, min(conf, 1.0)) or raise/log and skip), then
use that sanitized value when constructing _ScoredLesson; apply the identical
validation/clamping to the other lesson intake path that processes
_static_lessons (the second loader mentioned around lines 194-200) so no
out-of-range confidence is stored.

In `@src/gradata/middleware/anthropic_adapter.py`:
- Around line 50-67: The _extract_text function silently mishandles a
string-shaped response.content by iterating its characters; fix by
special-casing string content at the start of the function (when you obtain
content from response or dict) and return the string directly (or normalize it
to a single text block) before the list iteration. Update the logic in
_extract_text so it checks isinstance(content, str) and handles/returns that
string instead of treating it like an iterable of blocks.

In `@src/gradata/middleware/crewai_adapter.py`:
- Around line 47-55: The constructor for CrewAIGuard currently sets strict to
True by default which inverts the intended behavior; change the __init__
signature to default strict to False (def __init__(..., *, strict: bool = False,
...)) and keep assigning self._strict = strict so that the adapter defaults to
non-strict/pass-through behavior as documented.

In `@src/gradata/middleware/langchain_adapter.py`:
- Around line 66-76: The current on_llm_start mutates only prompts[0]; change it
to prepend build_brain_rules_block(self._source) to every entry in the batch by
iterating over all items (e.g., for i in range(len(prompts)): prompts[i] =
f"{block}\n\n{prompts[i]}") and keep the same early-return when block or prompts
is falsy; apply the same fix to the chat handler (the method handling messages
around lines 78–100) by iterating over every message in the messages list and
prepending the block to the message content, handling both dict messages with a
'content' key and message objects with a .content attribute, updating them
in-place.
- Around line 90-100: The current code naively stringifies BaseMessage.content
when msg_type == "system" (using f"{content}\n\n{block}"), which will corrupt
multimodal messages whose content is a list; modify the logic in
langchain_adapter.py to inspect the type of first.content: if it's a str,
concatenate as before; if it's a list (or other sequence of blocks), extend or
append the new block into that list (preserving structure); otherwise, fall back
to wrapping both into a list. Update the handling for first (the BaseMessage)
and for insertion into first_batch/SystemMessage so SystemMessage(content=...)
receives a string or list consistent with the message content type.

In `@src/gradata/middleware/openai_adapter.py`:
- Around line 74-79: The current logic stringifies structured system message
content (variable existing) before calling inject_into_system, which corrupts
non-string payloads; change the handling in the block that inspects out[0] so
that if existing is a str you call inject_into_system(existing, block) and
replace out[0]["content"] with that result, but if existing is not a str (e.g.,
list/dict multimodal content) do NOT call str() or mutate it—instead create a
new system message (role="system") containing just the injected text and prepend
or insert it into out (so you preserve the original structured existing
content); update references in this code path around inject_into_system, out,
and existing accordingly.

In `@tests/test_middleware_core.py`:
- Around line 50-56: The test test_build_brain_rules_block_respects_max_rules
creates synthetic lessons with confidence values that exceed 1.0; update the
lessons generation in that test so confidence stays within the valid [0.0,1.0]
range (e.g., cap each confidence with min(1.0, computed_value) or generate using
a smaller increment) so RuleSource(lessons=lessons, max_rules=5) receives only
valid confidences and the test continues to exercise max_rules.

In `@tests/test_middleware_langchain.py`:
- Around line 114-129: Combine the two tests into one parametrized test that
covers both strict=True and strict=False cases: create a single test function
(e.g., test_on_llm_end_strict_parametrized) using pytest.mark.parametrize over
strict and an expected behavior flag, instantiate LangChainCallback with
brain_path=brain_with_em_dash_rule and the parametrized strict value, call
cb.on_llm_end(_FakeLLMResult("bad \u2014 output")), and assert that
RuleViolation is raised when expected (use pytest.raises for the strict case)
and not raised for the non-strict case; reference LangChainCallback,
_FakeLLMResult and RuleViolation to locate the original test logic.

In `@tests/test_middleware_openai.py`:
- Around line 102-125: Combine the two boundary tests into a single parametrized
test to remove duplication: create one test (e.g., test_wrap_openai_strictness)
decorated with pytest.mark.parametrize over strict values (True, False) and an
expected_outcome flag (or expected_exception for True), instantiate the same
_FakeClient(reply="bad \u2014 output") and call wrap_openai with the
parametrized strict argument (using brain_with_em_dash_rule), then assert that
when strict is True a RuleViolation is raised (use pytest.raises for
RuleViolation) and when strict is False the call returns a non-None response;
update/remove the original test_wrap_openai_strict_raises_on_violation and
test_wrap_openai_non_strict_does_not_raise accordingly.
- Around line 120-125: Replace the weak truthiness check on resp with concrete
assertions that validate the adapter's returned structure and content: after
calling wrapped.chat.completions.create, assert that resp contains expected
top-level fields (e.g., "id" and "model" == "gpt-4o-mini"), that resp.choices is
a non-empty list, and that resp.choices[0] has a message with role "assistant"
and non-empty content; use the same wrapped.chat.completions.create call and
variable resp to locate the test to modify.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b4e0e919-c159-40b7-b016-95df586f0e71

📥 Commits

Reviewing files that changed from the base of the PR and between ce1ad3c and d362e74.

📒 Files selected for processing (12)
  • docs/middleware.md
  • src/gradata/middleware/__init__.py
  • src/gradata/middleware/_core.py
  • src/gradata/middleware/anthropic_adapter.py
  • src/gradata/middleware/crewai_adapter.py
  • src/gradata/middleware/langchain_adapter.py
  • src/gradata/middleware/openai_adapter.py
  • tests/test_middleware_anthropic.py
  • tests/test_middleware_core.py
  • tests/test_middleware_crewai.py
  • tests/test_middleware_langchain.py
  • tests/test_middleware_openai.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_middleware_crewai.py
  • tests/test_middleware_anthropic.py
  • tests/test_middleware_core.py
  • tests/test_middleware_openai.py
  • tests/test_middleware_langchain.py
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/middleware/__init__.py
  • src/gradata/middleware/openai_adapter.py
  • src/gradata/middleware/langchain_adapter.py
  • src/gradata/middleware/anthropic_adapter.py
  • src/gradata/middleware/_core.py
  • src/gradata/middleware/crewai_adapter.py
🔇 Additional comments (2)
docs/middleware.md (1)

1-108: LGTM — docs are clear and aligned with adapter behavior.

Examples and behavior notes (strict mode, bypass, optional deps) are coherent and actionable.

src/gradata/middleware/anthropic_adapter.py (1)

103-119: Good interception boundary in create().

Injection is scoped to system, and post-checking is cleanly applied without mutating returned response objects.

Comment thread src/gradata/middleware/_core.py
Comment thread src/gradata/middleware/anthropic_adapter.py Outdated
Comment thread src/gradata/middleware/crewai_adapter.py
Comment thread src/gradata/middleware/langchain_adapter.py Outdated
Comment thread src/gradata/middleware/langchain_adapter.py Outdated
Comment thread src/gradata/middleware/openai_adapter.py Outdated
Comment thread tests/test_middleware_core.py
Comment thread tests/test_middleware_langchain.py Outdated
Comment thread tests/test_middleware_openai.py Outdated
Comment thread tests/test_middleware_openai.py Outdated
- _core.py: clamp lesson confidence to [0.0, 1.0] in both intake paths
- anthropic_adapter: handle string-shaped response.content (no char-iteration)
- crewai_adapter: default strict=False to match docstring/pass-through contract
- langchain_adapter: inject rules into every prompt/batch, not just index 0
- langchain_adapter: preserve list/multimodal system content (no stringify)
- openai_adapter: preserve structured system content (prepend fresh sys msg)
- tests: parametrize openai/langchain strict boundary; cap synthetic confidences
- tests: assert concrete response content, add multimodal + batch regressions
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 13, 2026

Deploying gradata-dashboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: 25afe9d
Status: ✅  Deploy successful!
Preview URL: https://71781303.gradata-dashboard.pages.dev
Branch Preview URL: https://feat-sdk-middleware-adapter.gradata-dashboard.pages.dev

View logs

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gradata/middleware/_core.py`:
- Around line 134-148: The loop over self._static_lessons currently calls
float(lesson.get("confidence", 0.0) or 0.0) which will raise for non-numeric
strings; guard the parsing in the block that builds _ScoredLesson so that you
try to coerce confidence only if it's numeric (e.g., check
isinstance/str.isnumeric/try-except ValueError) and on failure use a safe
default (0.0) or skip the lesson; specifically update the code around
_clamp_confidence and the confidence assignment so malformed
lesson["confidence"] values do not let TypeError/ValueError escape and instead
produce a clamped default or omit the entry when constructing the _ScoredLesson
for self._static_lessons.

In `@src/gradata/middleware/crewai_adapter.py`:
- Around line 81-89: _coerce_text currently skips explicit empty strings because
it checks truthiness before returning, causing "" to fall through to
str(output); update the two places where you iterate attrs
("raw","output","text","content") over getattr(output, ...) and over
output.get(...) to return the value whenever isinstance(val, str) (i.e., remove
the truthiness check) so that empty strings are preserved; leave the final
fallback to str(output) only if no string fields are present.

In `@src/gradata/middleware/langchain_adapter.py`:
- Around line 17-20: Update the module docstring in
src/gradata/middleware/langchain_adapter.py to reflect current batch behavior:
replace the sentence claiming injection is "best-effort on the first prompt
only" with a statement that injection now mutates every prompt in a batch
(mentioning on_llm_start() mutates every prompt and on_chat_model_start()
iterates every batch entry), and advise that OpenAIMiddleware still provides
stricter per-prompt control; reference the module-level docstring and the two
methods on_llm_start and on_chat_model_start so maintainers can verify wording
against their implementations.

In `@tests/test_middleware_crewai.py`:
- Around line 30-46: Collapse the two nearly identical tests into a single
parametrized test over strict values for CrewAIGuard using the same
brain_with_em_dash_rule setup: call guard =
CrewAIGuard(brain_path=brain_with_em_dash_rule, strict=strict) and guard(text)
for text = "has em dash \u2014 here", then assert the expected outcomes per
parameter — when strict is True assert ok is False and that result contains
"em-dash" or "em dash" (case-insensitive), and when strict is False assert ok is
True and result == text; keep the test name and parameterization clear to cover
both boundary cases in one test.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 44f21cc5-4296-407d-aa0a-76707d6befca

📥 Commits

Reviewing files that changed from the base of the PR and between ce1ad3c and e57267d.

📒 Files selected for processing (12)
  • docs/middleware.md
  • src/gradata/middleware/__init__.py
  • src/gradata/middleware/_core.py
  • src/gradata/middleware/anthropic_adapter.py
  • src/gradata/middleware/crewai_adapter.py
  • src/gradata/middleware/langchain_adapter.py
  • src/gradata/middleware/openai_adapter.py
  • tests/test_middleware_anthropic.py
  • tests/test_middleware_core.py
  • tests/test_middleware_crewai.py
  • tests/test_middleware_langchain.py
  • tests/test_middleware_openai.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_middleware_openai.py
  • tests/test_middleware_crewai.py
  • tests/test_middleware_core.py
  • tests/test_middleware_anthropic.py
  • tests/test_middleware_langchain.py
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/middleware/crewai_adapter.py
  • src/gradata/middleware/langchain_adapter.py
  • src/gradata/middleware/openai_adapter.py
  • src/gradata/middleware/anthropic_adapter.py
  • src/gradata/middleware/__init__.py
  • src/gradata/middleware/_core.py
🔇 Additional comments (17)
docs/middleware.md (1)

1-108: LGTM!

The documentation is well-structured, covers all four adapters with clear examples, and accurately documents the shared behavior (rule source, strict mode, bypass switch, optional dependencies). The advanced RuleSource customization section is a helpful addition for power users.

tests/test_middleware_anthropic.py (5)

1-6: LGTM!

Proper module docstring and from __future__ import annotations import as required by coding guidelines.


19-59: LGTM!

The stub and fake classes are well-designed to mimic the essential parts of the Anthropic SDK without requiring the real package. The last_kwargs capture pattern enables clean assertions on injection behavior.


82-98: LGTM!

Good specific assertions checking for <brain-rules>, TONE, and em dashes strings rather than just truthiness. The test properly validates injection behavior.


132-148: LGTM!

The non-strict mode test correctly validates that violations are logged but don't raise. The assertion on line 147 properly checks for specific log content ("rule violation") rather than just checking that any log was emitted.


190-215: LGTM!

The import error test is thorough — it removes both the stub and cached adapter module, installs a custom import hook to block anthropic, and verifies the error message contains the install hint. This ensures users get actionable guidance when the optional dependency is missing.

tests/test_middleware_core.py (5)

1-16: LGTM!

Proper imports with from __future__ import annotations as required by coding guidelines.


50-58: LGTM!

The confidence clamping with min(1.0, 0.90 + i / 200) ensures all synthetic values stay within the valid [0.0, 1.0] range, properly exercising max_rules truncation while respecting the confidence contract.


61-82: LGTM!

Good test coverage for enforcement behavior: validating pattern name specificity (em-dash), strict mode raising, and the distinction between RULE and non-RULE tiers.


95-114: LGTM!

The bypass tests thoroughly validate the GRADATA_BYPASS environment variable behavior, including the important edge case that "0" is treated as false (line 98-99). The test on line 113 correctly verifies that strict mode returns an empty list rather than raising when bypassed.


116-135: LGTM!

The filesystem-based RuleSource tests properly use tmp_path fixtures (no hardcoded paths) and validate both the happy path (loading from lessons.md) and the graceful degradation when the brain directory doesn't exist.

src/gradata/middleware/anthropic_adapter.py (6)

1-36: LGTM!

Proper module structure with from __future__ import annotations, clear docstring with usage example, and appropriate imports from the core module.


38-46: LGTM!

The import guard correctly raises an informative ImportError with an install hint, and properly chains the original exception using from exc.


48-71: LGTM!

The _extract_text function robustly handles multiple response shapes: direct string content (line 57-58), SDK objects with typed content blocks, and dict-shaped responses. This defensive approach ensures compatibility across Anthropic SDK versions.


74-96: LGTM!

The middleware class is well-designed: it validates the optional dependency upfront, stores the original messages object for delegation, and uses __getattr__ to transparently pass through all other client attributes.


98-124: LGTM!

The _MessagesProxy correctly handles both system prompt formats (string and list of content blocks) and properly delegates non-create calls to the original messages object. The guard on line 121 (if text:) appropriately skips enforcement when no text was extracted.


126-136: LGTM!

Clean convenience constructor that mirrors the class parameters.

Comment thread src/gradata/middleware/_core.py
Comment thread src/gradata/middleware/crewai_adapter.py Outdated
Comment thread src/gradata/middleware/langchain_adapter.py Outdated
Comment thread tests/test_middleware_crewai.py Outdated
Consolidate the "getattr-or-dict-key" response-probing pattern into a
single `_get` helper in `_core.py`. All four adapters
(openai/anthropic/langchain/crewai) were re-implementing the same
attr-then-fallback-to-dict lookup around response fields and content
blocks, so the helper lets each extractor collapse by ~30%.

Also clean up adjacent smells:
- openai: drop dead `if not block: return list(messages)` branch (guarded
  upstream) and the double `list(messages)` copy at the call site.
- openai `_inject_into_messages`: flatten the nested if/else so the
  string/None case is the one explicit branch and every non-string
  content (multimodal list, unexpected shape) falls through to
  "prepend a fresh system message".
- crewai: pull the output text-key tuple into a module constant so it's
  defined once rather than repeated for attr-vs-dict passes.
- __init__: replace the chained if-branch lazy dispatch with a single
  _LAZY_EXPORTS map + importlib.

Behaviour is unchanged — all CR-motivated fixes (multimodal list/string
handling, confidence clamping, batch-prompt iteration, strict default
flip) are preserved. Full suite: 2111 passed / 23 skipped, ruff clean,
pyright 0 errors (same 8 pre-existing warnings).

Co-Authored-By: Gradata <noreply@gradata.ai>
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

Polite re-ping: CR review is still showing CHANGES_REQUESTED but all line-level actionables from the last review were addressed in the most recent push. Could you re-review when convenient? @coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

@Gradata: Thanks for the polite re-ping! Triggering a full re-review now to reflect all the changes from your latest push.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

Addressed CR feedback round 3:

  • _core.py: guard float(confidence) against non-numeric values (e.g. "high") so malformed caller-supplied lessons skip instead of aborting injection. Added test.
  • crewai_adapter.py _coerce_text(): preserve explicit empty raw/output/text/content fields so the guard's empty-output fast path still fires (no more object-repr fallthrough). Added regression test.
  • langchain_adapter.py: module docstring updated to reflect that injection now mutates every prompt / batch entry (prior text still claimed first-prompt-only).
  • test_middleware_crewai.py: parametrized strict/non-strict boundary tests (matches coding guideline for tests/**).

Skipped:

  • CodeRabbit nitpicks on test_middleware_openai.py and test_middleware_langchain.py parametrization — already parametrized in round 2 (@pytest.mark.parametrize("strict", [True, False])).
  • CR nitpick on test_middleware_core.py fixture confidence bounds — already capped at min(1.0, ...).

pytest tests/ -x -q: 2113 passed, 23 skipped.
Push SHA: 701d27a.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

@coderabbitai review — fixes pushed, please re-verify.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

@Gradata: Thanks for the detailed round-3 summary and the re-ping! Kicking off a fresh review now to verify all the addressed items — the float(confidence) guard, _coerce_text() empty-field preservation, the langchain_adapter.py docstring correction, and the parametrized CrewAI boundary tests.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review — fixes pushed since last review, please re-verify

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

@Gradata: Thanks for the heads-up! Kicking off a fresh incremental review now to verify the latest pushed fixes.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review — CR Pro active, please re-verify.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

@Gradata: Thanks for confirming CR Pro is active — kicking off a fresh review now to re-verify all the latest fixes across _core.py, crewai_adapter.py, langchain_adapter.py, and the updated test suite.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review — re-verify latest pushes

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

@Gradata: Thanks for the re-ping! Kicking off a fresh incremental review now to re-verify all the latest pushes across the middleware adapters and test suite.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review — rebased on latest main, all prior fixes applied

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

@Gradata: Thanks for the heads-up on the rebase! Kicking off a fresh incremental review now to verify the rebased branch and confirm all prior fixes are intact across _core.py, crewai_adapter.py, langchain_adapter.py, and the full test suite.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata Gradata merged commit e5de451 into main Apr 15, 2026
16 checks passed
@Gradata Gradata deleted the feat/sdk-middleware-adapter branch April 15, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant