Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 7 additions & 20 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,16 @@ rand = "0.8"
futures-util = "0.3"

# Agent dependencies (using Rig - LLM application framework)
rig-core = { version = "0.27", features = ["derive"] }
rig-bedrock = { version = "0.3", path = "vendor/rig-bedrock" } # Vendored with fix for extended thinking + tool calls
rig-core = { version = "0.27", features = ["derive", "image"] }

# AWS Bedrock dependencies (inlined bedrock module with extended thinking fixes)
async-stream = "0.3"
aws-config = { version = "1", features = ["behavior-version-latest"] }
aws-sdk-bedrockruntime = "1"
aws-smithy-types = "1"
base64 = "0.22"
schemars = "0.8"
tracing = "0.1"

# Diff rendering for file confirmation UI
similar = "2.6"
Expand Down Expand Up @@ -107,8 +115,3 @@ path = "examples/check_vulnerabilities.rs"
name = "security_analysis"
path = "examples/security_analysis.rs"

# Patch rig-bedrock to fix extended thinking support with tool use
# The upstream version drops Reasoning blocks when a tool call is present.
# See: vendor/rig-bedrock/src/types/assistant_content.rs for the fix.
[patch.crates-io]
rig-bedrock = { version = "0.3", path = "vendor/rig-bedrock" }
4 changes: 2 additions & 2 deletions src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ pub async fn run_interactive(
}
ProviderType::Bedrock => {
// Bedrock provider via rig-bedrock - same pattern as OpenAI/Anthropic
let client = rig_bedrock::client::Client::from_env();
let client = crate::bedrock::client::Client::from_env();

// Extended thinking for Claude models via Bedrock
// This enables Claude to show its reasoning process before responding.
Expand Down Expand Up @@ -1477,7 +1477,7 @@ pub async fn run_query(
}
ProviderType::Bedrock => {
// Bedrock provider via rig-bedrock - same pattern as Anthropic
let client = rig_bedrock::client::Client::from_env();
let client = crate::bedrock::client::Client::from_env();
let model_name = model
.as_deref()
.unwrap_or("global.anthropic.claude-sonnet-4-5-20250929-v1:0");
Expand Down
4 changes: 2 additions & 2 deletions vendor/rig-bedrock/src/client.rs → src/bedrock/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::image::ImageGenerationModel;
use crate::{completion::CompletionModel, embedding::EmbeddingModel};
use super::image::ImageGenerationModel;
use super::{completion::CompletionModel, embedding::EmbeddingModel};
use aws_config::{BehaviorVersion, Region};
use rig::client::Nothing;
use rig::prelude::*;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! All supported models <https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html>

use crate::{
use super::{
client::Client,
types::{
assistant_content::AwsConverseOutput, completion_request::AwsCompletionRequest,
Expand Down Expand Up @@ -173,7 +173,7 @@ impl CompletionModel {

impl completion::CompletionModel for CompletionModel {
type Response = AwsConverseOutput;
type StreamingResponse = crate::streaming::BedrockStreamingResponse;
type StreamingResponse = super::streaming::BedrockStreamingResponse;

type Client = Client;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use aws_smithy_types::Blob;
use rig::embeddings::{self, Embedding, EmbeddingError};
use serde::{Deserialize, Serialize};

use crate::{client::Client, types::errors::AwsSdkInvokeModelError};
use super::{client::Client, types::errors::AwsSdkInvokeModelError};

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
Expand Down
6 changes: 3 additions & 3 deletions vendor/rig-bedrock/src/image.rs → src/bedrock/image.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::client::Client;
use crate::types::errors::AwsSdkInvokeModelError;
use crate::types::text_to_image::{TextToImageGeneration, TextToImageResponse};
use super::client::Client;
use super::types::errors::AwsSdkInvokeModelError;
use super::types::text_to_image::{TextToImageGeneration, TextToImageResponse};
use aws_smithy_types::Blob;
use rig::image_generation::{
self, ImageGenerationError, ImageGenerationRequest, ImageGenerationResponse,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::types::completion_request::AwsCompletionRequest;
use crate::{completion::CompletionModel, types::errors::AwsSdkConverseStreamError};
use super::types::completion_request::AwsCompletionRequest;
use super::{completion::CompletionModel, types::errors::AwsSdkConverseStreamError};
use async_stream::stream;
use aws_sdk_bedrockruntime::types as aws_bedrock;
use rig::completion::GetTokenUsage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rig::{
};
use serde::{Deserialize, Serialize};

use crate::types::message::RigMessage;
use super::message::RigMessage;

use super::{converse_output::InternalConverseOutput, json::AwsDocument};
use rig::completion;
Expand Down Expand Up @@ -78,6 +78,29 @@ impl TryFrom<aws_bedrock::ContentBlock> for RigAssistantContent {
type Error = CompletionError;

fn try_from(value: aws_bedrock::ContentBlock) -> Result<Self, Self::Error> {
// Debug: Log incoming AWS content block
let block_type = match &value {
aws_bedrock::ContentBlock::Text(t) => format!("Text(len={})", t.len()),
aws_bedrock::ContentBlock::ToolUse(t) => {
format!("ToolUse(id={}, name={})", t.tool_use_id, t.name)
}
aws_bedrock::ContentBlock::ReasoningContent(r) => match r {
aws_bedrock::ReasoningContentBlock::ReasoningText(rt) => format!(
"ReasoningContent::ReasoningText(len={}, has_sig={})",
rt.text.len(),
rt.signature.is_some()
),
aws_bedrock::ReasoningContentBlock::RedactedContent(blob) => format!(
"ReasoningContent::RedactedContent(blob_len={})",
blob.as_ref().len()
),
_ => "ReasoningContent::Unknown".to_string(),
},
aws_bedrock::ContentBlock::ToolResult(_) => "ToolResult".to_string(),
_ => "Other".to_string(),
};
tracing::debug!("Converting AWS ContentBlock to Rig: {}", block_type);

match value {
aws_bedrock::ContentBlock::Text(text) => {
Ok(RigAssistantContent(AssistantContent::Text(Text { text })))
Expand All @@ -91,18 +114,42 @@ impl TryFrom<aws_bedrock::ContentBlock> for RigAssistantContent {
)),
aws_bedrock::ContentBlock::ReasoningContent(reasoning_block) => match reasoning_block {
aws_bedrock::ReasoningContentBlock::ReasoningText(reasoning_text) => {
tracing::debug!(
"Converting ReasoningText: text_len={}, signature={:?}",
reasoning_text.text.len(),
reasoning_text
.signature
.as_ref()
.map(|s| format!("{}...", &s[..s.len().min(20)]))
);
Ok(RigAssistantContent(AssistantContent::Reasoning(
rig::message::Reasoning::new(&reasoning_text.text)
.with_signature(reasoning_text.signature),
)))
}
_ => Err(CompletionError::ProviderError(
"AWS Bedrock returned unsupported ReasoningContentBlock variant".into(),
)),
aws_bedrock::ReasoningContentBlock::RedactedContent(blob) => {
tracing::warn!(
"AWS Bedrock returned RedactedContent (blob_len={}). This variant is not yet supported!",
blob.as_ref().len()
);
Err(CompletionError::ProviderError(format!(
"AWS Bedrock returned RedactedContent (blob_len={}). This variant needs to be handled for multi-turn conversations with extended thinking.",
blob.as_ref().len()
)))
}
_ => {
tracing::error!("AWS Bedrock returned unknown ReasoningContentBlock variant");
Err(CompletionError::ProviderError(
"AWS Bedrock returned unsupported ReasoningContentBlock variant".into(),
))
}
},
_ => Err(CompletionError::ProviderError(
"AWS Bedrock returned unsupported ContentBlock".into(),
)),
_ => {
tracing::warn!("AWS Bedrock returned unsupported ContentBlock type");
Err(CompletionError::ProviderError(
"AWS Bedrock returned unsupported ContentBlock".into(),
))
}
}
}
}
Expand Down Expand Up @@ -190,12 +237,9 @@ impl TryFrom<RigAssistantContent> for aws_bedrock::ContentBlock {

#[cfg(test)]
mod tests {
use crate::types::{
assistant_content::RigAssistantContent, converse_output::InternalConverseOutput,
errors::TypeConversionError,
};

use super::super::{converse_output::InternalConverseOutput, errors::TypeConversionError};
use super::AwsConverseOutput;
use super::RigAssistantContent;
use aws_sdk_bedrockruntime::types as aws_bedrock;
use rig::{OneOrMany, completion, message::AssistantContent};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::types::json::AwsDocument;
use crate::types::message::RigMessage;
use super::json::AwsDocument;
use super::message::RigMessage;
use aws_sdk_bedrockruntime::types as aws_bedrock;
use aws_sdk_bedrockruntime::types::{
InferenceConfiguration, SystemContentBlock, Tool, ToolConfiguration, ToolInputSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use rig::{
message::{Document, DocumentSourceKind},
};

pub(crate) use crate::types::media_types::RigDocumentMediaType;
pub(crate) use super::media_types::RigDocumentMediaType;
use base64::{Engine, prelude::BASE64_STANDARD};
use uuid::Uuid;

Expand Down Expand Up @@ -95,7 +95,7 @@ mod tests {
message::{Document, DocumentMediaType, DocumentSourceKind},
};

use crate::types::document::RigDocument;
use super::RigDocument;

#[test]
fn test_document_to_aws_document() {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ mod tests {
message::{DocumentSourceKind, Image, ImageMediaType},
};

use crate::types::image::RigImage;
use super::RigImage;

#[test]
fn test_image_to_aws_image() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ mod tests {
use aws_smithy_types::{Document, Number};
use serde_json::Value;

use crate::types::json::AwsDocument;
use super::AwsDocument;

#[test]
fn test_json_to_aws_document() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,51 @@ impl TryFrom<RigMessage> for aws_bedrock::Message {
.map_err(|e| CompletionError::RequestError(Box::new(e)))?
}
Message::Assistant { content, .. } => {
// Debug: Log what we're converting from Rig to AWS format
tracing::debug!(
"Converting Assistant message with {} content blocks to AWS format",
content.len()
);
for (i, c) in content.iter().enumerate() {
let type_name = match c {
AssistantContent::Reasoning(r) => format!(
"Reasoning(len={}, has_sig={})",
r.reasoning.len(),
r.signature.is_some()
),
AssistantContent::ToolCall(t) => {
format!("ToolCall(id={}, name={})", t.id, t.function.name)
}
AssistantContent::Text(t) => format!("Text(len={})", t.text.len()),
AssistantContent::Image(_) => "Image".to_string(),
};
tracing::debug!(" Input content[{}]: {}", i, type_name);
}

// Convert all content blocks
let mut content_blocks: Vec<aws_bedrock::ContentBlock> = content
.into_iter()
.map(|content| RigAssistantContent(content).try_into())
.collect::<Result<Vec<aws_bedrock::ContentBlock>, _>>()?;

// Debug: Log converted blocks before sorting
tracing::debug!(
"Converted {} content blocks, before sorting:",
content_blocks.len()
);
for (i, block) in content_blocks.iter().enumerate() {
let type_name = match block {
aws_bedrock::ContentBlock::ReasoningContent(_) => "ReasoningContent",
aws_bedrock::ContentBlock::ToolUse(t) => {
tracing::debug!(" ToolUse: id={}, name={}", t.tool_use_id, t.name);
"ToolUse"
}
aws_bedrock::ContentBlock::Text(_) => "Text",
_ => "Other",
};
tracing::debug!(" Block[{}]: {}", i, type_name);
}

// CRITICAL: Sort to put Reasoning blocks FIRST
// AWS Bedrock requires assistant messages to start with thinking blocks
// when extended thinking is enabled. Without this, multi-turn conversations
Expand All @@ -48,6 +87,18 @@ impl TryFrom<RigMessage> for aws_bedrock::Message {
_ => 3,
});

// Debug: Log after sorting
tracing::debug!("After sorting, content block order:");
for (i, block) in content_blocks.iter().enumerate() {
let type_name = match block {
aws_bedrock::ContentBlock::ReasoningContent(_) => "ReasoningContent",
aws_bedrock::ContentBlock::ToolUse(_) => "ToolUse",
aws_bedrock::ContentBlock::Text(_) => "Text",
_ => "Other",
};
tracing::debug!(" Block[{}]: {}", i, type_name);
}

aws_bedrock::Message::builder()
.role(aws_bedrock::ConversationRole::Assistant)
.set_content(Some(content_blocks))
Expand Down Expand Up @@ -113,7 +164,7 @@ impl TryFrom<super::converse_output::Message> for RigMessage {

#[cfg(test)]
mod tests {
use crate::types::message::RigMessage;
use super::RigMessage;
use aws_sdk_bedrockruntime::types as aws_bedrock;
use rig::{
OneOrMany,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ mod tests {
message::{DocumentSourceKind, Image, ImageMediaType, Text, ToolResultContent},
};

use crate::types::tool::RigToolResultContent;
use super::RigToolResultContent;

#[test]
fn rig_tool_text_to_aws_tool() {
Expand Down
Loading