Skip to content

Conversation

@iceljc
Copy link
Collaborator

@iceljc iceljc commented Dec 23, 2025

PR Type

Enhancement


Description

  • Implement Claude streaming support with async chat completions

  • Add PDF reading capability detection via model settings

  • Refactor Anthropic provider with improved tool handling and thinking parameters

  • Enhance image converter logic with null safety checks


Diagram Walkthrough

flowchart LR
  A["Claude Chat Completion"] --> B["Streaming Support"]
  A --> C["Tool Use Handling"]
  A --> D["Thinking Parameters"]
  E["PDF Reading"] --> F["Model Capability Check"]
  G["Image Converter"] --> H["Null Safety Refactor"]
  B --> I["Message Hub Integration"]
  C --> J["Stop Reason Constants"]
Loading

File Walkthrough

Relevant files
Enhancement
LlmModelSetting.cs
Add PDF reading capability property                                           

src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs

  • Added AllowPdfReading property that checks if model supports PDF
    reading capability
  • Property returns true only if Capabilities contains
    LlmModelCapability.PdfReading
+2/-0     
FileInstructService.Pdf.cs
Refactor PDF reading with model capability detection         

src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs

  • Extract model name to variable for reuse across method
  • Retrieve model settings using ILlmProviderService to check PDF reading
    capability
  • Replace provider-specific check with model capability check via
    AllowPdfReading
  • Improve image converter selection logic based on model capabilities
+10/-4   
StopReason.cs
Add Claude stop reason constants                                                 

src/Plugins/BotSharp.Plugin.AnthropicAI/Constants/StopReason.cs

  • Create new constants file for Claude stop reason values
  • Define constants for EndTurn, MaxTokens, ToolUse, StopSequence,
    ContentFilter, and GuardRail
+11/-0   
ChatCompletionProvider.cs
Implement Claude streaming and enhance chat completion     

src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs

  • Implement GetChatCompletionsAsync method with streaming callback
    support
  • Implement GetChatCompletionsStreamingAsync method with real-time text
    streaming and message hub integration
  • Add IConversationStateService dependency for state management
  • Refactor tool handling to use response.ToolCalls instead of parsing
    content blocks
  • Add thinking parameters support via GetThinkingParams method
  • Extract Anthropic client creation to ProviderHelper.GetAnthropicClient
  • Improve message content handling for multimodal support in assistant
    messages
  • Use stop reason constants instead of string literals
  • Add streaming parameter to PrepareOptions method
  • Refactor parameter initialization and tool building logic
+272/-104
ProviderHelper.cs
Add Anthropic client factory helper                                           

src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ProviderHelper.cs

  • Create new helper class with static method to instantiate Anthropic
    client
  • Encapsulate client creation logic with settings retrieval and API
    authentication
+12/-0   
Using.cs
Reorganize and expand global using statements                       

src/Plugins/BotSharp.Plugin.AnthropicAI/Using.cs

  • Reorganize global using statements with better grouping by namespace
  • Add missing imports for streaming, message hub, and settings
  • Add imports for BotSharp.Core.Infrastructures.Streams and
    BotSharp.Core.MessageHub
  • Add imports for hooks, message hub models, and ML task settings
+16/-6   
Bug fix
FileInstructService.cs
Improve image converter null safety                                           

src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs

  • Add null/empty check for provider parameter in GetImageConverter
    method
  • Return null early if provider is null or empty instead of using
    default
  • Simplify converter lookup to use exact provider match
+6/-1     
Configuration changes
BotSharp.Plugin.AnthropicAI.csproj
Update project dependency to Core                                               

src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj

  • Change project reference from BotSharp.Abstraction to BotSharp.Core
  • Add UTF-8 BOM to project file
+2/-2     

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive info in logs

Description: New DEBUG logging prints potentially sensitive LLM content and tool-call arguments (which
may include secrets/PII) via _logger.LogDebug(...) for both tool calls and full streamed
text, risking sensitive information exposure in logs when debug logging is enabled.
ChatCompletionProvider.cs [217-233]

Referred Code
#if DEBUG
                    _logger.LogDebug($"Tool Call (id: {toolCall?.Id}) => {toolCall?.Name}({toolCall?.Arguments})");
#endif
                }
                else if (delta.StopReason == StopReason.EndTurn)
                {
                    var allText = textStream.GetText();
                    responseMessage = new RoleDialogModel(AgentRole.Assistant, allText)
                    {
                        CurrentAgentId = agent.Id,
                        MessageId = messageId,
                        IsStreaming = true
                    };

#if DEBUG
                    _logger.LogDebug($"Stream text Content: {allText}");
#endif
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Null handling missing: GetAnthropicClient dereferences settings.ApiKey without checking for null settings (or
missing/empty API key), which can cause runtime failures without graceful handling.

Referred Code
public static AnthropicClient GetAnthropicClient(string provider, string model, IServiceProvider services)
{
    var settingsService = services.GetRequiredService<ILlmProviderService>();
    var settings = settingsService.GetSetting(provider, model);
    var client = new AnthropicClient(new APIAuthentication(settings.ApiKey));
    return client;

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Internal details exposed: The streaming path can emit delta.StopReason as assistant content, potentially exposing
internal stop reasons to end users instead of returning a generic user-facing message.

Referred Code
else if (!string.IsNullOrEmpty(delta.StopReason))
{
    responseMessage = new RoleDialogModel(AgentRole.Assistant, delta.StopReason)
    {
        CurrentAgentId = agent.Id,
        MessageId = messageId,
        IsStreaming = true
    };
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Sensitive data in logs: Debug logging prints tool call arguments (toolCall?.Arguments) which may include sensitive
user-provided data and should be redacted or omitted.

Referred Code
#if DEBUG
                    _logger.LogDebug($"Tool Call (id: {toolCall?.Id}) => {toolCall?.Name}({toolCall?.Arguments})");
#endif

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
External input validation: Provider/model values derived from options are used to resolve settings and capabilities
without visible validation/allowlisting, requiring verification that upstream code
constrains these inputs safely.

Referred Code
var provider = options?.Provider ?? "openai";
var model = options?.Model ?? "gpt-5-mini";

var pdfFiles = await DownloadAndSaveFiles(sessionDir, files);
var targetFiles = pdfFiles;

var settingsService = _services.GetRequiredService<ILlmProviderService>();
var modelSettings = settingsService.GetSetting(provider, model);
var converter = GetImageConverter(options?.ImageConverter);

if (converter == null && modelSettings?.AllowPdfReading != true)
{

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Decouple plugin from core implementation

The Anthropic plugin's new dependency on BotSharp.Core for streaming violates
the plugin architecture. To fix this, abstract streaming and message hub
features into interfaces within BotSharp.Abstraction and make the plugin depend
on those interfaces.

Examples:

src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj [18]
    <ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs [164-186]
        var hub = _services.GetRequiredService<MessageHub<HubObserveData<RoleDialogModel>>>();
        var conv = _services.GetRequiredService<IConversationService>();
        var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty;

        var contentHooks = _services.GetHooks<IContentGeneratingHook>(agent.Id);
        // Before chat completion hook
        foreach (var hook in contentHooks)
        {
            await hook.BeforeGenerating(agent, conversations);
        }

 ... (clipped 13 lines)

Solution Walkthrough:

Before:

// In BotSharp.Plugin.AnthropicAI.csproj
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />

// In ChatCompletionProvider.cs
using BotSharp.Core.Infrastructures.Streams;
using BotSharp.Core.MessageHub;

public async Task<RoleDialogModel> GetChatCompletionsStreamingAsync(...)
{
    // Direct dependency on concrete class from BotSharp.Core
    var hub = _services.GetRequiredService<MessageHub<...>>();
    
    // Direct instantiation of concrete class from BotSharp.Core
    using var textStream = new RealtimeTextStream();
    // ...
}

After:

// In BotSharp.Plugin.AnthropicAI.csproj
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />

// In ChatCompletionProvider.cs (assuming new abstractions are created)
using BotSharp.Abstraction.Messaging; // e.g., IMessageHub
using BotSharp.Abstraction.Streaming; // e.g., IRealtimeTextStream

public async Task<RoleDialogModel> GetChatCompletionsStreamingAsync(...)
{
    // Dependency on abstraction from BotSharp.Abstraction
    var hub = _services.GetRequiredService<IMessageHub<...>>();
    
    // Dependency on abstraction (e.g., via factory)
    using var textStream = _services.GetRequiredService<IRealtimeTextStream>();
    // ...
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical architectural flaw where the AnthropicAI plugin now depends on concrete implementations in BotSharp.Core, breaking the intended loose coupling and modularity of the plugin system.

High
Possible issue
Restore handling of file URLs

Restore the functionality to handle file URLs in CollectMessageContentParts by
adding logic to process file.FileUrl in addition to file.FileData, ensuring
support for images referenced by URL.

src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs [499-515]

 private void CollectMessageContentParts(List<ContentBase> contentParts, List<BotSharpFile> files)
 {
     foreach (var file in files)
     {
         if (!string.IsNullOrEmpty(file.FileData))
         {
             var (contentType, binary) = FileUtility.GetFileInfoFromData(file.FileData);
             var contentPart = new ImageContent
             {
                 Source = new ImageSource
                 {
                     MediaType = contentType,
                     Data = binary.ToArray()
                 }
             };
             contentParts.Add(contentPart);
         }
+        else if (!string.IsNullOrEmpty(file.FileUrl))
+        {
+            var contentType = FileUtility.GetFileContentType(file.FileUrl);
+            var contentPart = new ImageContent
+            {
+                Source = new ImageSource
+                {
+                    MediaType = contentType,
+                    Url = file.FileUrl
+                }
+            };
+            contentParts.Add(contentPart);
+        }
     }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a functional regression where handling for file.FileUrl was removed. Re-adding this logic is critical for maintaining feature parity and supporting multimodal messages that reference images by URL.

Medium
Check settings before client creation

Add validation to GetAnthropicClient to ensure that the retrieved settings and
settings.ApiKey are not null or empty, throwing an InvalidOperationException if
they are missing.

src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ProviderHelper.cs [5-11]

 public static AnthropicClient GetAnthropicClient(string provider, string model, IServiceProvider services)
 {
     var settingsService = services.GetRequiredService<ILlmProviderService>();
-    var settings = settingsService.GetSetting(provider, model);
-    var client = new AnthropicClient(new APIAuthentication(settings.ApiKey));
-    return client;
+    var settings = settingsService.GetSetting(provider, model)
+        ?? throw new InvalidOperationException($"No LLM settings found for provider '{provider}', model '{model}'.");
+    if (string.IsNullOrWhiteSpace(settings.ApiKey))
+    {
+        throw new InvalidOperationException($"API key is not configured for provider '{provider}', model '{model}'.");
+    }
+    return new AnthropicClient(new APIAuthentication(settings.ApiKey));
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly points out missing validation for settings and settings.ApiKey, which could lead to a NullReferenceException. Adding explicit checks and throwing informative exceptions significantly improves the robustness and debuggability of the client creation logic.

Medium
Avoid adding empty text content part

Avoid adding an empty TextContent part to assistant messages by checking if
message.LlmContent is not null or empty before adding it to the content parts.

src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs [353-363]

 var contentParts = new List<ContentBase>();
 if (allowMultiModal && !message.Files.IsNullOrEmpty())
 {
     CollectMessageContentParts(contentParts, message.Files);
 }
-contentParts.Add(new TextContent() { Text = message.LlmContent });
+
+if (!string.IsNullOrEmpty(message.LlmContent))
+{
+    contentParts.Add(new TextContent() { Text = message.LlmContent });
+}
+
 messages.Add(new Message
 {
     Role = RoleType.Assistant,
     Content = contentParts
 });
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This is a valid improvement that makes the API payload cleaner and more robust by preventing the addition of an empty TextContent part, which could be unnecessary or cause issues.

Low
Learned
best practice
Prevent null converter usage

Avoid calling ConvertPdfToImages with a null converter; if the model supports
PDF reading, skip conversion, otherwise fail fast with a clear error when no
converter is configured.

src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs [34-42]

 var converter = GetImageConverter(options?.ImageConverter);
 
 if (converter == null && modelSettings?.AllowPdfReading != true)
 {
     var fileCoreSettings = _services.GetRequiredService<FileCoreSettings>();
     converter = GetImageConverter(fileCoreSettings?.ImageConverter?.Provider);
 }
 
-targetFiles = await ConvertPdfToImages(converter, pdfFiles);
+if (converter == null)
+{
+    if (modelSettings?.AllowPdfReading == true)
+    {
+        targetFiles = pdfFiles;
+    }
+    else
+    {
+        throw new InvalidOperationException("No PDF-to-image converter configured for models that don't support direct PDF reading.");
+    }
+}
+else
+{
+    targetFiles = await ConvertPdfToImages(converter, pdfFiles);
+}
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add precise null/state checks to avoid passing null dependencies into downstream operations (defensive coding).

Low
General
Simplify and align function arguments serialization

Simplify and align function argument serialization in the streaming
implementation by using ToJsonString() instead of a complex chain of ToString(),
IfNullOrEmptyAs(), and null-coalescing.

src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs [207-215]

 var toolCall = choice.ToolCalls.FirstOrDefault();
 responseMessage = new RoleDialogModel(AgentRole.Function, string.Empty)
 {
     CurrentAgentId = agent.Id,
     MessageId = messageId,
     ToolCallId = toolCall?.Id,
     FunctionName = toolCall?.Name,
-    FunctionArgs = toolCall?.Arguments?.ToString()?.IfNullOrEmptyAs("{}") ?? "{}"
+    FunctionArgs = toolCall?.Arguments?.ToJsonString() ?? "{}"
 };
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This suggestion improves code quality by simplifying the serialization of FunctionArgs and aligning the streaming implementation with the non-streaming one, which enhances readability and maintainability.

Low
  • More

@iceljc iceljc merged commit 7e98ce6 into SciSharp:master Dec 23, 2025
4 checks passed
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