Skip to content

fix/added universal Bedrock model support using Converse API. fixes #1306#1308

Merged
muddlebee merged 10 commits intoTracer-Cloud:mainfrom
shubh586:fix/bedrock-non-anthropic-models
May 5, 2026
Merged

fix/added universal Bedrock model support using Converse API. fixes #1306#1308
muddlebee merged 10 commits intoTracer-Cloud:mainfrom
shubh586:fix/bedrock-non-anthropic-models

Conversation

@shubh586
Copy link
Copy Markdown
Contributor

@shubh586 shubh586 commented May 5, 2026

Fixes #1306

Describe the changes you have made in this PR -

Summary

This PR overhauls the BedrockLLMClient to unlock support for all Amazon Bedrock foundation models (such as Mistral, LLaMA, and Amazon Titan) by integrating the model-agnostic boto3 Bedrock converse API.

Before and After

🔴 Before this PR:
The BedrockLLMClient was strictly hardcoded to use the AnthropicBedrock SDK.

  • If a user attempted to use any non-Anthropic model (e.g., mistral.mistral-large-2402-v1:0), the investigation would get STUCK and fail with a generic 'NoneType' object is not iterable error.

🟢 After this PR:
The client now dynamically inspects the requested model ID.

  • Anthropic Claude models are still safely routed to the AnthropicBedrock SDK (ensuring zero regressions).
  • All other models are automatically routed through a new _invoke_converse handler that uses boto3.client("bedrock-runtime"). This translates OpenSRE's internal messages into Bedrock's native converse schema.

Steps to reproduce

  1. Configure your environment variables:
    • LLM_PROVIDER=bedrock
    • BEDROCK_REASONING_MODEL=mistral.mistral-large-2402-v1:0
  2. Run the opensre interactive shell.
  3. Send a prompt to the agent.
  4. Before: The assistant fails with a 'NoneType' error. After: The Mistral model successfully executes the investigation.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

  • Overhauls BedrockLLMClient to support all Bedrock foundation models by routing non-Anthropic model IDs (Mistral, LLaMA, Titan, etc.) through the boto3 converse API while preserving the existing AnthropicBedrock SDK path for Claude models, gated by the new _is_anthropic_bedrock_model helper.
  • Previously flagged issues are resolved: the dead else-branch in content formatting is removed, and the empty-text fallback now raises a RuntimeError with stopReason context instead of returning raw boto3 metadata.
  • Six new unit tests cover the routing helper, converse dispatch, system/temperature forwarding, and the no-text-block error path; the _invoke_anthropic dispatch branch lacks an end-to-end coverage test.

Confidence Score: 5/5

Safe to merge — no logic regressions on the Claude path, and the new Converse path is well-guarded with explicit error handling.

All findings are P2 (non-blocking style/coverage concerns). Previously flagged P1s (dead else-branch, str(response) fallback) are resolved. Core routing logic is correct and tested.

No files require special attention; the missing _invoke_anthropic dispatch test in tests/services/test_llm_client.py is the only gap worth addressing before the next refactor.

Important Files Changed

Filename Overview
app/services/llm_client.py Adds _is_anthropic_bedrock_model routing helper and splits invoke into _invoke_anthropic (AnthropicBedrock SDK) and _invoke_converse (boto3); previously flagged issues resolved.
tests/services/test_llm_client.py Adds 6 new tests covering the routing helper and converse path; missing a dispatch test for the Claude → AnthropicBedrock path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["BedrockLLMClient.invoke()"] --> B{"_is_anthropic_bedrock_model(model_id)"}
    B -->|True: anthropic.claude in model_id| C["_invoke_anthropic()"]
    B -->|False: all other models| D["_invoke_converse()"]
    C --> E["AnthropicBedrock.messages.create()"]
    E -->|retry w/ backoff| E
    E --> F["LLMResponse(content)"]
    D --> G["boto3 bedrock-runtime.converse()"]
    G -->|retry w/ backoff| G
    G --> H{"text blocks found?"}
    H -->|No| I["raise RuntimeError"]
    H -->|Yes| F
Loading

Reviews (2): Last reviewed commit: "docs(bedrock): align application inferen..." | Re-trigger Greptile

Comment on lines +271 to +279
converse_messages = [
{
"role": msg["role"],
"content": [{"text": msg["content"]}]
if isinstance(msg["content"], str)
else msg["content"],
}
for msg in messages
]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Dead else branch silently passes incompatible content blocks

The else msg["content"] branch is unreachable in practice: _normalize_messages always stringifies content (str(content)), and engine.apply(str) -> str. If this branch were ever reached (e.g., by a future guardrail returning structured content), raw content would be forwarded to the Converse API, which expects [{"text": "..."}] — not Anthropic's [{"type": "text", "text": "..."}] format — causing a silent schema mismatch and API error.

Consider removing the dead branch to make intent explicit:

converse_messages = [
    {
        "role": msg["role"],
        "content": [{"text": msg["content"]}],
    }
    for msg in messages
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_normalize_messages always produces strings, so the else branch is unreachable. Remove it to make intent explicit.

Comment on lines +318 to +321
content = "\n".join(text_parts).strip()
if not content:
content = str(response)
return LLMResponse(content=content)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Raw boto3 response dict used as LLM content on empty-text fallback

When no "text" blocks are found in the response (e.g., the model returns only tool-use blocks, or an unexpected schema), content falls back to str(response). The boto3 Converse response dict includes stopReason, usage, ResponseMetadata, etc., so downstream callers (e.g., StructuredOutputClient._extract_json_payload) would receive raw AWS metadata as the LLM answer, causing confusing JSON parse failures.

A more explicit fallback would surface the issue clearly:

if not content:
    logger.warning("Bedrock converse returned no text blocks; raw response: %s", response)
    raise RuntimeError(f"Bedrock converse returned no text content: {response}")

Copy link
Copy Markdown
Collaborator

@muddlebee muddlebee May 5, 2026

Choose a reason for hiding this comment

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

str(response) on a boto3 dict dumps ResponseMetadata, usage, and stopReason, which will break downstream JSON parsing. Raise a clear error instead.

Comment thread app/services/llm_client.py
@shubh586 shubh586 changed the title Fix/bedrock non anthropic models fix/added universal Bedrock model support using Converse API. fixes #1306 May 5, 2026
@muddlebee
Copy link
Copy Markdown
Collaborator

@shubh586 fix review comments

Comment on lines +318 to +321
content = "\n".join(text_parts).strip()
if not content:
content = str(response)
return LLMResponse(content=content)
Copy link
Copy Markdown
Collaborator

@muddlebee muddlebee May 5, 2026

Choose a reason for hiding this comment

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

str(response) on a boto3 dict dumps ResponseMetadata, usage, and stopReason, which will break downstream JSON parsing. Raise a clear error instead.

content = _extract_text(response)
return LLMResponse(content=content)

def _invoke_converse(self, prompt_or_messages: Any) -> LLMResponse:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Add unit tests for _invoke_converse and _is_anthropic_bedrock_model in tests/services/test_llm_client.py. The new boto3 path has zero coverage for message conversion, response parsing, and error handling.

@shubh586
Copy link
Copy Markdown
Contributor Author

shubh586 commented May 5, 2026

Thanks for the feedback @muddlebee! I'm working on it!

Comment thread app/services/llm_client.py Outdated
model_lower = model_id.lower()
if "anthropic.claude" in model_lower:
return True
# Application inference profile ARNs — default to Anthropic unless overridden
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Application inference profile ARNs are forced to Anthropic unless BEDROCK_USE_CONVERSE=1. Non-Claude profiles still fail on AnthropicBedrock. Default these ARNs to converse or resolve the profile target model before routing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

got it! I’ll update the routing logic.

muddlebee added 2 commits May 5, 2026 22:13
- Route application-inference-profile ARNs to Converse for non-Claude pools
- Raise when Converse returns no text blocks (avoid str(response) metadata)
- Build Converse messages with explicit text blocks only
- Apply isort for boto3 import
- Add unit tests for routing, message shape, and empty-text handling
@muddlebee
Copy link
Copy Markdown
Collaborator

@greptileai review

Comment thread tests/services/test_llm_client.py Fixed
Comment thread tests/services/test_llm_client.py Fixed
Comment thread tests/services/test_llm_client.py Fixed
Comment thread tests/services/test_llm_client.py Fixed
muddlebee and others added 5 commits May 5, 2026 22:17
Use _InactiveGuardrailEngine as the patched callable instead of wrapping
construction in lambdas CodeQL flagged on the PR branch.
- Extend llm-providers Amazon Bedrock section (Claude vs Converse, profiles)
- Add BEDROCK_* and bedrock to .env.example alongside other LLM providers
Resolve .env.example: combine LLM_PROVIDER comment lists (bedrock/minimax/
ollama from PR branch + opencode from main).
@muddlebee muddlebee merged commit fcf138d into Tracer-Cloud:main May 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🎉 MERGED! @shubh586 just shipped something. The diff gods are pleased. 🙌


👋 Join us on Discord - OpenSRE : hang out, contribute, or hunt for features and issues. Everyone's welcome.

@muddlebee
Copy link
Copy Markdown
Collaborator

@shubh586 thank you for efforts, but changes not as per contributor guidlines and standards. next time please take care :)

@shubh586
Copy link
Copy Markdown
Contributor Author

shubh586 commented May 5, 2026

Thank them for review

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.

[FEATURE]support non-Anthropic models (Mistral/Llama) via Bedrock Converse API

3 participants