Apply LLM middleware and hooks to final ToolCallLoop call#19
Merged
Conversation
eanzhao
approved these changes
Mar 2, 2026
Contributor
eanzhao
left a comment
There was a problem hiding this comment.
这次改动的价值比较明确:
- 把
ToolCallLoop里的 LLM 调用逻辑抽到InvokeLlmAsync,减少重复代码,hook/middleware 生命周期也更一致; maxRounds用尽后新增一次 不带 tools 的 final LLM call,能提高拿到最终文本回复的概率;- 测试已覆盖 final call 的关键行为(
Tools == null,以及 LLM middleware / hook 被调用次数)。
有两个非阻塞的小建议(低优先级):
- 增加一个正向用例:final no-tools call 返回文本时,断言返回值和
messages中 assistant 文本都正确; - 增加一个 terminate 用例:验证 final call 在 middleware
Terminate = true时的短路行为(尤其是 provider 调用次数和返回内容)。
结论:LGTM,可合并(建议后续补上上述两条测试,回归风险会更低)。
eanzhao
added a commit
that referenced
this pull request
Apr 30, 2026
19 inline comments arrived after de82e0a; verified each. Three of them (#13, #14, #16) point at the HttpClient captive bug already fixed in de82e0a — those will be answered with a reply. Three are NyxID-side contract gaps (#15, #18, #19) verified against ~/Code/NyxID HEAD cdfef0a; those need separate NyxID PRs and will be tracked. The rest are fixed here: - /model list (codex MAJOR #11): read owner default from context.RegistrationScopeId, not the ambient queryPort overload — channel inbound has no Studio HTTP request behind it, so the ambient resolver returned `default`/unrelated state. Falls back to ambient only when the scope is empty (defensive). Tests pinned. - StateTokenCodec.TryDecodeAsync (consensus MINOR #10): map AevatarOAuthClientNotProvisionedException to a distinct state_client_not_provisioned code instead of state_signature_invalid. IdentityOAuthEndpoints surfaces a "正在初始化, 30 秒后重试" detail for that code, matching the /init handler's cold-start message. - AevatarOAuthClientBootstrapService (#8, #9): - wrap RunWithRetryAsync in RunSafelyAsync that logs any escape so the unobserved-task exception sink is no longer the only safety net. - StopAsync now catches TimeoutException too: when the host shutdown deadline fires before the bootstrap task observes its own _stoppingCts cancellation, log + return cleanly instead of leaking a noisy trace. - AevatarOAuthClientGAgent.HandleEnsureProvisioned (#6, #7): document why CancellationToken.None is the contract — the framework's EventHandlerDiscoverer requires single-parameter handlers, so a turn-scoped CT cannot be surfaced. The named HTTP client's per- request timeout bounds the worst case during silo shutdown. - NyxIdRedirectUriResolver (#4): emit a warning when all URL sources are unset and the environment is not developer-shaped, parity with NyxIdAuthorityResolver's existing fallback warning. Wired through bootstrap + broker call sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request refactors the LLM invocation logic in
ToolCallLoopto improve code reuse and clarity, and updates the loop's behavior when the maximum number of tool call rounds is reached. It also enhances test coverage to ensure the new logic is properly exercised. The most important changes are grouped below.Refactoring and Code Reuse
InvokeLlmAsyncinToolCallLoop, reducing duplication and improving maintainability. (src/Aevatar.AI.Core/Tools/ToolCallLoop.cs[1] [2]Behavior Change: Final LLM Call
src/Aevatar.AI.Core/Tools/ToolCallLoop.cssrc/Aevatar.AI.Core/Tools/ToolCallLoop.csL135-R157)Testing and Middleware
ExecuteAsync_WhenMaxRoundsReachedWithoutTerminalContent_Shouldto verify that the final LLM call is made without tools, and that hooks and middleware are invoked the correct number of times. (test/Aevatar.AI.Tests/ToolCallLoopTests.cs[1] [2]DelegateLlmCallMiddlewarehelper class to facilitate testing of LLM call middleware. (test/Aevatar.AI.Tests/ToolCallLoopTests.cstest/Aevatar.AI.Tests/ToolCallLoopTests.csR284-R289)