knowlyr-core 0.1.0__tar.gz
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.
- knowlyr_core-0.1.0/.gitignore +32 -0
- knowlyr_core-0.1.0/PKG-INFO +29 -0
- knowlyr_core-0.1.0/pyproject.toml +47 -0
- knowlyr_core-0.1.0/src/knowlyrcore/__init__.py +59 -0
- knowlyr_core-0.1.0/src/knowlyrcore/domain.py +205 -0
- knowlyr_core-0.1.0/src/knowlyrcore/env.py +135 -0
- knowlyr_core-0.1.0/src/knowlyrcore/models.py +59 -0
- knowlyr_core-0.1.0/src/knowlyrcore/registry.py +117 -0
- knowlyr_core-0.1.0/src/knowlyrcore/timestep.py +34 -0
- knowlyr_core-0.1.0/src/knowlyrcore/wrappers.py +203 -0
- knowlyr_core-0.1.0/tests/test_domain.py +190 -0
- knowlyr_core-0.1.0/tests/test_env.py +235 -0
- knowlyr_core-0.1.0/tests/test_models.py +84 -0
- knowlyr_core-0.1.0/tests/test_registry.py +164 -0
- knowlyr_core-0.1.0/tests/test_wrappers.py +311 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.idea/
|
|
18
|
+
.vscode/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
|
|
22
|
+
# Testing
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.coverage
|
|
25
|
+
htmlcov/
|
|
26
|
+
|
|
27
|
+
# Claude Code
|
|
28
|
+
CLAUDE.md
|
|
29
|
+
|
|
30
|
+
# OS
|
|
31
|
+
.DS_Store
|
|
32
|
+
Thumbs.db
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: knowlyr-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared data models for knowlyr agent toolchain
|
|
5
|
+
Project-URL: Homepage, https://github.com/liuxiaotong/knowlyr-agent
|
|
6
|
+
Project-URL: Documentation, https://github.com/liuxiaotong/knowlyr-agent/tree/main/packages/core
|
|
7
|
+
Project-URL: Repository, https://github.com/liuxiaotong/knowlyr-agent
|
|
8
|
+
Project-URL: Issues, https://github.com/liuxiaotong/knowlyr-agent/issues
|
|
9
|
+
Author-email: Liu Kai <mrliukai@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: agent,code-agent,data-models,trajectory
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/plain
|
|
28
|
+
|
|
29
|
+
Shared data models for knowlyr agent toolchain.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "knowlyr-core"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Shared data models for knowlyr agent toolchain"
|
|
9
|
+
readme = {text = "Shared data models for knowlyr agent toolchain.", content-type = "text/plain"}
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Liu Kai", email = "mrliukai@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["agent", "data-models", "trajectory", "code-agent"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"pydantic>=2.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = ["pytest", "ruff"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/liuxiaotong/knowlyr-agent"
|
|
38
|
+
Documentation = "https://github.com/liuxiaotong/knowlyr-agent/tree/main/packages/core"
|
|
39
|
+
Repository = "https://github.com/liuxiaotong/knowlyr-agent"
|
|
40
|
+
Issues = "https://github.com/liuxiaotong/knowlyr-agent/issues"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/knowlyrcore"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
line-length = 100
|
|
47
|
+
target-version = "py310"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""knowlyr-core — 共享数据模型、领域配置与环境协议
|
|
2
|
+
|
|
3
|
+
提供 knowlyr 生态各子包共用的基础数据类型、领域 Profile、
|
|
4
|
+
Gymnasium 风格 AgentEnv 协议和环境注册表。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
from knowlyrcore.domain import (
|
|
10
|
+
BROWSER_PROFILE,
|
|
11
|
+
CODING_PROFILE,
|
|
12
|
+
GENERIC_PROFILE,
|
|
13
|
+
DomainProfile,
|
|
14
|
+
OutcomeSpec,
|
|
15
|
+
ToolCategory,
|
|
16
|
+
ToolSpec,
|
|
17
|
+
get_domain_profile,
|
|
18
|
+
list_domain_profiles,
|
|
19
|
+
load_domain_profile,
|
|
20
|
+
)
|
|
21
|
+
from knowlyrcore.env import AgentEnv, EnvWrapper
|
|
22
|
+
from knowlyrcore.models import TaskInfo, ToolResult
|
|
23
|
+
from knowlyrcore.registry import (
|
|
24
|
+
EnvSpec,
|
|
25
|
+
list_envs,
|
|
26
|
+
make,
|
|
27
|
+
register,
|
|
28
|
+
spec,
|
|
29
|
+
)
|
|
30
|
+
from knowlyrcore.timestep import TimeStep
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# 数据模型
|
|
34
|
+
"TaskInfo",
|
|
35
|
+
"ToolResult",
|
|
36
|
+
# 领域配置
|
|
37
|
+
"DomainProfile",
|
|
38
|
+
"ToolCategory",
|
|
39
|
+
"ToolSpec",
|
|
40
|
+
"OutcomeSpec",
|
|
41
|
+
"CODING_PROFILE",
|
|
42
|
+
"BROWSER_PROFILE",
|
|
43
|
+
"GENERIC_PROFILE",
|
|
44
|
+
"get_domain_profile",
|
|
45
|
+
"load_domain_profile",
|
|
46
|
+
"list_domain_profiles",
|
|
47
|
+
# 环境协议
|
|
48
|
+
"AgentEnv",
|
|
49
|
+
"EnvWrapper",
|
|
50
|
+
"TimeStep",
|
|
51
|
+
# 注册表
|
|
52
|
+
"EnvSpec",
|
|
53
|
+
"register",
|
|
54
|
+
"make",
|
|
55
|
+
"list_envs",
|
|
56
|
+
"spec",
|
|
57
|
+
# 版本
|
|
58
|
+
"__version__",
|
|
59
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""领域配置 — DomainProfile / ToolCategory / ToolSpec / OutcomeSpec.
|
|
2
|
+
|
|
3
|
+
通过 DomainProfile 声明工具分类和结果判定规则,让 pipeline 支持任意 tool-use agent 领域。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ToolCategory(str, Enum):
|
|
15
|
+
"""工具功能分类."""
|
|
16
|
+
|
|
17
|
+
READ = "read" # 读取/观察状态
|
|
18
|
+
WRITE = "write" # 修改状态
|
|
19
|
+
SEARCH = "search" # 搜索/发现
|
|
20
|
+
EXECUTE = "execute" # 执行命令
|
|
21
|
+
NAVIGATE = "navigate" # 切换上下文/位置
|
|
22
|
+
SUBMIT = "submit" # 提交/完成
|
|
23
|
+
THINK = "think" # 内部推理,无副作用
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ToolSpec(BaseModel):
|
|
27
|
+
"""工具规格定义.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
name: 工具主名称
|
|
31
|
+
category: 功能分类
|
|
32
|
+
stateful_key: 标识操作目标的参数名 (如 file_path / url / element_id)
|
|
33
|
+
aliases: 同一工具的别名列表
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
name: str
|
|
37
|
+
category: ToolCategory
|
|
38
|
+
stateful_key: str = ""
|
|
39
|
+
aliases: list[str] = Field(default_factory=list)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class OutcomeSpec(BaseModel):
|
|
43
|
+
"""结果判定规格.
|
|
44
|
+
|
|
45
|
+
定义如何从 outcome dict 中提取成功/分数信息。
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
success_field: 布尔成功字段名
|
|
49
|
+
score_field: 分数字段名 (如 tests_passed)
|
|
50
|
+
total_field: 总量字段名 (如 tests_total),与 score_field 配合计算比例
|
|
51
|
+
partial_credit_field: 手动部分得分字段名
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
success_field: str = "success"
|
|
55
|
+
score_field: str = ""
|
|
56
|
+
total_field: str = ""
|
|
57
|
+
partial_credit_field: str = "partial_credit"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DomainProfile(BaseModel):
|
|
61
|
+
"""领域配置.
|
|
62
|
+
|
|
63
|
+
声明式地描述一个 agent 领域的工具集、结果判定、任务字段等。
|
|
64
|
+
|
|
65
|
+
Attributes:
|
|
66
|
+
domain: 领域标识 (如 coding / browser / data_analysis)
|
|
67
|
+
display_name: 可读名称
|
|
68
|
+
tools: 该领域的工具列表
|
|
69
|
+
outcome_spec: 结果判定规则
|
|
70
|
+
task_fields: 领域特有的任务字段 {字段名: 说明}
|
|
71
|
+
default_rubric_weights: 默认 rubric 权重覆盖
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
domain: str
|
|
75
|
+
display_name: str = ""
|
|
76
|
+
tools: list[ToolSpec] = Field(default_factory=list)
|
|
77
|
+
outcome_spec: OutcomeSpec = Field(default_factory=OutcomeSpec)
|
|
78
|
+
task_fields: dict[str, str] = Field(default_factory=dict)
|
|
79
|
+
default_rubric_weights: dict[str, float] = Field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ============================================================
|
|
83
|
+
# 内置领域 Profiles
|
|
84
|
+
# ============================================================
|
|
85
|
+
|
|
86
|
+
CODING_PROFILE = DomainProfile(
|
|
87
|
+
domain="coding",
|
|
88
|
+
display_name="Code Agent",
|
|
89
|
+
tools=[
|
|
90
|
+
ToolSpec(
|
|
91
|
+
name="read_file", category=ToolCategory.READ,
|
|
92
|
+
stateful_key="file_path",
|
|
93
|
+
aliases=["Read", "cat"],
|
|
94
|
+
),
|
|
95
|
+
ToolSpec(
|
|
96
|
+
name="edit_file", category=ToolCategory.WRITE,
|
|
97
|
+
stateful_key="file_path",
|
|
98
|
+
aliases=["Edit", "sed", "write_file", "Write"],
|
|
99
|
+
),
|
|
100
|
+
ToolSpec(
|
|
101
|
+
name="bash", category=ToolCategory.EXECUTE,
|
|
102
|
+
aliases=["Bash", "shell", "run"],
|
|
103
|
+
),
|
|
104
|
+
ToolSpec(
|
|
105
|
+
name="grep", category=ToolCategory.SEARCH,
|
|
106
|
+
aliases=["Grep", "Glob", "find", "ls", "search"],
|
|
107
|
+
),
|
|
108
|
+
ToolSpec(name="git", category=ToolCategory.EXECUTE),
|
|
109
|
+
ToolSpec(name="ipython", category=ToolCategory.EXECUTE),
|
|
110
|
+
ToolSpec(name="submit", category=ToolCategory.SUBMIT),
|
|
111
|
+
ToolSpec(name="finish", category=ToolCategory.SUBMIT),
|
|
112
|
+
ToolSpec(name="think", category=ToolCategory.THINK),
|
|
113
|
+
],
|
|
114
|
+
outcome_spec=OutcomeSpec(
|
|
115
|
+
success_field="success",
|
|
116
|
+
score_field="tests_passed",
|
|
117
|
+
total_field="tests_total",
|
|
118
|
+
),
|
|
119
|
+
task_fields={
|
|
120
|
+
"repo": "Git 仓库 (owner/repo)",
|
|
121
|
+
"base_commit": "基础 commit hash",
|
|
122
|
+
"test_command": "测试命令",
|
|
123
|
+
"language": "编程语言",
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
BROWSER_PROFILE = DomainProfile(
|
|
128
|
+
domain="browser",
|
|
129
|
+
display_name="Browser Agent",
|
|
130
|
+
tools=[
|
|
131
|
+
ToolSpec(
|
|
132
|
+
name="click", category=ToolCategory.WRITE,
|
|
133
|
+
stateful_key="element_id",
|
|
134
|
+
aliases=["tap"],
|
|
135
|
+
),
|
|
136
|
+
ToolSpec(
|
|
137
|
+
name="type_text", category=ToolCategory.WRITE,
|
|
138
|
+
stateful_key="element_id",
|
|
139
|
+
aliases=["type", "fill", "input"],
|
|
140
|
+
),
|
|
141
|
+
ToolSpec(
|
|
142
|
+
name="navigate", category=ToolCategory.NAVIGATE,
|
|
143
|
+
stateful_key="url",
|
|
144
|
+
aliases=["goto", "open_url"],
|
|
145
|
+
),
|
|
146
|
+
ToolSpec(name="screenshot", category=ToolCategory.READ),
|
|
147
|
+
ToolSpec(
|
|
148
|
+
name="scroll", category=ToolCategory.NAVIGATE,
|
|
149
|
+
aliases=["scroll_up", "scroll_down"],
|
|
150
|
+
),
|
|
151
|
+
ToolSpec(name="wait", category=ToolCategory.READ),
|
|
152
|
+
ToolSpec(name="extract_text", category=ToolCategory.READ),
|
|
153
|
+
ToolSpec(name="select", category=ToolCategory.WRITE),
|
|
154
|
+
ToolSpec(name="submit", category=ToolCategory.SUBMIT),
|
|
155
|
+
],
|
|
156
|
+
outcome_spec=OutcomeSpec(success_field="success"),
|
|
157
|
+
task_fields={
|
|
158
|
+
"url": "目标 URL",
|
|
159
|
+
"expected_result": "预期结果描述",
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
GENERIC_PROFILE = DomainProfile(
|
|
164
|
+
domain="generic",
|
|
165
|
+
display_name="Generic Tool-Use Agent",
|
|
166
|
+
tools=[],
|
|
167
|
+
outcome_spec=OutcomeSpec(success_field="success"),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
_BUILTIN_PROFILES: dict[str, DomainProfile] = {
|
|
171
|
+
"coding": CODING_PROFILE,
|
|
172
|
+
"browser": BROWSER_PROFILE,
|
|
173
|
+
"generic": GENERIC_PROFILE,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_domain_profile(domain: str) -> DomainProfile:
|
|
178
|
+
"""获取内置领域 Profile.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
domain: 领域标识 (coding / browser / generic)
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
对应的 DomainProfile,未找到时返回 GENERIC_PROFILE
|
|
185
|
+
"""
|
|
186
|
+
return _BUILTIN_PROFILES.get(domain, GENERIC_PROFILE)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def load_domain_profile(path: str) -> DomainProfile:
|
|
190
|
+
"""从 JSON 文件加载自定义 DomainProfile.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
path: JSON 文件路径
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
DomainProfile 实例
|
|
197
|
+
"""
|
|
198
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
199
|
+
data = json.load(f)
|
|
200
|
+
return DomainProfile.model_validate(data)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def list_domain_profiles() -> list[str]:
|
|
204
|
+
"""列出所有内置领域名称."""
|
|
205
|
+
return list(_BUILTIN_PROFILES.keys())
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""AgentEnv — Gymnasium 风格 Agent 环境协议.
|
|
2
|
+
|
|
3
|
+
借鉴 Gymnasium Env + BrowserGym + AgentGym 的核心模式:
|
|
4
|
+
- reset() → TimeStep
|
|
5
|
+
- step(action) → TimeStep
|
|
6
|
+
- close()
|
|
7
|
+
- Wrapper 可组合
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from knowlyrcore.timestep import TimeStep
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AgentEnv(ABC):
|
|
19
|
+
"""Agent 环境协议.
|
|
20
|
+
|
|
21
|
+
所有环境(Docker 沙箱、浏览器、API mock 等)都实现此接口。
|
|
22
|
+
action 格式统一为 ``{"tool": "...", "params": {...}}``。
|
|
23
|
+
|
|
24
|
+
Usage::
|
|
25
|
+
|
|
26
|
+
env = SandboxEnv(config)
|
|
27
|
+
ts = env.reset(task=my_task)
|
|
28
|
+
while not ts.done:
|
|
29
|
+
action = agent.act(ts.observation)
|
|
30
|
+
ts = env.step(action)
|
|
31
|
+
env.close()
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
metadata: dict[str, Any] = {}
|
|
35
|
+
domain: str = "coding"
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def reset(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
task: Any | None = None,
|
|
42
|
+
seed: int | None = None,
|
|
43
|
+
) -> TimeStep:
|
|
44
|
+
"""重置环境到初始状态.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
task: 任务信息 (TaskInfo 或兼容 dict)
|
|
48
|
+
seed: 随机种子(用于可复现性)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
初始 TimeStep
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def step(self, action: dict[str, Any]) -> TimeStep:
|
|
57
|
+
"""执行一步动作.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
action: 动作字典,格式 {"tool": "...", "params": {...}}
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
执行后的 TimeStep
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def close(self) -> None:
|
|
68
|
+
"""清理资源(默认空实现)."""
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def available_tools(self) -> list[str]:
|
|
72
|
+
"""当前可用的工具/动作名列表."""
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def unwrapped(self) -> AgentEnv:
|
|
77
|
+
"""返回最内层非 Wrapper 环境."""
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def __enter__(self) -> AgentEnv:
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def __exit__(self, *args: Any) -> None:
|
|
84
|
+
self.close()
|
|
85
|
+
|
|
86
|
+
def __repr__(self) -> str:
|
|
87
|
+
return f"<{self.__class__.__name__} domain={self.domain!r}>"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class EnvWrapper(AgentEnv):
|
|
91
|
+
"""环境包装器基类 — 对应 gymnasium.Wrapper.
|
|
92
|
+
|
|
93
|
+
透传所有方法到内部环境,子类只需覆盖想要修改的方法。
|
|
94
|
+
|
|
95
|
+
Usage::
|
|
96
|
+
|
|
97
|
+
class MyWrapper(EnvWrapper):
|
|
98
|
+
def step(self, action):
|
|
99
|
+
ts = self.env.step(action)
|
|
100
|
+
ts.reward = compute_reward(ts)
|
|
101
|
+
return ts
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, env: AgentEnv):
|
|
105
|
+
self.env = env
|
|
106
|
+
|
|
107
|
+
def reset(self, **kwargs: Any) -> TimeStep:
|
|
108
|
+
"""透传 reset."""
|
|
109
|
+
return self.env.reset(**kwargs)
|
|
110
|
+
|
|
111
|
+
def step(self, action: dict[str, Any]) -> TimeStep:
|
|
112
|
+
"""透传 step."""
|
|
113
|
+
return self.env.step(action)
|
|
114
|
+
|
|
115
|
+
def close(self) -> None:
|
|
116
|
+
"""透传 close."""
|
|
117
|
+
self.env.close()
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def available_tools(self) -> list[str]:
|
|
121
|
+
"""透传 available_tools."""
|
|
122
|
+
return self.env.available_tools
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def unwrapped(self) -> AgentEnv:
|
|
126
|
+
"""递归返回最内层环境."""
|
|
127
|
+
return self.env.unwrapped
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def domain(self) -> str: # type: ignore[override]
|
|
131
|
+
"""透传 domain."""
|
|
132
|
+
return self.env.domain
|
|
133
|
+
|
|
134
|
+
def __repr__(self) -> str:
|
|
135
|
+
return f"<{self.__class__.__name__}({self.env!r})>"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""共享数据模型 — ToolResult / TaskInfo."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolResult(BaseModel):
|
|
11
|
+
"""工具执行结果.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
output: 标准输出内容
|
|
15
|
+
exit_code: 退出码 (0 表示成功)
|
|
16
|
+
error: 错误信息 (如果有)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
output: str = ""
|
|
20
|
+
exit_code: int = 0
|
|
21
|
+
error: str | None = None
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def success(self) -> bool:
|
|
25
|
+
"""执行是否成功."""
|
|
26
|
+
return self.exit_code == 0 and self.error is None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TaskInfo(BaseModel):
|
|
30
|
+
"""任务信息.
|
|
31
|
+
|
|
32
|
+
描述一个待执行的任务,支持任意领域。coding 特有字段(repo/base_commit 等)
|
|
33
|
+
保留供向后兼容,非 coding 领域可忽略。
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
task_id: 任务唯一标识
|
|
37
|
+
description: 任务描述
|
|
38
|
+
type: 任务类型 (如 bug_fix, feature, qa_check)
|
|
39
|
+
language: 编程语言 (coding 领域)
|
|
40
|
+
difficulty: 难度等级 (easy / medium / hard)
|
|
41
|
+
repo: 目标仓库 (coding 领域, 如 "owner/repo")
|
|
42
|
+
base_commit: 基础 commit hash (coding 领域)
|
|
43
|
+
test_command: 测试命令 (coding 领域)
|
|
44
|
+
domain: 所属领域 (coding / browser / generic / 自定义)
|
|
45
|
+
success_criteria: 成功判定描述 (领域无关)
|
|
46
|
+
metadata: 额外元数据
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
task_id: str = ""
|
|
50
|
+
description: str = ""
|
|
51
|
+
type: str = ""
|
|
52
|
+
language: str = ""
|
|
53
|
+
difficulty: str = ""
|
|
54
|
+
repo: str = ""
|
|
55
|
+
base_commit: str = ""
|
|
56
|
+
test_command: str = ""
|
|
57
|
+
domain: str = ""
|
|
58
|
+
success_criteria: str = ""
|
|
59
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""环境注册表 — knowlyr.make() 发现机制.
|
|
2
|
+
|
|
3
|
+
借鉴 gymnasium.register() / gymnasium.make() 模式,
|
|
4
|
+
命名规范沿用 "namespace/env-name" 格式。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from knowlyrcore.env import AgentEnv
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class EnvSpec:
|
|
17
|
+
"""环境规格 — 对应 gymnasium.EnvSpec.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
id: 环境标识 (如 "knowlyr/sandbox", "swebench/django-11099")
|
|
21
|
+
env_cls: 环境类
|
|
22
|
+
kwargs: 默认构造参数
|
|
23
|
+
domain: 领域标识
|
|
24
|
+
description: 环境描述
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
id: str
|
|
28
|
+
env_cls: type[AgentEnv]
|
|
29
|
+
kwargs: dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
domain: str = "coding"
|
|
31
|
+
description: str = ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_REGISTRY: dict[str, EnvSpec] = {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def register(
|
|
38
|
+
id: str,
|
|
39
|
+
env_cls: type[AgentEnv],
|
|
40
|
+
*,
|
|
41
|
+
domain: str = "coding",
|
|
42
|
+
description: str = "",
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""注册一个环境.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
id: 环境标识 (如 "knowlyr/sandbox")
|
|
49
|
+
env_cls: 环境类(必须继承 AgentEnv)
|
|
50
|
+
domain: 领域标识
|
|
51
|
+
description: 环境描述
|
|
52
|
+
**kwargs: 默认构造参数
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: 环境 ID 已注册
|
|
56
|
+
"""
|
|
57
|
+
if id in _REGISTRY:
|
|
58
|
+
raise ValueError(f"环境已注册: {id!r}")
|
|
59
|
+
_REGISTRY[id] = EnvSpec(
|
|
60
|
+
id=id,
|
|
61
|
+
env_cls=env_cls,
|
|
62
|
+
kwargs=kwargs,
|
|
63
|
+
domain=domain,
|
|
64
|
+
description=description,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def make(id: str, **override_kwargs: Any) -> AgentEnv:
|
|
69
|
+
"""创建环境实例 — 对应 gymnasium.make().
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
id: 环境标识
|
|
73
|
+
**override_kwargs: 覆盖默认构造参数
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
AgentEnv 实例
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
KeyError: 环境未注册
|
|
80
|
+
"""
|
|
81
|
+
if id not in _REGISTRY:
|
|
82
|
+
available = ", ".join(sorted(_REGISTRY.keys())) or "(空)"
|
|
83
|
+
raise KeyError(f"未注册的环境: {id!r}。可用: {available}")
|
|
84
|
+
env_spec = _REGISTRY[id]
|
|
85
|
+
merged_kwargs = {**env_spec.kwargs, **override_kwargs}
|
|
86
|
+
return env_spec.env_cls(**merged_kwargs)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def list_envs(domain: str | None = None) -> list[str]:
|
|
90
|
+
"""列出已注册的环境 ID.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
domain: 按领域过滤(None 返回全部)
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
环境 ID 列表
|
|
97
|
+
"""
|
|
98
|
+
if domain is None:
|
|
99
|
+
return sorted(_REGISTRY.keys())
|
|
100
|
+
return sorted(k for k, v in _REGISTRY.items() if v.domain == domain)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def spec(id: str) -> EnvSpec | None:
|
|
104
|
+
"""获取环境规格.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
id: 环境标识
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
EnvSpec 或 None
|
|
111
|
+
"""
|
|
112
|
+
return _REGISTRY.get(id)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _clear_registry() -> None:
|
|
116
|
+
"""清空注册表(仅用于测试)."""
|
|
117
|
+
_REGISTRY.clear()
|