Skip to content

Commit a438727

Browse files
author
raylchen
committed
bugfix: 更新 skill 执行
1 parent 70ec5a2 commit a438727

22 files changed

Lines changed: 776 additions & 389 deletions

format.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
- Rewrite import statements across project files accordingly.
3434
- Optional: refresh package API blocks after rename.
3535
36+
6) align-init-all-order
37+
- Align top-level ``__all__`` order with import symbol order in ``__init__.py``.
38+
- Remaining legacy names not found in imports are appended in original order.
39+
- Supports both whole-project mode and single-file mode.
40+
3641
Examples:
3742
python3 format.py sort-imports --root . --dry-run
3843
python3 format.py sort-imports --root .
@@ -41,6 +46,8 @@
4146
python3 format.py sync-imports-package-api --root . --dry-run
4247
python3 format.py rename-private-modules --root . --dry-run
4348
python3 format.py rename-private-modules --root . --build-package-api
49+
python3 format.py align-init-all-order --root . --dry-run
50+
python3 format.py align-init-all-order --path trpc_agent_sdk/skills/__init__.py
4451
python3 format.py check-chinese --root . --dry-run
4552
"""
4653

@@ -1296,6 +1303,35 @@ def run_check_chinese(root: Path, dry_run: bool) -> int:
12961303
return 0
12971304

12981305

1306+
def run_align_init_all_order(root: Path, dry_run: bool, path: str | None) -> int:
1307+
"""Align __all__ order in __init__.py with import order."""
1308+
apply = not dry_run
1309+
target_files: list[Path] = []
1310+
1311+
if path:
1312+
target = Path(path).resolve()
1313+
if not target.exists() or not target.is_file():
1314+
print(f"Invalid --path: {target}", file=sys.stderr)
1315+
return 2
1316+
if target.name != "__init__.py":
1317+
print(f"--path must point to an __init__.py file: {target}", file=sys.stderr)
1318+
return 2
1319+
target_files = [target]
1320+
else:
1321+
target_files = [package_dir / "__init__.py" for package_dir in iter_package_dirs(root)]
1322+
1323+
changed_files: list[Path] = []
1324+
for init_path in sorted(set(target_files)):
1325+
if merge_init_all_exports(init_path, apply=apply):
1326+
changed_files.append(init_path)
1327+
1328+
mode = "DRY_RUN" if dry_run else "APPLY"
1329+
print(f"[{mode}] align-init-all-order scanned: {len(target_files)}, updated: {len(changed_files)}")
1330+
for p in changed_files:
1331+
print(str(p))
1332+
return 0
1333+
1334+
12991335
def main() -> int:
13001336
parser = argparse.ArgumentParser(description="Python import/public-API maintenance tool.")
13011337
subparsers = parser.add_subparsers(dest="command", required=True)
@@ -1349,6 +1385,14 @@ def main() -> int:
13491385
p_check_chinese.add_argument("--root", default=".", help="Project root directory.")
13501386
p_check_chinese.add_argument("--dry-run", action="store_true", help="Read-only mode (same output).")
13511387

1388+
p_align_all = subparsers.add_parser(
1389+
"align-init-all-order",
1390+
help="Align __all__ order with import order in __init__.py files.",
1391+
)
1392+
p_align_all.add_argument("--root", default=".", help="Project root directory.")
1393+
p_align_all.add_argument("--path", default=None, help="Optional path to a specific __init__.py file.")
1394+
p_align_all.add_argument("--dry-run", action="store_true", help="Preview changes without writing files.")
1395+
13521396
args = parser.parse_args()
13531397
try:
13541398
root = _validate_root(args.root)
@@ -1372,6 +1416,8 @@ def main() -> int:
13721416
return run_detect_issues(root, args.dry_run)
13731417
if args.command == "check-chinese":
13741418
return run_check_chinese(root, args.dry_run)
1419+
if args.command == "align-init-all-order":
1420+
return run_align_init_all_order(root, args.dry_run, args.path)
13751421

13761422
print(f"Unknown command: {args.command}", file=sys.stderr)
13771423
return 2

tests/agents/core/test_skill_processor.py

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@
2222
_default_knowledge_only_guidance,
2323
_default_full_tooling_and_workspace_guidance,
2424
_default_tooling_and_workspace_guidance,
25-
_is_knowledge_only,
2625
_normalize_custom_guidance,
2726
_normalize_load_mode,
2827
_SKILLS_LOADED_ORDER_STATE_KEY,
29-
_SKILLS_OVERVIEW_HEADER,
3028
)
3129
from trpc_agent_sdk.context import InvocationContext, create_agent_context
3230
from trpc_agent_sdk.events import EventActions
@@ -38,6 +36,7 @@
3836
SKILL_TOOLS_STATE_KEY_PREFIX,
3937
BaseSkillRepository,
4038
Skill,
39+
SkillProfileFlags,
4140
SkillResource,
4241
SkillSummary,
4342
)
@@ -169,20 +168,6 @@ def test_empty_mode_defaults_to_turn(self):
169168
assert _normalize_load_mode(None) == SKILL_LOAD_MODE_TURN
170169

171170

172-
class TestIsKnowledgeOnly:
173-
def test_knowledge_only_profiles(self):
174-
"""Recognized knowledge-only profile strings return True."""
175-
assert _is_knowledge_only("knowledge_only") is True
176-
assert _is_knowledge_only("knowledge") is True
177-
assert _is_knowledge_only("Knowledge-Only") is True
178-
179-
def test_non_knowledge_profiles(self):
180-
"""Non-matching profiles return False."""
181-
assert _is_knowledge_only("full") is False
182-
assert _is_knowledge_only("") is False
183-
assert _is_knowledge_only(None) is False
184-
185-
186171
class TestNormalizeCustomGuidance:
187172
def test_empty_returns_empty(self):
188173
"""Empty string passes through."""
@@ -207,27 +192,28 @@ def test_existing_newlines_preserved(self):
207192
class TestGuidanceTextBuilders:
208193
def test_knowledge_only_guidance_contains_header(self):
209194
"""Knowledge-only guidance includes the tooling guidance header."""
210-
text = _default_knowledge_only_guidance()
195+
text = _default_knowledge_only_guidance(SkillProfileFlags.resolve_flags("knowledge_only"))
211196
assert "Tooling and workspace guidance" in text
212197

213198
def test_full_tooling_guidance_exec_enabled(self):
214199
"""Full tooling guidance with exec tools enabled mentions skill_exec."""
215-
text = _default_full_tooling_and_workspace_guidance(exec_tools_disabled=False)
200+
text = _default_full_tooling_and_workspace_guidance(SkillProfileFlags.resolve_flags("full"))
216201
assert "skill_exec" in text
217202

218203
def test_full_tooling_guidance_exec_disabled(self):
219204
"""Full tooling guidance with exec tools disabled omits skill_exec mention."""
220-
text = _default_full_tooling_and_workspace_guidance(exec_tools_disabled=True)
205+
text = _default_full_tooling_and_workspace_guidance(
206+
SkillProfileFlags.resolve_flags("full").without_interactive_execution())
221207
assert "interactive execution is available" in text
222208

223209
def test_default_routing_knowledge_only(self):
224210
"""Dispatcher routes to knowledge-only guidance for matching profile."""
225-
text = _default_tooling_and_workspace_guidance("knowledge_only", False)
211+
text = _default_tooling_and_workspace_guidance(SkillProfileFlags.resolve_flags("knowledge_only"))
226212
assert "progressive disclosure" in text
227213

228214
def test_default_routing_full(self):
229215
"""Dispatcher routes to full guidance for non-matching profile."""
230-
text = _default_tooling_and_workspace_guidance("", False)
216+
text = _default_tooling_and_workspace_guidance(SkillProfileFlags.resolve_flags("full"))
231217
assert "skill_run" in text
232218

233219

@@ -258,8 +244,8 @@ def test_custom_parameters(self, sample_repo):
258244
assert proc._load_mode == SKILL_LOAD_MODE_ONCE
259245
assert proc._tooling_guidance == "custom"
260246
assert proc._tool_result_mode is True
261-
assert proc._tool_profile == "knowledge_only"
262-
assert proc._exec_tools_disabled is True
247+
assert proc._tool_flags.load is True
248+
assert proc._tool_flags.exec is False
263249
assert proc._max_loaded_skills == 5
264250

265251

tests/skills/test_run_tool.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from trpc_agent_sdk.code_executors import WorkspaceRunResult
1919
from trpc_agent_sdk.context import InvocationContext
2020
from trpc_agent_sdk.skills import BaseSkillRepository
21+
from trpc_agent_sdk.skills import Skill
2122
from trpc_agent_sdk.skills.tools import ArtifactInfo
2223
from trpc_agent_sdk.skills.tools import SkillRunFile
2324
from trpc_agent_sdk.skills.tools import SkillRunInput
@@ -198,6 +199,7 @@ def setup_method(self):
198199
self.mock_runtime.manager = Mock(return_value=self.mock_manager)
199200
self.mock_runtime.fs = Mock(return_value=self.mock_fs)
200201
self.mock_runtime.runner = Mock(return_value=self.mock_runner)
202+
self.mock_repository.get = Mock(return_value=Skill(body="Command:\n echo hello\n"))
201203

202204
self.mock_ctx = Mock(spec=InvocationContext)
203205
self.mock_ctx.agent_context = Mock()

tests/skills/test_tools.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ def test_skill_list_tools_success(self):
141141

142142
result = skill_list_tools(mock_ctx, "test-skill")
143143

144-
assert len(result["tools"]) == 3
145-
assert "tool1" in result["tools"]
146-
assert "tool2" in result["tools"]
147-
assert "tool3" in result["tools"]
144+
assert len(result["available_tools"]) == 3
145+
assert "tool1" in result["available_tools"]
146+
assert "tool2" in result["available_tools"]
147+
assert "tool3" in result["available_tools"]
148148

149149
def test_skill_list_tools_no_tools(self):
150150
"""Test listing tools for skill with no tools."""
@@ -158,7 +158,7 @@ def test_skill_list_tools_no_tools(self):
158158

159159
result = skill_list_tools(mock_ctx, "test-skill")
160160

161-
assert result["tools"] == []
161+
assert result["available_tools"] == []
162162

163163
def test_skill_list_tools_skill_not_found(self):
164164
"""Test listing tools for nonexistent skill."""
@@ -171,7 +171,7 @@ def test_skill_list_tools_skill_not_found(self):
171171

172172
result = skill_list_tools(mock_ctx, "nonexistent-skill")
173173

174-
assert result["tools"] == []
174+
assert result["available_tools"] == []
175175

176176
def test_skill_list_tools_repository_not_found(self):
177177
"""Test listing tools when repository not found."""

tests/skills/tools/test_skill_exec.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,26 +231,26 @@ async def test_put_and_get_session(self):
231231
mock_session = MagicMock()
232232
mock_session.exited_at = None
233233
await tool._put_session("s1", mock_session)
234-
result = await tool.get_session("s1")
234+
result = await tool._get_session("s1")
235235
assert result is mock_session
236236

237237
async def test_get_unknown_session_raises(self):
238238
tool = self._make_exec_tool()
239239
with pytest.raises(ValueError, match="unknown session_id"):
240-
await tool.get_session("nonexistent")
240+
await tool._get_session("nonexistent")
241241

242242
async def test_remove_session(self):
243243
tool = self._make_exec_tool()
244244
mock_session = MagicMock()
245245
mock_session.exited_at = None
246246
await tool._put_session("s1", mock_session)
247-
result = await tool.remove_session("s1")
247+
result = await tool._remove_session("s1")
248248
assert result is mock_session
249249

250250
async def test_remove_unknown_session_raises(self):
251251
tool = self._make_exec_tool()
252252
with pytest.raises(ValueError, match="unknown session_id"):
253-
await tool.remove_session("nonexistent")
253+
await tool._remove_session("nonexistent")
254254

255255
def test_declaration(self):
256256
tool = self._make_exec_tool()

tests/skills/tools/test_skill_list_tool.py

Lines changed: 5 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@
33
# Copyright (C) 2026 Tencent. All rights reserved.
44
#
55
# tRPC-Agent-Python is licensed under Apache-2.0.
6-
"""Unit tests for trpc_agent_sdk.skills.tools._skill_list_tool.
7-
8-
Covers:
9-
- _extract_shell_examples_from_skill_body: Command section parsing
10-
- skill_list_tools: returns tools and command examples
11-
- skill_list_tools: handles missing skill / repository
12-
"""
6+
"""Unit tests for trpc_agent_sdk.skills.tools._skill_list_tool."""
137

148
from __future__ import annotations
159

@@ -19,79 +13,10 @@
1913

2014
from trpc_agent_sdk.skills._types import Skill, SkillSummary
2115
from trpc_agent_sdk.skills.tools._skill_list_tool import (
22-
_extract_shell_examples_from_skill_body,
2316
skill_list_tools,
2417
)
2518

2619

27-
# ---------------------------------------------------------------------------
28-
# _extract_shell_examples_from_skill_body
29-
# ---------------------------------------------------------------------------
30-
31-
class TestExtractShellExamples:
32-
def test_empty_body(self):
33-
assert _extract_shell_examples_from_skill_body("") == []
34-
35-
def test_command_section(self):
36-
body = "Command:\n python scripts/run.py --input data.csv\n\nOverview"
37-
result = _extract_shell_examples_from_skill_body(body)
38-
assert len(result) >= 1
39-
assert "python scripts/run.py" in result[0]
40-
41-
def test_limit(self):
42-
body = ""
43-
for i in range(10):
44-
body += f"Command:\n cmd_{i} --arg\n\n"
45-
result = _extract_shell_examples_from_skill_body(body, limit=3)
46-
assert len(result) <= 3
47-
48-
def test_stops_at_section_break(self):
49-
body = "Command:\n python run.py\n\nOutput files\nMore content"
50-
result = _extract_shell_examples_from_skill_body(body)
51-
assert len(result) == 1
52-
53-
def test_multiline_command(self):
54-
body = "Command:\n python scripts/long.py \\\n --arg1 val1 \\\n --arg2 val2\n\n"
55-
result = _extract_shell_examples_from_skill_body(body)
56-
assert len(result) >= 1
57-
58-
def test_no_command_section(self):
59-
body = "# Overview\nJust a description.\n"
60-
result = _extract_shell_examples_from_skill_body(body)
61-
assert result == []
62-
63-
def test_deduplication(self):
64-
body = "Command:\n python run.py\n\nCommand:\n python run.py\n"
65-
result = _extract_shell_examples_from_skill_body(body)
66-
assert len(result) == 1
67-
68-
def test_rejects_non_command_starting_chars(self):
69-
body = "Command:\n !not_a_command\n\n"
70-
result = _extract_shell_examples_from_skill_body(body)
71-
assert result == []
72-
73-
def test_command_with_numbered_break(self):
74-
body = "Command:\n python run.py\n\n1) Next section\n"
75-
result = _extract_shell_examples_from_skill_body(body)
76-
assert len(result) == 1
77-
78-
def test_skips_empty_lines_before_command(self):
79-
body = "Command:\n\n\n python run.py\n\nEnd"
80-
result = _extract_shell_examples_from_skill_body(body)
81-
assert len(result) >= 1
82-
83-
def test_stops_at_tools_section(self):
84-
body = "Command:\n python run.py\n\ntools:\n- tool1"
85-
result = _extract_shell_examples_from_skill_body(body)
86-
assert len(result) == 1
87-
88-
def test_whitespace_normalization(self):
89-
body = "Command:\n python run.py --arg val\n\n"
90-
result = _extract_shell_examples_from_skill_body(body)
91-
assert len(result) == 1
92-
assert " " not in result[0]
93-
94-
9520
# ---------------------------------------------------------------------------
9621
# skill_list_tools
9722
# ---------------------------------------------------------------------------
@@ -103,7 +28,7 @@ def _make_ctx(repository=None):
10328

10429

10530
class TestSkillListTools:
106-
def test_returns_tools_and_examples(self):
31+
def test_returns_tools(self):
10732
skill = Skill(
10833
summary=SkillSummary(name="test"),
10934
body="Command:\n python run.py\n\nOverview",
@@ -114,16 +39,15 @@ def test_returns_tools_and_examples(self):
11439
ctx = _make_ctx(repository=repo)
11540

11641
result = skill_list_tools(ctx, "test")
117-
assert result["tools"] == ["get_weather", "get_data"]
118-
assert len(result["command_examples"]) >= 1
42+
assert result["available_tools"] == ["get_weather", "get_data"]
11943

12044
def test_skill_not_found(self):
12145
repo = MagicMock()
12246
repo.get = MagicMock(return_value=None)
12347
ctx = _make_ctx(repository=repo)
12448

12549
result = skill_list_tools(ctx, "nonexistent")
126-
assert result == {"tools": [], "command_examples": []}
50+
assert result == {"available_tools": []}
12751

12852
def test_no_repository_raises(self):
12953
ctx = _make_ctx(repository=None)
@@ -137,4 +61,4 @@ def test_no_tools_or_examples(self):
13761
ctx = _make_ctx(repository=repo)
13862

13963
result = skill_list_tools(ctx, "test")
140-
assert result["tools"] == []
64+
assert result["available_tools"] == []

0 commit comments

Comments
 (0)