Repository navigation
fix: AI node skill invocation error #7849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -404,7 +404,7 @@ def lookup(city: str) -> str: | |
|
|
||
| with ( | ||
| TemporaryDirectory(prefix="maxkb-agent-compat-") as directory, | ||
| patch.object(agent_module, "init_skills", new=AsyncMock()), | ||
| patch.object(agent_module, "init_skills", new=MagicMock()), | ||
| patch.object( | ||
| agent_module, | ||
| "MultiServerMCPClient", | ||
|
|
@@ -442,7 +442,7 @@ def lookup(city: str) -> str: | |
|
|
||
| with ( | ||
| TemporaryDirectory(prefix="maxkb-agent-limit-") as directory, | ||
| patch.object(agent_module, "init_skills", new=AsyncMock()), | ||
| patch.object(agent_module, "init_skills", new=MagicMock()), | ||
| patch.object(agent_module.CONFIG, "get", return_value="1"), | ||
| patch.object( | ||
| agent_module, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The change from Key Issues & Suggestions:
Conclusion:Revert to |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,19 +12,22 @@ | |
| import os | ||
| import zipfile | ||
|
|
||
| from asgiref.sync import sync_to_async | ||
| from django.db.models import QuerySet | ||
|
|
||
| from common.utils.rsa_util import rsa_long_decrypt | ||
| from knowledge.models import File | ||
| from tools.models import Tool | ||
|
|
||
|
|
||
| async def init_skills(skill_tool_ids, temp_dir): | ||
| def init_skills(skill_tool_ids, temp_dir): | ||
| """ | ||
| 同步函数:包含 ORM 查询、RSA 密钥读取(缓存未命中时查库)、文件读取与解压, | ||
| 必须在工作线程中调用,不能在事件循环线程内执行(否则触发 SynchronousOnlyOperation 并阻塞循环)。 | ||
| """ | ||
| if not skill_tool_ids: | ||
| return | ||
| skills_dir = os.path.join(temp_dir, "skills") | ||
| tools = await sync_to_async(lambda: list(QuerySet(Tool).filter(id__in=skill_tool_ids, is_active=True)))() | ||
| tools = list(QuerySet(Tool).filter(id__in=skill_tool_ids, is_active=True)) | ||
| if not tools: | ||
| return | ||
|
|
||
|
|
@@ -35,10 +38,10 @@ async def init_skills(skill_tool_ids, temp_dir): | |
| else: | ||
| params = init_params_default_value | ||
|
|
||
| file = await sync_to_async(lambda t=tool: QuerySet(File).filter(id=t.code).first())() | ||
| file = QuerySet(File).filter(id=tool.code).first() | ||
| if not file: | ||
| continue | ||
| file_bytes = await sync_to_async(file.get_bytes)() | ||
| file_bytes = file.get_bytes() | ||
|
|
||
| with zipfile.ZipFile(io.BytesIO(file_bytes), "r") as zip_ref: | ||
| members = [m for m in zip_ref.namelist() if not m.startswith("__MACOSX/") and "__MACOSX" not in m] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here are the issues, potential problems and optimization suggestions for this diff: Correctness / Potential Issues
Style / Minor
SummaryThe core change (making the function synchronous) is reasonable only if every caller is updated to run it in a worker thread. Fix the dead import, clarify the docstring, switch to |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this diff, here are the observations:
Issue: Replacing
AsyncMockwithMagicMockfor an async functionThe diff changes
init_skillsfrom being patched withAsyncMock()toMagicMock()in two test locations. Ifinit_skillsis a coroutine/async function that getsawaited in production code, this change will cause problems.Problems introduced by this change
Awaitability breaks on call. If the production code does
await init_skills(...), aMagicMock()returns a plain (non-awaitable) object when called, raising:Unless every awaited return value is explicitly configured with
return_value=..., tests may pass by accident/spuriously or fail inconsistently.Loss of async semantics verification.
AsyncMockasserts the call was awaited properly and correctly models async behavior (e.g., it's recognized byasyncio.iscoroutinefunction). Switching toMagicMocksilently removes that safety net — a test could pass even if the code stops awaitinginit_skills.Inconsistency risk across test suite. Tests still patching other async functions with
AsyncMock(e.g.,get_toolsremainsAsyncMock) but this one becomesMagicMock→ inconsistent mocking model, harder to reason about.Silent false positives. If
init_skillsisn't actually awaited (a latent bug),MagicMockhides it;AsyncMockwould surface it via a "coroutine was never awaited" warning.Recommendation
init_skillsis async: revert toAsyncMock(). This is almost certainly what you want.AsyncMockwas arguably wrong originally, and this change is correct — but double-check whyget_tools(clearly async) usesAsyncMockwhileinit_skillsdoesn't; verify the actual signature ofagent_module.init_skills.from unittest.mock import AsyncMock, MagicMock) — the diff shows usage but not imports, so make sureMagicMockis imported in each test file to avoidNameError.Minor / optimization notes
withblocks use nearly identical patch setups (init_skills,MultiServerMCPClient). Consider extracting a shared pytest fixture or helper context manager to reduce duplication and keep mock types consistent.init_skillsreturns something consumed downstream, set an explicitreturn_value(e.g.,MagicMock(return_value=...)orAsyncMock(return_value=...)) rather than relying on auto-generated mocks.Bottom line: This change is suspicious. Verify whether
init_skillsis async — if so, this is a regression and should be reverted toAsyncMock().