Skip to content

Add LLM tool call diagnostics#351

Merged
ModerRAS merged 3 commits into
masterfrom
codex/add-llm-trace-logging-and-fix-empty-encoded-value
May 13, 2026
Merged

Add LLM tool call diagnostics#351
ModerRAS merged 3 commits into
masterfrom
codex/add-llm-trace-logging-and-fix-empty-encoded-value

Conversation

@ModerRAS

@ModerRAS ModerRAS commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • add detailed diagnostics around McpToolHelper tool routing, argument conversion, DI resolution, proxy/external MCP execution, and result logging
  • log provider-specific tool call assembly/execution failures for OpenAI Chat, Responses API, Anthropic, Gemini, and Ollama
  • improve LLMAgent proxy tool import/remote execution logs and persist richer agent exception summaries

Verification

  • dotnet build TelegramSearchBot.sln --configuration Release --no-restore
  • dotnet test TelegramSearchBot.LLM.Test/TelegramSearchBot.LLM.Test.csproj --no-build --configuration Release --filter "FullyQualifiedNameMcpToolHelper|FullyQualifiedNameToolExecutor|FullyQualifiedName~OpenAIToolCallNormalization"
  • dotnet test TelegramSearchBot.Test/TelegramSearchBot.Test.csproj --no-build --configuration Release --filter "FullyQualifiedNameAgentIntegrationTests|FullyQualifiedNameAgentRegistryServiceTests|FullyQualifiedNameLLMTaskQueueServiceTests|FullyQualifiedNameChunkPollingServiceTests"

Summary by CodeRabbit

  • New Features

    • Added an exception-summary helper to produce concise, primary-exception messages for logs and user-facing errors.
  • Bug Fixes

    • Improved error handling across LLM/tool execution and background processing so failures surface clearer summarized error text.
  • Improvements

    • Expanded structured diagnostics and lifecycle logging; introduced configurable log level (default: Verbose) and updated docs/README.
    • Enhanced drafting/message-stream logging and task/tool lifecycle visibility.
  • Tests

    • Added tests covering log-level parsing behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1c4ea0e-2f13-436f-a2d1-5e41abe43583

📥 Commits

Reviewing files that changed from the base of the PR and between a07cb9d and e7bc4ab.

📒 Files selected for processing (2)
  • TelegramSearchBot.Common/Env.cs
  • TelegramSearchBot.Test/Common/EnvBotApiEndpointTests.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • TelegramSearchBot.Common/Env.cs

📝 Walkthrough

Walkthrough

Adds ExceptionExtensions and applies concise exception-summary logging and richer diagnostics across LLM tool execution, MCP orchestration, agent loops, polling/consumption, streaming, and global Serilog configuration; includes Env log-level parsing and tests.

Changes

Error Logging and Observability Enhancement

Layer / File(s) Summary
Exception unwrapping utilities
TelegramSearchBot.Common/ExceptionExtensions.cs
New ExceptionExtensions helper with GetPrimaryException and GetLogSummary to unwrap wrapper exceptions and format primary-exception summaries.
Env, docs, and tests
TelegramSearchBot.Common/Env.cs, README.md, Docs/Build_and_Test_Guide.md, TelegramSearchBot.Test/*
Adds Config.LogLevel, Env.ResolveSerilogMinimumLevel, exposes Env.SerilogMinimumLevel, updates docs/examples, and adds unit tests for log-level parsing.
OpenAIResponsesService: function-call & snapshot handling
TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs
Normalizes CallId/name/arguments, wraps ResponseItem.CreateFunctionCallItem in try/catch with detailed logs, and adjusts snapshot deserialization with marker stripping and last-resolved CallId fallback.
OpenAIService sanitization & tool paths
TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs
Adds SanitizeAndTruncateArguments and uses it when logging native tool-call metadata failures; uses ex.GetLogSummary() for native/XML per-tool failures and logs structured context.
Anthropic/Gemini/Ollama tool error handling
TelegramSearchBot.LLM/Service/AI/LLM/AnthropicService.cs, GeminiService.cs, OllamaService.cs
Wraps JSON parsing for tool inputs/display, logs structured context on parse/execution failures, and uses ex.GetLogSummary() for tool-result/error strings.
MCP tool orchestration and external execution
TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs
Reworks ExecuteRegisteredToolAsync: elapsed-time capture, CDATA sanitization, structured route/argument logs, proxy/external/local branches, bounded/truncated logging for external executor results, Redis export diagnostics, and proxy registry bookkeeping.
Agent loop, ToolExecutor, and LLMAgent routing
TelegramSearchBot.LLMAgent/...
AgentLoopService computes retry/error reasons via GetLogSummary() and records extended diagnostics; ToolExecutor accepts optional ILogger and logs remote execution lifecycle; LLMAgentProgram logs Redis import payload and proxy routing with error summaries.
Chunk polling & task consumer
TelegramSearchBot/Service/AI/LLM/ChunkPollingService.cs, TelegramTaskConsumer.cs
Both services accept optional ILogger<T>; ChunkPollingService logs per-task polling exceptions and final snapshot delivery failures; TelegramTaskConsumer logs task start, execution errors with full context, and completion details (result length, error summary).
Draft message streaming diagnostics
TelegramSearchBot/Service/BotAPI/SendMessageService.Streaming.cs
Adds CreateDraftId and GetContentPreview helpers; enriches SendDraftStream with startup debug logs, fallback/update/sync warnings including truncated previews and lengths, and markdown-collapse detection.
Program/Bootstrap/Controllers logging
TelegramSearchBot/Program.cs, AppBootstrap/AppBootstrap.cs, TelegramSearchBot/Controller/AI/LLM/*
Applies Env.SerilogMinimumLevel to Serilog sinks and OpenTelemetry; AppBootstrap uses structured Serilog Error overloads; controllers now emit structured error logs when agent processing yields AgentChunkType.Error.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

"I nibble logs with tidy care,
I unwrap errors, make them fair.
Summaries short and bright,
Debugging hops into light. 🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.19% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add LLM tool call diagnostics' directly and clearly summarizes the main change of adding detailed logging and diagnostics throughout the LLM tool execution pipeline.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ 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 codex/add-llm-trace-logging-and-fix-empty-encoded-value

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 3

🧹 Nitpick comments (2)
TelegramSearchBot/Service/AI/LLM/ChunkPollingService.cs (1)

75-79: 💤 Low value

Consider adding context to loop-level exception logs.

The exception logs at lines 76 and 78 could benefit from additional context similar to the per-task logs (lines 58, 60). Consider logging the count of tracked tasks or other diagnostic state to aid troubleshooting when the entire polling loop encounters errors.

💡 Example enhancement
 } catch (RedisException ex) {
-    _logger.LogWarning(ex, "Redis error in ChunkPollingService poll loop, retrying after delay");
+    _logger.LogWarning(ex, "Redis error in ChunkPollingService poll loop, retrying after delay. TrackedTaskCount={TrackedTaskCount}", _trackedTasks.Count);
 } catch (Exception ex) {
-    _logger.LogError(ex, "Unexpected error in ChunkPollingService poll loop");
+    _logger.LogError(ex, "Unexpected error in ChunkPollingService poll loop. TrackedTaskCount={TrackedTaskCount}", _trackedTasks.Count);
 }
🤖 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 `@TelegramSearchBot/Service/AI/LLM/ChunkPollingService.cs` around lines 75 -
79, The loop-level exception handlers in ChunkPollingService currently log
exceptions without diagnostic state; update the catch blocks around the poll
loop to include relevant context (e.g., the current number of tracked tasks and
any identifying loop state) in the _logger.LogWarning and _logger.LogError calls
— for example include _trackedTasks.Count (or the method/property that exposes
tracked tasks) and any loop iteration id or polling interval variable so the log
messages mirror the richer per-task logs and aid debugging.
TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs (1)

725-729: 💤 Low value

Consider conditional CDATA stripping for performance.

The regex is applied to every argument value unconditionally. If CDATA markers are rare, consider adding a pre-check to avoid regex overhead:

 foreach (var kvp in originalArguments) {
     var value = kvp.Value;
-    value = Regex.Replace(value ?? string.Empty, @"<!\[CDATA\[(.*?)\]\]>", "$1").Trim();
+    if (!string.IsNullOrEmpty(value) && value.Contains("CDATA")) {
+        value = Regex.Replace(value, @"<!\[CDATA\[(.*?)\]\]>", "$1");
+    }
+    value = (value ?? string.Empty).Trim();
     cleanedArguments[kvp.Key] = value;
 }
🤖 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 `@TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs` around lines 725 -
729, The loop over originalArguments unconditionally runs Regex.Replace on every
value which is wasteful; change the logic in the foreach (var kvp in
originalArguments) block that sets cleanedArguments[kvp.Key] so you first
null-coalesce and Trim the value, then only call Regex.Replace (the current
Regex.Replace(value ?? string.Empty, @"<!\[CDATA\[(.*?)\]\]>", "$1").Trim())
when the string contains the CDATA marker (e.g. value.Contains("<![CDATA[")),
otherwise skip the regex and assign the trimmed string directly; keep references
to originalArguments, cleanedArguments and Regex.Replace so the change is
localized to that loop.
🤖 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 `@TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs`:
- Around line 1200-1219: When parsing function-call and function-output blocks,
NormalizeToolCallId is called independently which lets malformed snapshots with
empty callId generate different IDs; fix by tracking a lastResolvedCallId in the
surrounding iteration scope and use it when parts[0] is empty: for the func call
branch compute rawCallId = parts.Length > 0 ? parts[0] : "" then resolved =
OpenAIService.NormalizeToolCallId(rawCallId) and set lastResolvedCallId =
resolved (and use that in CreateFunctionCallItem), and in the func output branch
compute rawCallId = parts.Length > 0 ? parts[0] : "" and if rawCallId is empty
use lastResolvedCallId before calling OpenAIService.NormalizeToolCallId (or
reuse lastResolvedCallId directly) so CreateFunctionCallOutputItem receives the
same callId; reference NormalizeToolCallId, CreateFunctionCallItem,
CreateFunctionCallOutputItem, FuncCallMarker, FuncOutputMarker, payload, parts
and msg.Role when making the change.

In `@TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs`:
- Around line 1108-1121: The LogError is serializing full toolCallAccumulators
including raw Arguments which may contain user secrets or large payloads;
replace the direct JsonConvert.SerializeObject(...) with a sanitized/truncated
preview generator: map each kvp to { kvp.Value.Id, kvp.Value.Name,
ArgumentsPreview = SanitizeAndTruncate(kvp.Value.Arguments?.ToString(),
maxChars) } and pass that preview JSON to _logger.LogError instead; implement a
small helper method SanitizeAndTruncateArguments(string, int) (or reuse an
existing sanitizer) to remove sensitive patterns and limit length, and apply the
same change for other LogError occurrences that serialize toolCallAccumulators
(e.g., the similar blocks named around _logger.LogError and references to
toolCallAccumulators).

In `@TelegramSearchBot/Program.cs`:
- Line 24: Add a configurable log level: add a LogLevel property (enum/string)
to the Config class in TelegramSearchBot.Common/Env.cs with default
"Information" (allowing "Verbose" for diagnostics), load it from Config.json,
and expose a parsed Serilog LogEventLevel; then update Program.cs where
MinimumLevel.Verbose() is used to apply the configured level (e.g.,
MinimumLevel.Is(config.LogEventLevel)) instead of hardcoding Verbose, and set
the OpenTelemetry sink call to include restrictedToMinimumLevel using the same
configured LogEventLevel (e.g., restrictedToMinimumLevel: config.LogEventLevel)
so sinks honor the runtime configuration.

---

Nitpick comments:
In `@TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs`:
- Around line 725-729: The loop over originalArguments unconditionally runs
Regex.Replace on every value which is wasteful; change the logic in the foreach
(var kvp in originalArguments) block that sets cleanedArguments[kvp.Key] so you
first null-coalesce and Trim the value, then only call Regex.Replace (the
current Regex.Replace(value ?? string.Empty, @"<!\[CDATA\[(.*?)\]\]>",
"$1").Trim()) when the string contains the CDATA marker (e.g.
value.Contains("<![CDATA[")), otherwise skip the regex and assign the trimmed
string directly; keep references to originalArguments, cleanedArguments and
Regex.Replace so the change is localized to that loop.

In `@TelegramSearchBot/Service/AI/LLM/ChunkPollingService.cs`:
- Around line 75-79: The loop-level exception handlers in ChunkPollingService
currently log exceptions without diagnostic state; update the catch blocks
around the poll loop to include relevant context (e.g., the current number of
tracked tasks and any identifying loop state) in the _logger.LogWarning and
_logger.LogError calls — for example include _trackedTasks.Count (or the
method/property that exposes tracked tasks) and any loop iteration id or polling
interval variable so the log messages mirror the richer per-task logs and aid
debugging.
🪄 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

Run ID: 56aff00d-706c-405c-ad72-888b8d5f4dcc

📥 Commits

Reviewing files that changed from the base of the PR and between 715f2ed and bb3d9bf.

📒 Files selected for processing (17)
  • TelegramSearchBot.Common/ExceptionExtensions.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/AnthropicService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OllamaService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs
  • TelegramSearchBot.LLMAgent/LLMAgentProgram.cs
  • TelegramSearchBot.LLMAgent/Service/AgentLoopService.cs
  • TelegramSearchBot.LLMAgent/Service/ToolExecutor.cs
  • TelegramSearchBot/AppBootstrap/AppBootstrap.cs
  • TelegramSearchBot/Controller/AI/LLM/GeneralLLMController.cs
  • TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs
  • TelegramSearchBot/Program.cs
  • TelegramSearchBot/Service/AI/LLM/ChunkPollingService.cs
  • TelegramSearchBot/Service/AI/LLM/TelegramTaskConsumer.cs
  • TelegramSearchBot/Service/BotAPI/SendMessageService.Streaming.cs

Comment thread TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs
Comment thread TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs
Comment thread TelegramSearchBot/Program.cs Outdated
@github-actions

github-actions Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

PR Check Report

Summary

Test Results

Platform Status Details
Ubuntu Passed Tests passed, artifacts uploaded
Windows Passed Tests passed, artifacts uploaded

Code Quality

  • Code formatting check
  • Security vulnerability scan
  • Dependency analysis
  • Code coverage collection

Test Artifacts

  • Test results artifacts count: 2
  • Code coverage uploaded to Codecov

Links


This report is auto-generated by GitHub Actions

@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 `@TelegramSearchBot.Common/Env.cs`:
- Around line 160-166: ResolveSerilogMinimumLevel currently parses numeric
strings like "999" as enums; update the method to only accept defined
LogEventLevel values by, after Enum.TryParse<LogEventLevel>(...), verifying
Enum.IsDefined(typeof(LogEventLevel), parsed) before returning parsed; if
IsDefined is false, fall back to LogEventLevel.Verbose so only valid enum
members are accepted.
🪄 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

Run ID: a2d9f235-10f2-4c42-9ce2-b948cca78956

📥 Commits

Reviewing files that changed from the base of the PR and between bb3d9bf and a07cb9d.

📒 Files selected for processing (8)
  • Docs/Build_and_Test_Guide.md
  • README.md
  • TelegramSearchBot.Common/Env.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs
  • TelegramSearchBot/Program.cs
  • TelegramSearchBot/Service/AI/LLM/ChunkPollingService.cs
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs
  • TelegramSearchBot/Service/AI/LLM/ChunkPollingService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs

Comment thread TelegramSearchBot.Common/Env.cs
@ModerRAS ModerRAS merged commit 26de663 into master May 13, 2026
5 checks passed
@ModerRAS ModerRAS deleted the codex/add-llm-trace-logging-and-fix-empty-encoded-value branch May 13, 2026 06:29
@coderabbitai coderabbitai Bot mentioned this pull request May 31, 2026
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.

1 participant