xskill 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. xskill/__init__.py +29 -0
  2. xskill/adapters.py +183 -0
  3. xskill/agent.py +506 -0
  4. xskill/canary.py +458 -0
  5. xskill/candidates.py +554 -0
  6. xskill/cli.py +154 -0
  7. xskill/config.py +130 -0
  8. xskill/core.py +173 -0
  9. xskill/download_data.py +898 -0
  10. xskill/entities/__init__.py +9 -0
  11. xskill/entities/evaluator.py +78 -0
  12. xskill/entities/registry.py +97 -0
  13. xskill/entities/skill.py +212 -0
  14. xskill/entities/skill_repo.py +79 -0
  15. xskill/entities/trajectory.py +94 -0
  16. xskill/frontmatter.py +121 -0
  17. xskill/git_lock.py +79 -0
  18. xskill/index.py +585 -0
  19. xskill/llm_client.py +271 -0
  20. xskill/log.py +29 -0
  21. xskill/migrate.py +247 -0
  22. xskill/process.py +329 -0
  23. xskill/registry.py +481 -0
  24. xskill/sandbox/__init__.py +14 -0
  25. xskill/sandbox/agent_template.py +186 -0
  26. xskill/sandbox/base.py +249 -0
  27. xskill/sandbox/registry.py +31 -0
  28. xskill/sandbox/swe_smith.py +320 -0
  29. xskill/search.py +323 -0
  30. xskill/server.py +1391 -0
  31. xskill/skill_eval.py +402 -0
  32. xskill/skill_manager.py +262 -0
  33. xskill/skill_tools.py +616 -0
  34. xskill/tasks.py +455 -0
  35. xskill/traj_meta.py +41 -0
  36. xskill/types.py +92 -0
  37. xskill/ux_score.py +175 -0
  38. xskill/watcher.py +342 -0
  39. xskill/web/dist/assets/index-CWcPd2On.js +86 -0
  40. xskill/web/dist/assets/index-m9mjShnV.css +1 -0
  41. xskill/web/dist/index.html +13 -0
  42. xskill-0.3.0.dist-info/METADATA +259 -0
  43. xskill-0.3.0.dist-info/RECORD +47 -0
  44. xskill-0.3.0.dist-info/WHEEL +5 -0
  45. xskill-0.3.0.dist-info/entry_points.txt +2 -0
  46. xskill-0.3.0.dist-info/licenses/LICENSE +21 -0
  47. xskill-0.3.0.dist-info/top_level.txt +1 -0
xskill/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """xskill — 从 AI Agent 执行轨迹自动蒸馏可复用 Skill。
2
+
3
+ 公开 SDK:
4
+ from xskill import XSkill, Skill, Trajectory, Evaluator
5
+ from xskill.types import (
6
+ WatchDir, SkillHit, TrajectoryHit,
7
+ EvalScore, Candidate, UxScoreResult,
8
+ )
9
+
10
+ 进阶(少数场景,例如单测直接拿子系统):
11
+ from xskill import Registry, SkillRepo
12
+ """
13
+
14
+ __version__ = "0.3.0"
15
+
16
+ # 顶级公开面:4 个核心类
17
+ from xskill.core import XSkill
18
+ from xskill.entities.skill import Skill
19
+ from xskill.entities.trajectory import Trajectory
20
+ from xskill.entities.evaluator import Evaluator
21
+
22
+ # 进阶:子系统类(不必常用)
23
+ from xskill.entities.registry import Registry
24
+ from xskill.entities.skill_repo import SkillRepo
25
+
26
+ __all__ = [
27
+ "XSkill", "Skill", "Trajectory", "Evaluator",
28
+ "Registry", "SkillRepo",
29
+ ]
xskill/adapters.py ADDED
@@ -0,0 +1,183 @@
1
+ """
2
+ adapters.py -- Trajectory submission and format adaptation layer
3
+ =================================================================
4
+ Convert various input formats to the standard xskill format
5
+ (markdown + json metadata) and handle submission to the traj directory.
6
+ """
7
+
8
+ import json
9
+ import re
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ from xskill.config import get_traj_dir
14
+
15
+
16
+ def generate_traj_id(traj_dir: Path = None) -> str:
17
+ """
18
+ Auto-generate a traj ID like ``traj_0301`` based on existing files
19
+ in *traj_dir*. Scans for ``traj_*.md`` and picks max + 1.
20
+ """
21
+ traj_dir = traj_dir or get_traj_dir()
22
+ traj_dir.mkdir(parents=True, exist_ok=True)
23
+
24
+ existing_ids: list[int] = []
25
+ for f in traj_dir.glob("traj_*.md"):
26
+ m = re.match(r"traj_(\d+)", f.stem)
27
+ if m:
28
+ existing_ids.append(int(m.group(1)))
29
+
30
+ next_id = max(existing_ids) + 1 if existing_ids else 1
31
+ return f"traj_{next_id:04d}"
32
+
33
+
34
+ def adapt_trajectory(
35
+ content: str,
36
+ format: str,
37
+ metadata: Optional[dict] = None,
38
+ ) -> tuple[str, dict]:
39
+ """
40
+ Convert various input formats to the standard xskill representation.
41
+
42
+ Supported *format* values:
43
+
44
+ - ``markdown`` -- passthrough; content is already ``traj_*.md`` format.
45
+ - ``json`` -- JSON object with fields like ``messages``, ``tool_calls``, etc.
46
+ Converted to a markdown trajectory.
47
+ - ``raw`` -- plain text; wrapped in a basic trajectory markdown template.
48
+
49
+ Returns ``(md_content, json_metadata)``.
50
+ """
51
+ metadata = metadata or {}
52
+
53
+ if format == "markdown":
54
+ return content, metadata
55
+
56
+ if format == "json":
57
+ return _adapt_json(content, metadata)
58
+
59
+ if format == "raw":
60
+ return _adapt_raw(content, metadata)
61
+
62
+ raise ValueError(f"unsupported trajectory format: {format!r}")
63
+
64
+
65
+ # ------------------------------------------------------------------
66
+ # Internal converters
67
+ # ------------------------------------------------------------------
68
+
69
+ def _adapt_json(content: str, metadata: dict) -> tuple[str, dict]:
70
+ """Convert a JSON trajectory to markdown + metadata."""
71
+ data = json.loads(content)
72
+
73
+ # Merge top-level keys (except messages/tool_calls) into metadata
74
+ meta = dict(metadata)
75
+ for key in ("model", "instance_id", "repo", "task", "result", "exit_status"):
76
+ if key in data and key not in meta:
77
+ meta[key] = data[key]
78
+
79
+ # Build markdown from messages / tool_calls
80
+ lines: list[str] = []
81
+ lines.append(f"# Trajectory")
82
+ if meta.get("instance_id"):
83
+ lines.append(f"\n**instance_id**: {meta['instance_id']}")
84
+ if meta.get("model"):
85
+ lines.append(f"**model**: {meta['model']}")
86
+ lines.append("")
87
+
88
+ messages = data.get("messages", [])
89
+ tool_calls = data.get("tool_calls", [])
90
+
91
+ for msg in messages:
92
+ role = msg.get("role", "unknown")
93
+ text = msg.get("content", "")
94
+ lines.append(f"## {role.capitalize()}")
95
+ lines.append("")
96
+ if isinstance(text, str):
97
+ lines.append(text)
98
+ elif isinstance(text, list):
99
+ # multi-part content
100
+ for part in text:
101
+ if isinstance(part, dict):
102
+ lines.append(part.get("text", str(part)))
103
+ else:
104
+ lines.append(str(part))
105
+ lines.append("")
106
+
107
+ if tool_calls:
108
+ lines.append("## Tool Calls")
109
+ lines.append("")
110
+ for tc in tool_calls:
111
+ name = tc.get("name", tc.get("function", {}).get("name", "unknown"))
112
+ args = tc.get("arguments", tc.get("function", {}).get("arguments", ""))
113
+ lines.append(f"### {name}")
114
+ lines.append("```")
115
+ lines.append(args if isinstance(args, str) else json.dumps(args, ensure_ascii=False))
116
+ lines.append("```")
117
+ if tc.get("output"):
118
+ lines.append(f"\n**output**:\n```\n{tc['output']}\n```")
119
+ lines.append("")
120
+
121
+ md_content = "\n".join(lines)
122
+ return md_content, meta
123
+
124
+
125
+ def _adapt_raw(content: str, metadata: dict) -> tuple[str, dict]:
126
+ """Wrap plain text in a basic trajectory markdown template."""
127
+ lines = [
128
+ "# Trajectory",
129
+ "",
130
+ "## Raw Content",
131
+ "",
132
+ content,
133
+ "",
134
+ ]
135
+ md_content = "\n".join(lines)
136
+ return md_content, dict(metadata)
137
+
138
+
139
+ # ------------------------------------------------------------------
140
+ # Submission
141
+ # ------------------------------------------------------------------
142
+
143
+ def submit_trajectory(
144
+ content: str,
145
+ format: str = "markdown",
146
+ metadata: Optional[dict] = None,
147
+ traj_id: Optional[str] = None,
148
+ traj_dir: Optional[Path] = None,
149
+ ) -> dict:
150
+ """
151
+ Complete submission flow:
152
+
153
+ 1. Resolve *traj_dir* (from param or ``get_traj_dir()``).
154
+ 2. Generate *traj_id* if not provided.
155
+ 3. Adapt the input format to standard markdown + JSON metadata.
156
+ 4. Write ``traj_{id}.md`` and optionally ``traj_{id}.json``.
157
+ 5. Return ``{"traj_id": ..., "path": ..., "status": "stored"}``.
158
+ """
159
+ traj_dir = Path(traj_dir) if traj_dir else get_traj_dir()
160
+ traj_dir.mkdir(parents=True, exist_ok=True)
161
+
162
+ if not traj_id:
163
+ traj_id = generate_traj_id(traj_dir)
164
+
165
+ md_content, json_metadata = adapt_trajectory(content, format, metadata)
166
+
167
+ # Write markdown
168
+ md_path = traj_dir / f"{traj_id}.md"
169
+ md_path.write_text(md_content, encoding="utf-8")
170
+
171
+ # Write JSON metadata if non-empty
172
+ if json_metadata:
173
+ json_path = traj_dir / f"{traj_id}.json"
174
+ json_path.write_text(
175
+ json.dumps(json_metadata, ensure_ascii=False, indent=2),
176
+ encoding="utf-8",
177
+ )
178
+
179
+ return {
180
+ "traj_id": traj_id,
181
+ "path": str(md_path),
182
+ "status": "stored",
183
+ }