Skip to content

refactor(llm): separate identity and provider resolution#350

Merged
ModerRAS merged 2 commits into
masterfrom
codex/issue-293-llm-boundaries
May 13, 2026
Merged

refactor(llm): separate identity and provider resolution#350
ModerRAS merged 2 commits into
masterfrom
codex/issue-293-llm-boundaries

Conversation

@ModerRAS
Copy link
Copy Markdown
Owner

@ModerRAS ModerRAS commented May 12, 2026

Summary

This PR implements the first architecture slice from #293: removing hidden provider-coupled state from the LLM entry path and making provider resolution per-operation instead of cached inside a singleton factory.

Changes:

  • Introduce IBotIdentityProvider / BotIdentityProvider so bot identity is no longer stored through OpenAIService.BotName as shared provider state.
  • Introduce IGroupLlmSettingsService / GroupLlmSettingsService so group model get/set no longer depends on OpenAIService.
  • Update GeneralLLMController to depend on the identity/settings abstractions instead of injecting OpenAIService as a state holder.
  • Change LLMFactory to resolve provider services from DI for each call, instead of caching provider instances in the factory.
  • Remove the old concrete provider fields from GeneralLLMService.
  • Update OpenAI/Ollama/Gemini/Anthropic/Responses providers to read bot identity through IBotIdentityProvider, while keeping an instance-level BotName compatibility fallback for direct construction/tests.
  • Update the LLMAgent process to register the new services and apply bot identity through the provider abstraction.
  • Adjust affected unit tests for the new factory/controller boundaries.

Notes

This intentionally keeps the PR bounded. It does not yet introduce a full LlmExecutionRequest object or split all channel/model selection rules out of GeneralLLMService; those are better as follow-up slices after this state-boundary cleanup lands.

Validation

  • dotnet build TelegramSearchBot.sln --configuration Release --no-restore
  • dotnet test TelegramSearchBot.LLM.Test\TelegramSearchBot.LLM.Test.csproj --configuration Release --no-build — 202 passed
  • dotnet test TelegramSearchBot.Test\TelegramSearchBot.Test.csproj --configuration Release --no-build --filter "FullyQualifiedName~EditLLMConf|FullyQualifiedName~Agent" — 30 passed

Part of #293

Summary by CodeRabbit

  • New Features

    • Per-group LLM model configuration: set/get model per chat.
    • Persistent bot identity: unified bot name/userid handling across services.
  • Refactor

    • Simplified LLM provider routing and DI usage; services now resolve identities/settings via shared providers.
    • Controller now initializes and relies on centralized identity and group-model services.
  • Bug Fixes

    • Controller skips empty messages and respects enable flags.
  • Tests

    • Test suite updated to reflect DI and service-registration changes.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

This PR adds IBotIdentityProvider and IGroupLlmSettingsService contracts and implementations, refactors LLMFactory to resolve providers via IServiceProvider, updates provider services to use injected bot identity and group settings, simplifies GeneralLLMService, wires services into controller and agent layers, and updates tests for DI-based wiring.

Changes

Bot Identity and Group Settings Refactoring

Layer / File(s) Summary
Contracts: Identity & Group Settings
TelegramSearchBot.LLM/Interface/AI/LLM/IBotIdentityProvider.cs, TelegramSearchBot.LLM/Interface/AI/LLM/IGroupLlmSettingsService.cs
Adds IBotIdentityProvider with GetIdentityAsync/SetIdentity and BotIdentity record; adds IGroupLlmSettingsService with GetModelAsync and SetModelAsync signatures.
Implementations: BotIdentityProvider & GroupLlmSettingsService
TelegramSearchBot.LLM/Service/AI/LLM/BotIdentityProvider.cs, TelegramSearchBot.LLM/Service/AI/LLM/GroupLlmSettingsService.cs
Adds singleton BotIdentityProvider with thread-safe state and scoped GroupLlmSettingsService that normalizes, validates, and upserts group model names via EF Core.
LLMFactory: lazy DI resolution
TelegramSearchBot.LLM/Service/AI/LLM/LLMFactory.cs
Refactors factory to Transient, accept IServiceProvider, and resolve provider-specific ILLMService instances on demand via a provider switch.
Provider services: Bot identity integration
TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs, .../OllamaService.cs, .../GeminiService.cs, .../AnthropicService.cs, .../OpenAIResponsesService.cs
Update services to accept IBotIdentityProvider (and IGroupLlmSettingsService where applicable); BotName now resolves via the provider (with fallback), and system prompts use asynchronously resolved bot name.
GeneralLLMService simplification
TelegramSearchBot.LLM/Service/AI/LLM/GeneralLLMService.cs
Removes direct provider service fields from constructor; delegates provider routing to ILLMFactory.
Controller: identity & settings integration
TelegramSearchBot/Controller/AI/LLM/GeneralLLMController.cs
Injects IBotIdentityProvider and IGroupLlmSettingsService; initializes bot identity (with botClient.GetMe() fallback), uses identity for mention/command checks, routes model set/get through group settings service, and standardizes telegramMessage usage for chat/message IDs.
Agent & DI registrations
TelegramSearchBot.LLMAgent/LLMAgentProgram.cs, TelegramSearchBot.LLMAgent/Service/LlmServiceProxy.cs
Registers IBotIdentityProvider (singleton) and IGroupLlmSettingsService (scoped); LlmServiceProxy applies identity via IBotIdentityProvider instead of per-service BotName writes.
Test updates for DI refactor
TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs, TelegramSearchBot.LLM.Test/Service/AI/LLM/LLMFactoryTests.cs
Adjust tests for changed constructors and DI-based LLMFactory; LLMFactoryTests now registers mocks in a ServiceCollection and builds a ServiceProvider.

Sequence Diagram

sequenceDiagram
  participant Controller as GeneralLLMController
  participant BotProvider as IBotIdentityProvider
  participant GroupSettings as IGroupLlmSettingsService
  participant Factory as ILLMFactory
  participant Provider as OpenAIService

  Controller->>BotProvider: GetIdentityAsync()
  BotProvider-->>Controller: BotIdentity
  Controller->>GroupSettings: GetModelAsync(chatId)
  GroupSettings-->>Controller: modelName
  Controller->>Factory: GetLLMService(provider)
  Factory->>Provider: GetRequiredService<OpenAIService>()
  Provider-->>Factory: instance
  Factory-->>Controller: ILLMService
  Controller->>Provider: ExecAsync(...)
  Provider->>BotProvider: GetIdentityAsync()
  BotProvider-->>Provider: BotIdentity
  Provider-->>Controller: response
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • feat: LLM 模块重构计划 #293: The PR's introduction of IBotIdentityProvider and DI-based factory/provider refactor aligns with the planned LLM-module refactor described in the issue.

Possibly related PRs

Poem

🐰 I hop through code with gentle pride,
I bind each bot to name and guide,
Groups pick models, factories defer,
Identity flows, no static blur,
A rabbit cheers — the DI tide!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.00% 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 clearly and concisely describes the main architectural change: separating bot identity and LLM provider resolution into distinct abstractions instead of coupling them to OpenAIService.
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/issue-293-llm-boundaries

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

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

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

🧹 Nitpick comments (7)
TelegramSearchBot.LLM/Service/AI/LLM/BotIdentityProvider.cs (1)

16-20: 💤 Low value

Async-over-sync pattern: consider synchronous GetIdentity method.

GetIdentityAsync uses Task.FromResult inside a lock, which is synchronous work wrapped in an async signature. Since the operation is purely in-memory and doesn't perform I/O, consider either:

  1. Providing a synchronous GetIdentity() method alongside the async version
  2. Documenting why the async signature is needed (e.g., for future database-backed implementations)

The current implementation is correct but may cause unnecessary async state machine overhead for callers.

🤖 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/BotIdentityProvider.cs` around lines 16
- 20, GetIdentityAsync currently wraps a synchronous, in-memory read (locking
_lock and returning Task.FromResult(_identity)), which causes unnecessary async
overhead; add a synchronous getter GetIdentity() that acquires the same _lock
and returns BotIdentity directly (to be used by callers that don't need async),
and either keep GetIdentityAsync as a simple wrapper that calls
Task.FromResult(GetIdentity()) for compatibility or add a comment on why the
async signature is required for future async implementations; reference the
existing GetIdentityAsync method, the _lock field and the _identity field when
making the change.
TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs (2)

1614-1642: 💤 Low value

Code duplication: fallback logic mirrors GroupLlmSettingsService.

Lines 1620-1630 (SetModel fallback) and lines 1637-1641 (GetModel fallback) duplicate the logic implemented in GroupLlmSettingsService. While this duplication is intentional for backward compatibility, it creates a maintenance burden where changes to the persistence logic need to be made in two places.

Consider:

  1. Adding a comment documenting that this is fallback-only and should match GroupLlmSettingsService behavior
  2. In a future iteration, removing the fallback once all deployments have migrated to using the service

This is acceptable as-is for the transitional architecture but should be noted for future cleanup.

🤖 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/OpenAIService.cs` around lines 1614 -
1642, The SetModel and GetModel methods contain fallback persistence logic that
duplicates GroupLlmSettingsService behavior; add a clear comment inside both
SetModel and GetModel indicating these blocks are fallback-only, must mirror
GroupLlmSettingsService, and include a TODO to remove once all deployments use
GroupLlmSettingsService, so future maintainers know why the duplication exists
and to keep behavior in sync with GroupLlmSettingsService's
SetModelAsync/GetModelAsync implementations.

108-117: ⚡ Quick win

Sync-over-async anti-pattern in BotName property getter.

Line 109 uses GetAwaiter().GetResult() which is a sync-over-async anti-pattern that can cause deadlocks in certain contexts (though less likely in this worker service architecture). This appears necessary for backward compatibility where BotName is accessed as a property.

Consider:

  1. Marking this property as [Obsolete] and migrating callers to GetBotNameAsync()
  2. Documenting that this property should only be used in non-async contexts
  3. Ensuring no callers use this property from an async context where deadlock could occur

The async methods at lines 1000 and 1183 correctly use await GetBotNameAsync(), which is the preferred pattern.

🤖 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/OpenAIService.cs` around lines 108 -
117, The BotName property currently does sync-over-async via
GetBotNameAsync().GetAwaiter().GetResult() which risks deadlocks; mark the
BotName property as [Obsolete("Use GetBotNameAsync() instead")] and update any
callers to call GetBotNameAsync() (await it) instead; if you must keep a
synchronous fallback for backward compatibility, change the getter to call
GetBotNameAsync().ConfigureAwait(false).GetAwaiter().GetResult() and keep the
setter behavior that uses _botIdentityProvider.SetIdentity(Env.BotId, value) or
_fallbackBotName to preserve existing semantics.
TelegramSearchBot.LLMAgent/LLMAgentProgram.cs (1)

105-106: ⚡ Quick win

Service lifetime registration is consistent with other services.

Both registrations look appropriate for the LLMAgent's service lifetime needs. The singleton IBotIdentityProvider ensures shared identity state, and scoped IGroupLlmSettingsService aligns with the scoped DataDbContext registration (line 100-102).

Note: GroupLlmSettingsService has [Injectable(ServiceLifetime.Transient)] but is registered as Scoped here. While this works (manual registration takes precedence), consider updating the attribute to Scoped for consistency, or document why the lifetimes differ between the main app and the agent.

🤖 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.LLMAgent/LLMAgentProgram.cs` around lines 105 - 106, The
registration lifetime for GroupLlmSettingsService differs from its Injectable
attribute: IGroupLlmSettingsService is registered as Scoped in the DI setup
while GroupLlmSettingsService is annotated with
[Injectable(ServiceLifetime.Transient)]; reconcile this by either changing the
attribute on GroupLlmSettingsService to ServiceLifetime.Scoped, or changing the
registration call for IGroupLlmSettingsService to AddTransient, and add a short
comment explaining the intentional mismatch if you choose to keep differing
lifetimes; reference the symbols IGroupLlmSettingsService,
GroupLlmSettingsService, IBotIdentityProvider, BotIdentityProvider, and
DataDbContext when making the change.
TelegramSearchBot.LLM/Interface/AI/LLM/IGroupLlmSettingsService.cs (1)

6-6: ⚡ Quick win

Consider nullable annotation for GetModelAsync return type.

The AI summary indicates that GetModelAsync returns null when no record exists, but the return type Task<string> doesn't indicate nullability. With nullable reference types enabled, this should be Task<string?> to accurately reflect the contract and prevent null-reference warnings at call sites.

♻️ Proposed fix
-        Task<string> GetModelAsync(long chatId, CancellationToken cancellationToken = default);
+        Task<string?> GetModelAsync(long chatId, CancellationToken cancellationToken = default);
🤖 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/Interface/AI/LLM/IGroupLlmSettingsService.cs` at line
6, The GetModelAsync method on IGroupLlmSettingsService currently returns
Task<string> but can return null; change its signature to Task<string?>
(nullable reference) and update all implementing classes/methods that implement
GetModelAsync to return Task<string?> as well, then review and adjust callers to
handle a possible null (add null checks, default values, or annotate with
null-forgiving only where safe) so the contract matches actual behavior and
nullable warnings are resolved.
TelegramSearchBot.LLM/Service/AI/LLM/GroupLlmSettingsService.cs (1)

33-37: ⚖️ Poor tradeoff

Consider potential race condition on concurrent inserts.

If multiple requests simultaneously call SetModelAsync for a new chatId, both might see settings == null (line 33) and attempt to insert, causing a database duplicate key exception. While this is a narrow window, consider either:

  1. Using ExecuteUpdate for atomic upsert (EF 7+)
  2. Handling the DbUpdateException for duplicate key
  3. Documenting that the service should be called from a synchronized context

Given the Transient lifetime and likely single-threaded request handling per chat, this may be acceptable as-is.

🤖 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/GroupLlmSettingsService.cs` around lines
33 - 37, SetModelAsync currently checks if settings is null and then calls
_dbContext.GroupSettings.AddAsync(new GroupSettings { GroupId = chatId,
LLMModelName = modelName }, ...) which can race on concurrent inserts for the
same chatId; modify SetModelAsync to perform an atomic upsert or to handle
duplicate-key exceptions: either replace the insert path with an EF Core
ExecuteUpdate/Upsert call (if on EF7+) targeting GroupSettings to set
LLMModelName for the given GroupId, or wrap the AddAsync/SaveChangesAsync in a
try/catch for DbUpdateException and on duplicate-key error re-query the
GroupSettings row and update its LLMModelName then SaveChangesAsync; reference
the settings variable, GroupSettings entity, _dbContext.GroupSettings.AddAsync,
and SetModelAsync when making the change.
TelegramSearchBot.LLMAgent/Service/LlmServiceProxy.cs (1)

66-73: 💤 Low value

Consider logging when IBotIdentityProvider is unavailable.

The fallback to Env.BotId (line 71) ensures backward compatibility when IBotIdentityProvider isn't registered. However, consider adding a debug or warning log when the fallback is used, to help diagnose configuration issues where the provider is unexpectedly missing.

📝 Optional logging addition
 private void ApplyBotIdentity(string botName, long botUserId) {
     var identityProvider = _serviceProvider.GetService<IBotIdentityProvider>();
     if (identityProvider != null) {
         identityProvider.SetIdentity(botUserId, botName);
     } else {
+        _logger.LogDebug("IBotIdentityProvider not available, falling back to Env.BotId");
         Env.BotId = botUserId;
     }
 }
🤖 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.LLMAgent/Service/LlmServiceProxy.cs` around lines 66 - 73,
The ApplyBotIdentity method silently falls back to Env.BotId when
IBotIdentityProvider is missing; add a diagnostic log in that branch: resolve an
ILogger<LlmServiceProxy> (or the existing logger instance) from _serviceProvider
inside ApplyBotIdentity and emit a warning or debug message indicating
IBotIdentityProvider was not registered and the code is using Env.BotId (include
botName and botUserId in the message for context). Ensure logging is only done
when identityProvider == null and do not change the existing fallback assignment
to Env.BotId.
🤖 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/GroupLlmSettingsService.cs`:
- Line 28: SetModelAsync currently accepts modelName without validation which
can persist invalid settings; add a guard at the start of SetModelAsync (in
GroupLlmSettingsService) that checks if string.IsNullOrWhiteSpace(modelName) and
if so throws an ArgumentException/ArgumentNullException (including parameter
name modelName), or optionally return without persisting; also trim modelName
before use so you never store leading/trailing whitespace and perform this check
before any database calls that use chatId and modelName.

In `@TelegramSearchBot.LLM/Service/AI/LLM/LLMFactory.cs`:
- Line 11: Change the service lifetime on the LLMFactory registration: replace
the [Injectable(ServiceLifetime.Transient)] attribute on the LLMFactory class
with [Injectable(ServiceLifetime.Singleton)] so it follows the Scrutor DI
scanning rule for TelegramSearchBot namespace services; locate the Injectable
attribute applied to the LLMFactory class and update its ServiceLifetime
argument from Transient to Singleton.

In `@TelegramSearchBot/Controller/AI/LLM/GeneralLLMController.cs`:
- Around line 73-78: The code assumes botIdentity from
_botIdentityProvider.GetIdentityAsync() is non-null before checking UserName;
change the guard to first check for null (e.g., if (botIdentity == null ||
string.IsNullOrEmpty(botIdentity.UserName))) so the fallback to call
botClient.GetMe(), _botIdentityProvider.SetIdentity(me.Id, me.Username) and
create new BotIdentity(me.Id, me.Username ?? string.Empty) runs safely when
GetIdentityAsync() returns null or has an empty UserName; update references to
botIdentity accordingly so no dereference occurs before the null check.
- Around line 102-106: The code currently extracts the model name via
Message.Substring(5) without trimming or validating, so update the
Message.StartsWith("设置模型 ") handler to Trim() the substring (e.g. var model =
Message.Substring(5).Trim()), verify model is not null/empty/whitespace, and if
invalid send a user-friendly rejection via SendMessageService.SendMessage and
return without calling _groupLlmSettingsService.SetModelAsync; otherwise pass
the trimmed model into SetModelAsync, and use the trimmed value in
logger.LogInformation and the success SendMessageService.SendMessage call so no
blank model can be persisted.

---

Nitpick comments:
In `@TelegramSearchBot.LLM/Interface/AI/LLM/IGroupLlmSettingsService.cs`:
- Line 6: The GetModelAsync method on IGroupLlmSettingsService currently returns
Task<string> but can return null; change its signature to Task<string?>
(nullable reference) and update all implementing classes/methods that implement
GetModelAsync to return Task<string?> as well, then review and adjust callers to
handle a possible null (add null checks, default values, or annotate with
null-forgiving only where safe) so the contract matches actual behavior and
nullable warnings are resolved.

In `@TelegramSearchBot.LLM/Service/AI/LLM/BotIdentityProvider.cs`:
- Around line 16-20: GetIdentityAsync currently wraps a synchronous, in-memory
read (locking _lock and returning Task.FromResult(_identity)), which causes
unnecessary async overhead; add a synchronous getter GetIdentity() that acquires
the same _lock and returns BotIdentity directly (to be used by callers that
don't need async), and either keep GetIdentityAsync as a simple wrapper that
calls Task.FromResult(GetIdentity()) for compatibility or add a comment on why
the async signature is required for future async implementations; reference the
existing GetIdentityAsync method, the _lock field and the _identity field when
making the change.

In `@TelegramSearchBot.LLM/Service/AI/LLM/GroupLlmSettingsService.cs`:
- Around line 33-37: SetModelAsync currently checks if settings is null and then
calls _dbContext.GroupSettings.AddAsync(new GroupSettings { GroupId = chatId,
LLMModelName = modelName }, ...) which can race on concurrent inserts for the
same chatId; modify SetModelAsync to perform an atomic upsert or to handle
duplicate-key exceptions: either replace the insert path with an EF Core
ExecuteUpdate/Upsert call (if on EF7+) targeting GroupSettings to set
LLMModelName for the given GroupId, or wrap the AddAsync/SaveChangesAsync in a
try/catch for DbUpdateException and on duplicate-key error re-query the
GroupSettings row and update its LLMModelName then SaveChangesAsync; reference
the settings variable, GroupSettings entity, _dbContext.GroupSettings.AddAsync,
and SetModelAsync when making the change.

In `@TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs`:
- Around line 1614-1642: The SetModel and GetModel methods contain fallback
persistence logic that duplicates GroupLlmSettingsService behavior; add a clear
comment inside both SetModel and GetModel indicating these blocks are
fallback-only, must mirror GroupLlmSettingsService, and include a TODO to remove
once all deployments use GroupLlmSettingsService, so future maintainers know why
the duplication exists and to keep behavior in sync with
GroupLlmSettingsService's SetModelAsync/GetModelAsync implementations.
- Around line 108-117: The BotName property currently does sync-over-async via
GetBotNameAsync().GetAwaiter().GetResult() which risks deadlocks; mark the
BotName property as [Obsolete("Use GetBotNameAsync() instead")] and update any
callers to call GetBotNameAsync() (await it) instead; if you must keep a
synchronous fallback for backward compatibility, change the getter to call
GetBotNameAsync().ConfigureAwait(false).GetAwaiter().GetResult() and keep the
setter behavior that uses _botIdentityProvider.SetIdentity(Env.BotId, value) or
_fallbackBotName to preserve existing semantics.

In `@TelegramSearchBot.LLMAgent/LLMAgentProgram.cs`:
- Around line 105-106: The registration lifetime for GroupLlmSettingsService
differs from its Injectable attribute: IGroupLlmSettingsService is registered as
Scoped in the DI setup while GroupLlmSettingsService is annotated with
[Injectable(ServiceLifetime.Transient)]; reconcile this by either changing the
attribute on GroupLlmSettingsService to ServiceLifetime.Scoped, or changing the
registration call for IGroupLlmSettingsService to AddTransient, and add a short
comment explaining the intentional mismatch if you choose to keep differing
lifetimes; reference the symbols IGroupLlmSettingsService,
GroupLlmSettingsService, IBotIdentityProvider, BotIdentityProvider, and
DataDbContext when making the change.

In `@TelegramSearchBot.LLMAgent/Service/LlmServiceProxy.cs`:
- Around line 66-73: The ApplyBotIdentity method silently falls back to
Env.BotId when IBotIdentityProvider is missing; add a diagnostic log in that
branch: resolve an ILogger<LlmServiceProxy> (or the existing logger instance)
from _serviceProvider inside ApplyBotIdentity and emit a warning or debug
message indicating IBotIdentityProvider was not registered and the code is using
Env.BotId (include botName and botUserId in the message for context). Ensure
logging is only done when identityProvider == null and do not change the
existing fallback assignment to Env.BotId.
🪄 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: 687c6757-da01-4254-8f44-de06cbba4bdc

📥 Commits

Reviewing files that changed from the base of the PR and between d109c1f and f169054.

📒 Files selected for processing (16)
  • TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs
  • TelegramSearchBot.LLM.Test/Service/AI/LLM/LLMFactoryTests.cs
  • TelegramSearchBot.LLM/Interface/AI/LLM/IBotIdentityProvider.cs
  • TelegramSearchBot.LLM/Interface/AI/LLM/IGroupLlmSettingsService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/AnthropicService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/BotIdentityProvider.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GeneralLLMService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GroupLlmSettingsService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/LLMFactory.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/LlmServiceProxy.cs
  • TelegramSearchBot/Controller/AI/LLM/GeneralLLMController.cs
💤 Files with no reviewable changes (2)
  • TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GeneralLLMService.cs

Comment thread TelegramSearchBot.LLM/Service/AI/LLM/GroupLlmSettingsService.cs

namespace TelegramSearchBot.Service.AI.LLM {
[Injectable(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton)]
[Injectable(ServiceLifetime.Transient)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use singleton lifetime for Scrutor-scanned service registration.

Line 11 switches LLMFactory to transient, which conflicts with the project DI rule.

Proposed change
-[Injectable(ServiceLifetime.Transient)]
+[Injectable(ServiceLifetime.Singleton)]

As per coding guidelines, "Use Scrutor DI scanning with [Injectable(ServiceLifetime.Singleton)] attribute for service registration in namespaces under TelegramSearchBot".

🤖 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/LLMFactory.cs` at line 11, Change the
service lifetime on the LLMFactory registration: replace the
[Injectable(ServiceLifetime.Transient)] attribute on the LLMFactory class with
[Injectable(ServiceLifetime.Singleton)] so it follows the Scrutor DI scanning
rule for TelegramSearchBot namespace services; locate the Injectable attribute
applied to the LLMFactory class and update its ServiceLifetime argument from
Transient to Singleton.

Comment on lines +73 to 78
var botIdentity = await _botIdentityProvider.GetIdentityAsync();
if (string.IsNullOrEmpty(botIdentity.UserName)) {
var me = await botClient.GetMe();
Env.BotId = me.Id;
service.BotName = me.Username; // service.BotName is the username, e.g., "MyBot"
_botIdentityProvider.SetIdentity(me.Id, me.Username);
botIdentity = new BotIdentity(me.Id, me.Username ?? string.Empty);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against null identity before dereferencing.

Line 74 assumes GetIdentityAsync() never returns null. If it does, this path throws before fallback initialization.

Proposed change
- var botIdentity = await _botIdentityProvider.GetIdentityAsync();
- if (string.IsNullOrEmpty(botIdentity.UserName)) {
+ var botIdentity = await _botIdentityProvider.GetIdentityAsync() ?? new BotIdentity(0, string.Empty);
+ if (string.IsNullOrEmpty(botIdentity.UserName) || botIdentity.UserId == 0) {
     var me = await botClient.GetMe();
     _botIdentityProvider.SetIdentity(me.Id, me.Username);
     botIdentity = new BotIdentity(me.Id, me.Username ?? string.Empty);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var botIdentity = await _botIdentityProvider.GetIdentityAsync();
if (string.IsNullOrEmpty(botIdentity.UserName)) {
var me = await botClient.GetMe();
Env.BotId = me.Id;
service.BotName = me.Username; // service.BotName is the username, e.g., "MyBot"
_botIdentityProvider.SetIdentity(me.Id, me.Username);
botIdentity = new BotIdentity(me.Id, me.Username ?? string.Empty);
}
var botIdentity = await _botIdentityProvider.GetIdentityAsync() ?? new BotIdentity(0, string.Empty);
if (string.IsNullOrEmpty(botIdentity.UserName) || botIdentity.UserId == 0) {
var me = await botClient.GetMe();
_botIdentityProvider.SetIdentity(me.Id, me.Username);
botIdentity = new BotIdentity(me.Id, me.Username ?? string.Empty);
}
🤖 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/Controller/AI/LLM/GeneralLLMController.cs` around lines 73
- 78, The code assumes botIdentity from _botIdentityProvider.GetIdentityAsync()
is non-null before checking UserName; change the guard to first check for null
(e.g., if (botIdentity == null || string.IsNullOrEmpty(botIdentity.UserName)))
so the fallback to call botClient.GetMe(),
_botIdentityProvider.SetIdentity(me.Id, me.Username) and create new
BotIdentity(me.Id, me.Username ?? string.Empty) runs safely when
GetIdentityAsync() returns null or has an empty UserName; update references to
botIdentity accordingly so no dereference occurs before the null check.

Comment thread TelegramSearchBot/Controller/AI/LLM/GeneralLLMController.cs
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.

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

25-29: 💤 Low value

Consider using null-conditional operator for conciseness.

Lines 28 and 40 use ternary expressions that could be simplified using the null-conditional operator.

♻️ Suggested simplification
 public async Task<string?> GetModelAsync(long chatId, CancellationToken cancellationToken = default) {
     var settings = await _dbContext.GroupSettings.AsNoTracking()
         .FirstOrDefaultAsync(s => s.GroupId == chatId, cancellationToken);
-    return settings == null ? null : settings.LLMModelName;
+    return settings?.LLMModelName;
 }
-    var previous = settings == null ? null : settings.LLMModelName;
+    var previous = settings?.LLMModelName;

Also applies to: 38-40

🤖 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/GroupLlmSettingsService.cs` around lines
25 - 29, Replace the ternary null checks with null-conditional access to
simplify returns: in GetModelAsync, change "return settings == null ? null :
settings.LLMModelName;" to "return settings?.LLMModelName;". Do the same for the
other similar method(s) that fetch GroupSettings (e.g., the method that returns
SystemMessage) by returning "settings?.SystemMessage" (or the appropriate
property) instead of the ternary expression; keep the existing await
_dbContext.GroupSettings.AsNoTracking().FirstOrDefaultAsync(...) calls
unchanged.
🤖 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.

Nitpick comments:
In `@TelegramSearchBot.LLM/Service/AI/LLM/GroupLlmSettingsService.cs`:
- Around line 25-29: Replace the ternary null checks with null-conditional
access to simplify returns: in GetModelAsync, change "return settings == null ?
null : settings.LLMModelName;" to "return settings?.LLMModelName;". Do the same
for the other similar method(s) that fetch GroupSettings (e.g., the method that
returns SystemMessage) by returning "settings?.SystemMessage" (or the
appropriate property) instead of the ternary expression; keep the existing await
_dbContext.GroupSettings.AsNoTracking().FirstOrDefaultAsync(...) calls
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9d95602-2cb7-4299-b0db-df1adcebc631

📥 Commits

Reviewing files that changed from the base of the PR and between f169054 and 88b7050.

📒 Files selected for processing (9)
  • TelegramSearchBot.LLM/Interface/AI/LLM/IGroupLlmSettingsService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/AnthropicService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GroupLlmSettingsService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OllamaService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs
  • TelegramSearchBot.LLMAgent/Service/LlmServiceProxy.cs
  • TelegramSearchBot/Controller/AI/LLM/GeneralLLMController.cs
🚧 Files skipped from review as they are similar to previous changes (8)
  • TelegramSearchBot.LLM/Interface/AI/LLM/IGroupLlmSettingsService.cs
  • TelegramSearchBot.LLMAgent/Service/LlmServiceProxy.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OllamaService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/AnthropicService.cs
  • TelegramSearchBot.LLM/Service/AI/LLM/OpenAIResponsesService.cs
  • TelegramSearchBot/Controller/AI/LLM/GeneralLLMController.cs

@ModerRAS ModerRAS merged commit 715f2ed into master May 13, 2026
6 checks passed
@ModerRAS ModerRAS deleted the codex/issue-293-llm-boundaries branch May 13, 2026 00:50
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