Skip to content

feat: 集成 ChatAgent 到主流程并修复 CI 测试 (#183, #184) - #185

Merged
Qixuan112 merged 11 commits into
mainfrom
ci/github-actions-tests
Aug 3, 2026
Merged

feat: 集成 ChatAgent 到主流程并修复 CI 测试 (#183, #184)#185
Qixuan112 merged 11 commits into
mainfrom
ci/github-actions-tests

Conversation

@Qixuan112

@Qixuan112 Qixuan112 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

feat: 集成 ChatAgent 到主流程并修复 CI 测试 (#183, #184)

Summary by CodeRabbit

  • New Features

    • Added stateless agent support with session-aware conversation handling and configurable session limits.
    • Improved handling of synchronous provider calls and timed-out operations.
    • Preserved conversation context across follow-up messages while preventing stale session data.
  • Bug Fixes

    • Failed message sends now appear as errors in logs for easier troubleshooting.
    • Improved session isolation, concurrency control, and memory usage under high workloads.
  • Tests

    • Expanded coverage for timeouts, session locking, persistence, concurrency, and bounded caches.

- Run tests on Python 3.10, 3.11, 3.12
- Execute on PR and push to main
- Generate coverage reports
- Upload to Codecov
- core/agent/ 完整实现(ChatAgent/PlanAgent/ToolAgent,331 行)
- core/main.py 新增 ChatLLMAdapter,将 ChatLLM 适配为 ChatAgent provider 接口
- 补齐 ChatAgent 38 个占位测试为真实测试
- 新增 test_chat_agent_integration.py 集成测试
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR integrates stateless ChatAgent execution into TaleCore, adds bounded session locks and dedicated executors, propagates session snapshots, updates concurrency and cache tests, changes adapter failure logs to error level, and enables manual test workflow runs.

Changes

ChatAgent runtime

Layer / File(s) Summary
Dedicated execution and session-lock bounds
core/agent/chat_agent.py
Synchronous provider calls use a dedicated executor. Session locks use LRU-bounded storage and preserve locked sessions.
Session-lock and timeout validation
tests/unit/agent/*
Tests cover non-reentrant locks, LRU eviction, held-lock retention, and generation timeouts.

TaleCore ChatAgent integration

Layer / File(s) Summary
Provider adaptation and runtime wiring
core/main.py
TaleCore adds ChatLLMAdapter, initializes or rebuilds ChatAgent, resolves PlanLLM safely, uses dedicated context executors, and shuts down executors.
Session snapshots and follow-up propagation
core/main.py
Agent history combines persisted messages and snapshots. Session IDs flow through initial and follow-up calls.
Agent integration and concurrency validation
tests/unit/test_chat_agent_integration.py, tests/unit/test_concurrency_lock.py
Tests cover stateless calls, snapshots, session isolation, per-session ordering, semaphore limits, and cross-session concurrency.

Adapter and cache behavior

Layer / File(s) Summary
Adapter failure logging and test alignment
core/adapter/src/qq/adapter.py, core/adapter/src/websocket/adapter.py, tests/test_adapter_error_logging.py
Send failures log at error level. Tests align with SendResult, synchronous event emission, real paths, and WebSocket exceptions.
Bounded-cache behavior validation
tests/test_memory_leak_fix.py
Tests validate 200-key LRU bounds, per-key message limits, nickname mapping refresh, and bounded growth.
Manual test workflow trigger
.github/workflows/test.yml
The test workflow supports manual execution.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TaleCore
  participant ChatAgent
  participant ChatLLMAdapter
  participant ChatLLM
  participant SessionManager
  TaleCore->>SessionManager: load persisted history
  TaleCore->>ChatAgent: generate(history, session_id, timeout)
  ChatAgent->>ChatLLMAdapter: chat(messages)
  ChatLLMAdapter->>ChatLLM: forward system header and messages
  ChatLLM-->>ChatLLMAdapter: response
  ChatLLMAdapter-->>ChatAgent: response
  ChatAgent-->>TaleCore: staged response
  TaleCore->>SessionManager: persist or clear snapshot
Loading

Possibly related issues

Possibly related PRs

  • Qixuan112/TaleAI#171: Both changes modify core/main.py and extend per-session locking, semaphore limits, and stateless ChatLLM handling.
  • Qixuan112/TaleAI#179: This PR extends the ChatAgent implementation with bounded locks, timeout execution changes, and TaleCore integration.
  • Qixuan112/TaleAI#169: Both changes modify QQ and WebSocket adapter send-failure logging.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.73% 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 标题准确概括了将 ChatAgent 集成到主流程并修复 CI 测试这两个主要变更。
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/github-actions-tests

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.

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

🧹 Nitpick comments (7)
tests/test_memory_leak_fix.py (1)

145-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an access-order LRU eviction case.

These tests insert keys in order only. A FIFO cache would retain the same 300..499 keys and pass both tests. The tests do not verify the BoundedCache.__getitem__ access refresh.

  • tests/test_memory_leak_fix.py#L145-L154: Insert 200 groups, read the oldest group, insert one new group, and assert that the read group remains while the next-oldest group is evicted.
  • tests/test_memory_leak_fix.py#L282-L288: Add the same access-order case for _name_to_id, or parameterize one shared cache-LRU test.
🤖 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 `@tests/test_memory_leak_fix.py` around lines 145 - 154, Extend the cache tests
to verify access-order LRU behavior: in tests/test_memory_leak_fix.py lines
145-154, insert 200 groups, access the oldest group through
BoundedCache.__getitem__, insert one additional group, and assert the accessed
group remains while the next-oldest is evicted. Add the equivalent scenario for
_name_to_id at tests/test_memory_leak_fix.py lines 282-288, or parameterize a
shared test covering both caches.
core/agent/base.py (1)

19-39: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align the declared return type with the real contract.

generate is annotated -> str. The production provider ChatLLMAdapter.chat returns None on failure (core/main.py lines 83-106), and core/main.py _call_chatllm explicitly handles reply is None. Declare Optional[str] so callers see the nullable contract, or document that implementations must convert None to "".

♻️ Proposed signature change
     async def generate(
         self,
         messages: List[Dict],
         session_id: str,
         timeout: Optional[float] = 60.0
-    ) -> str:
+    ) -> Optional[str]:
🤖 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 `@core/agent/base.py` around lines 19 - 39, Update the abstract generate
method’s return annotation in the base agent class from str to Optional[str],
preserving the existing nullable failure contract used by ChatLLMAdapter.chat
and _call_chatllm.
tests/unit/agent/test_chat_agent_concurrency.py (1)

132-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wall-clock upper bounds are too tight for shared CI runners. Both test files assert an upper bound on elapsed wall-clock time with 0.3s to 0.5s of slack. The lower bounds are deterministic, because asyncio.sleep guarantees them. The upper bounds depend on runner load, so the new GitHub Actions matrix on Python 3.10, 3.11, and 3.12 can fail these tests without any code defect. The shared root cause is one benchmark pattern copied across both files.

  • tests/unit/agent/test_chat_agent_concurrency.py#L132-L133: widen the <2.5 upper bound, or move the timing assertion behind a marker that the default CI job skips.
  • tests/unit/agent/test_chat_agent_performance.py#L62-L62: widen the <0.8 upper bound for the 3-user benchmark, or apply the same marker.
  • tests/unit/agent/test_chat_agent_performance.py#L127-L128: widen the <1.3 upper bound for the 5-user semaphore benchmark, or apply the same marker.
  • tests/unit/agent/test_chat_agent_performance.py#L217-L218: widen the <2.5 upper bound for the 10-user stress benchmark, or apply the same marker.

Keep every lower bound. The lower bounds are what prove concurrency and semaphore behavior.

🤖 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 `@tests/unit/agent/test_chat_agent_concurrency.py` around lines 132 - 133,
Widen the wall-clock upper bounds while preserving every lower-bound assertion:
update tests/unit/agent/test_chat_agent_concurrency.py lines 132-133, and the
corresponding 3-user, 5-user semaphore, and 10-user stress benchmarks in
tests/unit/agent/test_chat_agent_performance.py lines 62, 127-128, and 217-218.
Alternatively, apply one consistent marker to these timing assertions so the
default CI job skips them; do not remove or weaken the lower bounds.
core/agent/chat_agent.py (1)

91-97: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace asyncio.get_event_loop() with asyncio.get_running_loop() in the three agent sync-provider paths. Each site runs this inside an already-running coroutine, so use get_running_loop() and keep the rest of the executor dispatch unchanged.

  • core/agent/chat_agent.py#L93
  • core/agent/plan_agent.py#L64
  • core/agent/tool_agent.py#L66

If the sync-provider dispatch is extracted later, apply the fix in that shared helper.

🤖 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 `@core/agent/chat_agent.py` around lines 91 - 97, Replace
asyncio.get_event_loop() with asyncio.get_running_loop() in the sync-provider
executor paths of core/agent/chat_agent.py lines 91-97, core/agent/plan_agent.py
lines 63-68, and core/agent/tool_agent.py lines 65-70; keep the existing
executor dispatch unchanged. If these paths are later consolidated into a shared
helper, apply the change there instead.
tests/unit/agent/test_chat_agent_locks.py (1)

107-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace asyncio.Semaphore._value assertions with behavioral checks.

_value is private and may vary across supported Python versions. Assert the semaphore state by draining permits with asyncio.wait_for(sem.acquire(), timeout=...) and confirming the next acquire blocks. Keep this change for lines 107, 168, 177, and 191.

🤖 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 `@tests/unit/agent/test_chat_agent_locks.py` at line 107, Replace the private
_value assertions in the affected semaphore tests, including the assertions
around agent._global_semaphore at lines 107, 168, 177, and 191, with behavioral
checks that acquire all expected permits using asyncio.wait_for and a short
timeout, then verify the next acquire times out or otherwise blocks. Preserve
each test’s expected semaphore capacity while avoiding direct access to
asyncio.Semaphore._value.
tests/unit/test_chat_agent_integration.py (1)

344-347: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the history assertion.

zip truncates to the shorter sequence. If a third user message leaks into the prompt, the assertion still passes. Assert the count first.

♻️ Proposed refactor
         user_msgs = [m for m in second if m.get("role") == "user"]
-        assert [m["content"].endswith(t) for m, t in zip(user_msgs, ["第一轮消息", "第二轮消息"])] == [True, True], (
-            "持久化模式应从 SessionManager 记忆读取历史"
-        )
+        assert len(user_msgs) == 2, f"应只含记忆中的一条 + 本轮一条,实际 {len(user_msgs)} 条"
+        assert user_msgs[0]["content"].endswith("第一轮消息")
+        assert user_msgs[1]["content"].endswith("第二轮消息")
🤖 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 `@tests/unit/test_chat_agent_integration.py` around lines 344 - 347, Strengthen
the history assertion in the test by first asserting that user_msgs contains
exactly two messages, then retain the existing content checks for the first and
second messages. Avoid relying on zip alone, so extra user messages cause the
test to fail.
tests/unit/test_concurrency_lock.py (1)

342-359: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These wall-clock assertions can flake on shared CI runners.

max_active >= 3 combined with max_active <= 3 requires the scheduler to fill the semaphore exactly. 0.5 < total_time < 1.5 requires stable timing. The PR adds a GitHub Actions workflow across three Python versions, so these tests will run on shared runners where a slow wave pushes total_time above 1.5s. Consider widening the upper bounds, or replacing the timing checks with event-based synchronization (for example, a barrier that releases only after three calls are active).

The same concern applies to Line 277 and Line 490.

🤖 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 `@tests/unit/test_concurrency_lock.py` around lines 342 - 359, Update the
concurrency tests around the max_active and total_time assertions, including the
analogous checks near the other referenced cases, to avoid relying on exact
scheduler saturation or tight wall-clock limits. Use event-based synchronization
where possible so three calls must become active before proceeding, and widen
remaining timing bounds enough for shared CI runners while preserving
verification of semaphore limiting and per-session parallelism.
🤖 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 `@core/agent/chat_agent.py`:
- Around line 91-101: Update the synchronous path in ChatAgent’s executor call
to use a dedicated ThreadPoolExecutor instead of the shared default executor,
isolating timed-out chat_method work from other run_in_executor users. Ensure
the executor is owned and cleaned up appropriately, and add a comment
documenting that asyncio timeout cancellation cannot stop an already-running
worker thread; retain the provider-level transport timeout requirement where
applicable.
- Around line 38-41: Bound the _session_locks registry while preserving locks
that are held or have waiters. Replace the plain dict with an OrderedDict-based
registry, update the lock lookup path in ChatAgent to refresh entries and evict
only least-recently-used locks whose lock is neither locked nor awaited, and
enforce a bounded size consistent with the existing cache policy. Keep
synchronization through _lock_manager_mutex so lookup, insertion, and eviction
remain race-safe.

In `@core/main.py`:
- Around line 1612-1625: Propagate the active session ID through the follow-up
tool-round path: update _handle_respond_message to pass sid into
_resolve_follow_up, then through _call_chatllm_with_timeout into _call_chatllm.
Ensure _call_chatllm_with_timeout forwards sid explicitly so the ChatAgent
branch uses it instead of self.chat.current_sid, preserving session history and
snapshots when persistence is disabled.
- Around line 337-345: Update CoreManager._init_chat_agent() to retain the model
returned by provider_manager.resolve("main_llm") and bind it when constructing
ChatAgent. Update ChatAgent.generate to forward the bound model to provider.chat
alongside messages, preserving the existing fallback behavior for unresolved
providers and initialization failures.
- Around line 366-372: Update the reload condition around _init_chat_agent so it
compares the existing ChatAgent provider adapter’s chatllm attribute with
self.chat, rather than comparing _provider directly to self.chat. Preserve
rebuilding when chat_agent is absent or the wrapped ChatLLM instance differs,
while avoiding recreation when the same ChatLLM remains configured.
- Around line 1520-1540: Update _get_session_messages to check the session’s
enabled state before calling session_manager.get_memory(sid), matching the
session.enabled guard used by _persist_snapshot. Skip persisted history for
disabled sessions while preserving snapshot handling and existing error
behavior.

In `@tests/test_memory_leak_fix.py`:
- Around line 507-513: Replace the shallow sys.getsizeof-based ratios in the
memory-growth assertions with a check that measures transitive retained entries,
including the buffer’s cache, timestamps, message lists, and the name map’s
nickname-related structures; alternatively remove the size ratios and explicitly
limit the test to verifying bounded key counts. Update the assertions and
failure messages around ratio_buffer and ratio_name_map accordingly.

In `@tests/unit/agent/test_chat_agent_locks.py`:
- Around line 53-58: Rename test_session_locks_are_reentrant to state that
session locks are not reentrant, preserving its existing assertions and
docstring.
- Line 95: Rename the ambiguous loop variable l in the assertion within the lock
test to a descriptive name, and update its reference in the all expression while
preserving the existing identity check against locks[0].

In `@tests/unit/agent/test_chat_agent_timeout.py`:
- Around line 198-209: Update the timeout test around agent.generate so it
validates the 60-second default rather than duplicating the explicit-timeout
case: remove the timeout override and make the simulated call exceed the
default, or revise the comment to accurately describe the custom timeout
behavior. Keep the existing asyncio.TimeoutError assertion and use
test_timeout_parameter_contract for the separate structural default check.

In `@tests/unit/test_chat_agent_integration.py`:
- Around line 31-46: Make the lightweight module stubs scoped to this test
module rather than leaving them in process-wide sys.modules: move the
_stub_modules injection into a module-scoped autouse fixture and remove only the
entries it added during teardown, or remove the stubbing entirely. Ensure later
test modules can import the real numpy and bs4 packages.

In `@tests/unit/test_concurrency_lock.py`:
- Around line 339-340: Remove the unnecessary f-string prefixes from the two
constant print messages near the Semaphore timing output and the corresponding
message near line 415. Keep the string contents and print behavior unchanged so
Ruff F541 is resolved.

---

Nitpick comments:
In `@core/agent/base.py`:
- Around line 19-39: Update the abstract generate method’s return annotation in
the base agent class from str to Optional[str], preserving the existing nullable
failure contract used by ChatLLMAdapter.chat and _call_chatllm.

In `@core/agent/chat_agent.py`:
- Around line 91-97: Replace asyncio.get_event_loop() with
asyncio.get_running_loop() in the sync-provider executor paths of
core/agent/chat_agent.py lines 91-97, core/agent/plan_agent.py lines 63-68, and
core/agent/tool_agent.py lines 65-70; keep the existing executor dispatch
unchanged. If these paths are later consolidated into a shared helper, apply the
change there instead.

In `@tests/test_memory_leak_fix.py`:
- Around line 145-154: Extend the cache tests to verify access-order LRU
behavior: in tests/test_memory_leak_fix.py lines 145-154, insert 200 groups,
access the oldest group through BoundedCache.__getitem__, insert one additional
group, and assert the accessed group remains while the next-oldest is evicted.
Add the equivalent scenario for _name_to_id at tests/test_memory_leak_fix.py
lines 282-288, or parameterize a shared test covering both caches.

In `@tests/unit/agent/test_chat_agent_concurrency.py`:
- Around line 132-133: Widen the wall-clock upper bounds while preserving every
lower-bound assertion: update tests/unit/agent/test_chat_agent_concurrency.py
lines 132-133, and the corresponding 3-user, 5-user semaphore, and 10-user
stress benchmarks in tests/unit/agent/test_chat_agent_performance.py lines 62,
127-128, and 217-218. Alternatively, apply one consistent marker to these timing
assertions so the default CI job skips them; do not remove or weaken the lower
bounds.

In `@tests/unit/agent/test_chat_agent_locks.py`:
- Line 107: Replace the private _value assertions in the affected semaphore
tests, including the assertions around agent._global_semaphore at lines 107,
168, 177, and 191, with behavioral checks that acquire all expected permits
using asyncio.wait_for and a short timeout, then verify the next acquire times
out or otherwise blocks. Preserve each test’s expected semaphore capacity while
avoiding direct access to asyncio.Semaphore._value.

In `@tests/unit/test_chat_agent_integration.py`:
- Around line 344-347: Strengthen the history assertion in the test by first
asserting that user_msgs contains exactly two messages, then retain the existing
content checks for the first and second messages. Avoid relying on zip alone, so
extra user messages cause the test to fail.

In `@tests/unit/test_concurrency_lock.py`:
- Around line 342-359: Update the concurrency tests around the max_active and
total_time assertions, including the analogous checks near the other referenced
cases, to avoid relying on exact scheduler saturation or tight wall-clock
limits. Use event-based synchronization where possible so three calls must
become active before proceeding, and widen remaining timing bounds enough for
shared CI runners while preserving verification of semaphore limiting and
per-session parallelism.
🪄 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 Plus

Run ID: 656245d2-3bfe-4959-8a41-cac0b893f188

📥 Commits

Reviewing files that changed from the base of the PR and between a8a0e4c and f56f92a.

📒 Files selected for processing (18)
  • core/adapter/src/qq/adapter.py
  • core/adapter/src/websocket/adapter.py
  • core/agent/__init__.py
  • core/agent/base.py
  • core/agent/chat_agent.py
  • core/agent/plan_agent.py
  • core/agent/tool_agent.py
  • core/main.py
  • tests/test_adapter_error_logging.py
  • tests/test_memory_leak_fix.py
  • tests/unit/agent/test_chat_agent_basic.py
  • tests/unit/agent/test_chat_agent_concurrency.py
  • tests/unit/agent/test_chat_agent_locks.py
  • tests/unit/agent/test_chat_agent_performance.py
  • tests/unit/agent/test_chat_agent_timeout.py
  • tests/unit/agent/test_llm_agent_base.py
  • tests/unit/test_chat_agent_integration.py
  • tests/unit/test_concurrency_lock.py

Comment thread core/agent/chat_agent.py Outdated
Comment thread core/agent/chat_agent.py
Comment thread core/main.py
Comment thread core/main.py Outdated
Comment thread core/main.py
Comment thread tests/unit/agent/test_chat_agent_locks.py Outdated
Comment thread tests/unit/agent/test_chat_agent_locks.py Outdated
Comment thread tests/unit/agent/test_chat_agent_timeout.py Outdated
Comment thread tests/unit/test_chat_agent_integration.py Outdated
Comment thread tests/unit/test_concurrency_lock.py Outdated
修复 PR #185 CodeRabbit 审查发现的 12 条评论:

core/main.py:
- 修复 _on_config_reloaded 的 ChatAgent 重建检查:_provider 是
  ChatLLMAdapter 而非 self.chat,改为比较 adapter 包装的 ChatLLM,
  避免每次 config_reloaded 都重建 agent、丢失锁/semaphore 状态
- 禁用的会话不加载历史(_get_session_messages),与旧路径
  set_session(load_history=session_enabled) 语义一致
- 工具轮次贯通 sid:_call_chatllm_with_timeout/_resolve_follow_up
  增加 sid 参数,无持久化模式下工具轮次不再落到空 sid,
  快照读写(上下文连续)恢复正常
- provider 直连回退路径绑定 model(functools.partial),
  BaseProvider.chat 的 model 是必填参数
- context window 图片下载/VLM 调用改用专用 executor,
  避免锁内阻塞任务占用默认线程池;run() 退出时释放线程池

core/agent/chat_agent.py:
- _session_locks 改为 LRU 有界(max_sessions=1000),持有中的锁
  绝不淘汰(推迟到空闲)
- 同步 provider 改用专用 ThreadPoolExecutor(4 线程),
  超时不中断已运行线程的局限在注释中说明

tests/:
- sys.modules 的 numpy/bs4 stub 移入 session 级 autouse fixture
  并在结束恢复原状,消除进程级全局污染(CI 顺序失败根因)
- 重命名 test_session_locks_are_not_reentrant(断言验证非重入)
- 修复 Ruff E741 歧义变量名、F541 多余 f 前缀
- 补充 LRU 淘汰与持有锁保护测试;timeout 默认 60s 契约断言
core/__init__.py 的 from .main import main 把 core.main 属性遮蔽为函数,
patch("core.main.calculate_split_interval") 在 CI 收集顺序下会解析到
函数对象而非模块而抛 AttributeError;改用 importlib 取真实模块对象
patch.object(同 test_memory_leak_fix.py 的修复)。
@Qixuan112 Qixuan112 closed this Aug 3, 2026
@Qixuan112 Qixuan112 reopened this Aug 3, 2026
# Conflicts:
#	core/agent/chat_agent.py
#	tests/unit/agent/test_chat_agent_locks.py
#	tests/unit/agent/test_chat_agent_timeout.py
@Qixuan112
Qixuan112 merged commit 8d30d41 into main Aug 3, 2026
3 of 4 checks passed
@Qixuan112
Qixuan112 deleted the ci/github-actions-tests branch August 3, 2026 14:45

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
core/main.py (2)

1603-1605: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Clear snapshots when a session is disabled.

Line 1605 returns before removing _chat_snapshots[sid]. _get_session_messages() then appends that snapshot on later requests. Disabled sessions therefore retain and resend prior user and assistant content to the LLM.

Proposed fix
             session = self.session_manager.get_session(sid)
             if session is not None and not session.enabled:
-                return  # 禁用的会话不持久化新记忆
+                self._chat_snapshots.pop(sid, None)
+                return
🤖 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 `@core/main.py` around lines 1603 - 1605, Update the disabled-session branch in
the session persistence flow around get_session and _chat_snapshots so it
removes _chat_snapshots[sid] before returning. Preserve the existing behavior of
skipping new-memory persistence while ensuring later _get_session_messages()
calls cannot reuse stale snapshot content.

1643-1669: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the provider fallback executable.

Line 1643 returns before the ChatAgent branch when ChatLLM initialization fails. This makes the direct-provider fallback from _init_chat_agent() unreachable.

Also create sid when self.chat_agent exists, and call self.chat.set_session(...) only when self.chat exists. Otherwise platform fallback calls either fail on self.chat.current_sid or lose per-session isolation.

Proposed fix
-        if self.chat is None:
+        if self.chat is None and self.chat_agent is None:
             logger.error("ChatLLM 未初始化")
             return "[系统错误] ChatLLM 未初始化,请检查 services.yaml 配置"
...
                 if not sid:
-                    sid = self.chat.current_sid or ""
+                    sid = (self.chat.current_sid if self.chat is not None else "") or ""
🤖 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 `@core/main.py` around lines 1643 - 1669, Update the initialization guard
before the ChatAgent branch so a missing self.chat does not return when
self.chat_agent is available, allowing the direct-provider fallback from
_init_chat_agent() to execute. In the self.chat_agent path, derive sid without
assuming self.chat exists, and guard self.chat.set_session(...) so it is called
only when self.chat is present, preserving per-session isolation for both
initialized and fallback providers.
core/agent/chat_agent.py (1)

59-90: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Eviction only checks the oldest entry, and the LRU test cannot detect the gap. The root cause is in _get_session_lock: it inspects only next(iter(self._session_locks)) and gives up entirely if that entry is locked, instead of scanning for any evictable entry. Under bursty traffic, a single long-running session at the head of the ordering can let the registry exceed max_sessions even while many newer, already-completed sessions sit free behind it. The companion test cannot catch this because it never holds a lock during eviction.

  • core/agent/chat_agent.py#L59-L90: scan past a busy head for the first free, non-current candidate before giving up, instead of breaking on the first locked entry (see proposed diff in the per-file comment above).
  • tests/unit/agent/test_chat_agent_locks.py#L98-L120: extend test_session_locks_lru_bounded with a case where the oldest lock stays held while later locks are free, and assert those free entries are still evicted once the bound-check runs.
🤖 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 `@core/agent/chat_agent.py` around lines 59 - 90, The _get_session_lock
eviction loop must scan past a locked oldest entry and remove the first free,
non-current lock until the registry meets _max_sessions, rather than stopping at
the first busy lock. Update tests/unit/agent/test_chat_agent_locks.py lines
98-120 in test_session_locks_lru_bounded to keep the oldest lock held while
later locks remain free, then assert those free entries are evicted when the
bound is exceeded.
tests/unit/test_concurrency_lock.py (1)

568-584: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Do not skip overlap checks based on message text.

ProcessedMessage objects created with the same text produce identical current_message() keys. If two sessions send identical text, this loop drops that cross-session pair before checking its lock overlap.

Compare unique event instances or session IDs instead, and exclude only the same event.

🤖 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 `@tests/unit/test_concurrency_lock.py` around lines 568 - 584, Update the
overlap-check loop to distinguish events by their unique event instances or
session IDs rather than comparing msg_i and msg_j message text. Remove the msg_i
== msg_j skip based on current message content, and exclude only when the two
event instances represent the same event so identical messages from different
sessions still undergo overlap checks.
🧹 Nitpick comments (3)
tests/unit/agent/test_chat_agent_locks.py (1)

98-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend LRU coverage to a busy-head scenario.

Every lock touched in this test is released before the next _get_session_lock call, so locked() is always False during eviction. The test does not cover the case where the oldest entry stays held while newer, already-free entries accumulate behind it. Add a case with a held oldest lock and multiple free later entries, and assert that the free entries still get evicted once eviction resumes (or that the bound is not silently exceeded forever). This complements test_session_locks_held_lock_never_evicted, which only checks the single-oldest-locked case.

🤖 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 `@tests/unit/agent/test_chat_agent_locks.py` around lines 98 - 120, Add a
busy-head scenario to test_session_locks_lru_bounded: retain the oldest session
lock in a held state while creating multiple newer, released sessions, then
release the oldest lock and verify eviction resumes by removing free entries
while keeping _session_locks within max_sessions. Ensure the assertions
specifically cover that free later locks are evicted rather than allowing the
dictionary to remain over capacity indefinitely.
tests/unit/test_concurrency_lock.py (2)

392-435: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the stateless call payload.

recorded_calls stores the sid and messages arguments, but the test checks only the extracted user text. The final assertion checks recorded_sids from set_session(), which is a different path. A regression that drops or misroutes sid or snapshot messages can still pass.

Assert the sid and relevant message snapshot for each recorded_calls entry.

🤖 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 `@tests/unit/test_concurrency_lock.py` around lines 392 - 435, Extend the
assertions in the stateless session test around recorded_calls to validate each
chat invocation’s sid and messages snapshot, not just the extracted user text.
Use the expected per-session sids and corresponding message contents for the
group_stateless_a and group_stateless_b calls, while retaining the existing
recorded_sids assertion for set_session().

347-364: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Make concurrency assertions scheduler-independent.

The max_active >= 3 assertion and fixed duration ranges depend on CI scheduling and wall-clock load. A correct run on a busy worker can fail these bounds.

Use events or barriers to hold calls deterministically, assert semaphore occupancy directly, and keep only a generous watchdog for deadlock detection. Move strict performance thresholds to a benchmark if they are required.

Also applies to: 490-498

🤖 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 `@tests/unit/test_concurrency_lock.py` around lines 347 - 364, Update the
concurrency test around the max_active and total_time assertions, including the
corresponding assertions near the later task batch, to avoid scheduler-dependent
parallelism and duration thresholds. Use synchronization events or barriers to
hold calls deterministically, assert Semaphore(3) occupancy directly, and retain
only a generous timeout as a deadlock watchdog; move strict performance
validation out of this unit test.
🤖 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 `@tests/unit/test_chat_agent_integration.py`:
- Around line 46-62: The _stub_heavy_deps fixture leaves stub modules in
sys.modules across the entire test session and runs too late to affect top-level
imports. Change it to module scope and defer imports that require numpy or bs4
until after fixture setup, or remove the stubs and rely on real dependencies;
ensure sys.modules is restored after each test module.

In `@tests/unit/test_concurrency_lock.py`:
- Line 342: Update the diagnostic print statement in the concurrency test to use
f-string interpolation so it outputs the measured max_active value instead of
the literal placeholder.

---

Outside diff comments:
In `@core/agent/chat_agent.py`:
- Around line 59-90: The _get_session_lock eviction loop must scan past a locked
oldest entry and remove the first free, non-current lock until the registry
meets _max_sessions, rather than stopping at the first busy lock. Update
tests/unit/agent/test_chat_agent_locks.py lines 98-120 in
test_session_locks_lru_bounded to keep the oldest lock held while later locks
remain free, then assert those free entries are evicted when the bound is
exceeded.

In `@core/main.py`:
- Around line 1603-1605: Update the disabled-session branch in the session
persistence flow around get_session and _chat_snapshots so it removes
_chat_snapshots[sid] before returning. Preserve the existing behavior of
skipping new-memory persistence while ensuring later _get_session_messages()
calls cannot reuse stale snapshot content.
- Around line 1643-1669: Update the initialization guard before the ChatAgent
branch so a missing self.chat does not return when self.chat_agent is available,
allowing the direct-provider fallback from _init_chat_agent() to execute. In the
self.chat_agent path, derive sid without assuming self.chat exists, and guard
self.chat.set_session(...) so it is called only when self.chat is present,
preserving per-session isolation for both initialized and fallback providers.

In `@tests/unit/test_concurrency_lock.py`:
- Around line 568-584: Update the overlap-check loop to distinguish events by
their unique event instances or session IDs rather than comparing msg_i and
msg_j message text. Remove the msg_i == msg_j skip based on current message
content, and exclude only when the two event instances represent the same event
so identical messages from different sessions still undergo overlap checks.

---

Nitpick comments:
In `@tests/unit/agent/test_chat_agent_locks.py`:
- Around line 98-120: Add a busy-head scenario to
test_session_locks_lru_bounded: retain the oldest session lock in a held state
while creating multiple newer, released sessions, then release the oldest lock
and verify eviction resumes by removing free entries while keeping
_session_locks within max_sessions. Ensure the assertions specifically cover
that free later locks are evicted rather than allowing the dictionary to remain
over capacity indefinitely.

In `@tests/unit/test_concurrency_lock.py`:
- Around line 392-435: Extend the assertions in the stateless session test
around recorded_calls to validate each chat invocation’s sid and messages
snapshot, not just the extracted user text. Use the expected per-session sids
and corresponding message contents for the group_stateless_a and
group_stateless_b calls, while retaining the existing recorded_sids assertion
for set_session().
- Around line 347-364: Update the concurrency test around the max_active and
total_time assertions, including the corresponding assertions near the later
task batch, to avoid scheduler-dependent parallelism and duration thresholds.
Use synchronization events or barriers to hold calls deterministically, assert
Semaphore(3) occupancy directly, and retain only a generous timeout as a
deadlock watchdog; move strict performance validation out of this unit test.
🪄 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 Plus

Run ID: ae10e6df-3196-4eb2-a043-3b9484cb4f83

📥 Commits

Reviewing files that changed from the base of the PR and between f56f92a and 2808a09.

📒 Files selected for processing (8)
  • .github/workflows/test.yml
  • core/agent/chat_agent.py
  • core/main.py
  • tests/test_memory_leak_fix.py
  • tests/unit/agent/test_chat_agent_locks.py
  • tests/unit/agent/test_chat_agent_timeout.py
  • tests/unit/test_chat_agent_integration.py
  • tests/unit/test_concurrency_lock.py

Comment on lines +46 to +62
@pytest.fixture(autouse=True, scope="session")
def _stub_heavy_deps():
"""session 级:注入 numpy/bs4 轻量 stub,结束后恢复 sys.modules 原状。"""
saved = {}
for name in _STUB_MODULES:
if name in sys.modules:
saved[name] = sys.modules[name]
try:
for name in _STUB_MODULES:
sys.modules.setdefault(name, type(sys)(name))
yield
finally:
for name in _STUB_MODULES:
if name in saved:
sys.modules[name] = saved[name]
else:
sys.modules.pop(name, None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

Fix the dependency-stub fixture lifecycle.

scope="session" runs the finally block only at pytest session teardown. The empty numpy and bs4 modules can remain in process-wide sys.modules while later tests run. This repeats the previous cross-module pollution issue.

Fixture setup also occurs after top-level imports, so it cannot stub dependencies needed during collection. Use module scope with deferred dependent imports, or remove the stubs and use the real dependencies.

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 48-48: Docstring contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF002)


[warning] 48-48: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)

🤖 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 `@tests/unit/test_chat_agent_integration.py` around lines 46 - 62, The
_stub_heavy_deps fixture leaves stub modules in sys.modules across the entire
test session and runs too late to affect top-level imports. Change it to module
scope and defer imports that require numpy or bs4 until after fixture setup, or
remove the stubs and rely on real dependencies; ensure sys.modules is restored
after each test module.

print(f"\n=== Semaphore Test Results ===")
print(f"Max concurrent executions: {max_active}")
print("\n=== Semaphore Test Results ===")
print("Max concurrent executions: {max_active}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore the f-string in the concurrency diagnostic.

Line 342 prints the literal {max_active} instead of the measured value.

Proposed fix
-    print("Max concurrent executions: {max_active}")
+    print(f"Max concurrent executions: {max_active}")
📝 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
print("Max concurrent executions: {max_active}")
print(f"Max concurrent executions: {max_active}")
🤖 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 `@tests/unit/test_concurrency_lock.py` at line 342, Update the diagnostic print
statement in the concurrency test to use f-string interpolation so it outputs
the measured max_active value instead of the literal placeholder.

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