base-agentkit 0.1.0__tar.gz → 0.1.2__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.
Files changed (53) hide show
  1. base_agentkit-0.1.2/PKG-INFO +159 -0
  2. base_agentkit-0.1.2/README.md +132 -0
  3. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/pyproject.toml +2 -2
  4. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/agent/agent.py +3 -2
  5. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/config/loader.py +17 -1
  6. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/config/schema.py +11 -0
  7. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/constants.py +4 -2
  8. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/__init__.py +2 -1
  9. base_agentkit-0.1.2/src/agentkit/tools/loader.py +246 -0
  10. base_agentkit-0.1.0/PKG-INFO +0 -142
  11. base_agentkit-0.1.0/README.md +0 -115
  12. base_agentkit-0.1.0/src/agentkit/tools/loader.py +0 -81
  13. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/LICENSE +0 -0
  14. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/__init__.py +0 -0
  15. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/agent/__init__.py +0 -0
  16. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/agent/budgets.py +0 -0
  17. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/agent/report.py +0 -0
  18. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/agent/tool_runtime.py +0 -0
  19. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/cli/__init__.py +0 -0
  20. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/cli/main.py +0 -0
  21. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/config/__init__.py +0 -0
  22. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/config/provider_defaults.py +0 -0
  23. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/errors.py +0 -0
  24. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/__init__.py +0 -0
  25. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/base.py +0 -0
  26. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/factory.py +0 -0
  27. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/providers/__init__.py +0 -0
  28. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/providers/anthropic_provider.py +0 -0
  29. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/providers/gemini_provider.py +0 -0
  30. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/providers/openai_provider.py +0 -0
  31. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/providers/qwen_provider.py +0 -0
  32. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/providers/vllm_provider.py +0 -0
  33. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/types.py +0 -0
  34. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/llm/usage.py +0 -0
  35. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/py.typed +0 -0
  36. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/runlog/__init__.py +0 -0
  37. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/runlog/events.py +0 -0
  38. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/runlog/jsonl.py +0 -0
  39. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/runlog/recorder.py +0 -0
  40. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/runlog/sinks.py +0 -0
  41. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/base.py +0 -0
  42. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/library/__init__.py +0 -0
  43. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/library/_fs_common.py +0 -0
  44. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/library/create_file.py +0 -0
  45. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/library/fs_tools.py +0 -0
  46. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/library/str_replace.py +0 -0
  47. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/library/view.py +0 -0
  48. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/library/word_count.py +0 -0
  49. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/registry.py +0 -0
  50. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/tools/types.py +0 -0
  51. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/workspace/__init__.py +0 -0
  52. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/workspace/fs.py +0 -0
  53. {base_agentkit-0.1.0 → base_agentkit-0.1.2}/src/agentkit/workspace/layout.py +0 -0
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: base-agentkit
3
+ Version: 0.1.2
4
+ Summary: Workspace-isolated Python framework for building tool-using LLM agents.
5
+ Keywords: agents,llm,tool-calling,workspace,ai
6
+ Author: sii-nyc
7
+ Author-email: sii-nyc <253108120111@sii.edu.cn>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: openai>=2.21.0
18
+ Requires-Dist: pydantic>=2.12.5
19
+ Requires-Dist: pyyaml>=6.0.3
20
+ Requires-Dist: requests>=2.32.5
21
+ Requires-Python: >=3.12
22
+ Project-URL: Homepage, https://github.com/Inkjourney/agentkit
23
+ Project-URL: Repository, https://github.com/Inkjourney/agentkit
24
+ Project-URL: Documentation, https://inkjourney.github.io/agentkit/
25
+ Project-URL: Issues, https://github.com/Inkjourney/agentkit/issues
26
+ Description-Content-Type: text/markdown
27
+
28
+ # AgentKit
29
+
30
+ [中文版本](docs/README.zh-CN.md)
31
+
32
+ AgentKit is a general-purpose framework for tool-using LLM agents. It provides
33
+ workspace isolation, a unified LLM abstraction, a tool-execution loop, and both
34
+ CLI and Python SDK entry points.
35
+
36
+ ## Current Capabilities
37
+
38
+ - Strong workspace isolation: all agent-visible file access goes through
39
+ `WorkspaceFS`
40
+ - Unified LLM abstraction:
41
+ `ConversationItem` / `ConversationState` / `UnifiedLLMRequest` /
42
+ `UnifiedLLMResponse`
43
+ - Provider support (sync, non-streaming, text)
44
+ - OpenAI (`responses` / `chat_completions`)
45
+ - Anthropic (`messages`)
46
+ - Gemini (`generateContent`)
47
+ - Qwen (OpenAI-compatible `chat_completions`)
48
+ - vLLM (OpenAI-compatible `chat_completions`)
49
+ - Registry-based tool system with built-in tools under
50
+ `src/agentkit/tools/library/` and custom tool loading from project
51
+ file/directory entries
52
+ - Agent loop that coordinates model reasoning and tool execution under step/time
53
+ budgets
54
+ - Run logs written to `workspace/logs/run_<run_id>.jsonl`
55
+
56
+ ## Status
57
+
58
+ - The repository includes unit tests for the agent loop, CLI, tool system,
59
+ runlog, and the OpenAI / Anthropic / Gemini / Qwen / vLLM provider adapters
60
+ - There is no smoke-test coverage against live upstream APIs yet, so a final
61
+ end-to-end validation against the target model is still recommended before
62
+ production use
63
+
64
+ ## Documentation
65
+
66
+ Full documentation lives under `docs/agentkit/`.
67
+ The published site is [GitHub Pages](https://inkjourney.github.io/agentkit/) and
68
+ deploys automatically from pushes to `main`.
69
+
70
+ ## Install From Source
71
+
72
+ ```bash
73
+ uv sync
74
+ uv run agentkit --help
75
+ ```
76
+
77
+ ## Install From PyPI
78
+
79
+ ```bash
80
+ pip install base-agentkit
81
+ agentkit --help
82
+ ```
83
+
84
+ The import path and CLI command stay the same after installation:
85
+
86
+ ```python
87
+ from agentkit import create_agent
88
+ ```
89
+
90
+ ## Example Configuration
91
+
92
+ ### vLLM
93
+
94
+ ```yaml
95
+ workspace:
96
+ root: "./vllm_workspace"
97
+
98
+ provider:
99
+ kind: "vllm"
100
+ model: "glm-5"
101
+ openai_api_variant: "chat_completions"
102
+ conversation_mode: "auto"
103
+ base_url: "http://localhost:8000/v1"
104
+ api_key: "empty"
105
+ temperature: 0.8
106
+ timeout_s: 600
107
+ retries: 2
108
+ enable_thinking: true
109
+
110
+ agent:
111
+ system_prompt: "You are a helpful agent. Use tools when needed."
112
+ budget:
113
+ max_steps: 200
114
+ time_budget_s: 1800
115
+ max_input_chars: 180000
116
+
117
+ tools:
118
+ entries:
119
+ - "./tools"
120
+ allowed:
121
+ - "view"
122
+ - "create_file"
123
+ - "str_replace"
124
+ - "word_count"
125
+
126
+ runlog:
127
+ enabled: true
128
+ redact: true
129
+ max_text_chars: 20000
130
+ ```
131
+
132
+ ## Run From The CLI
133
+
134
+ ```bash
135
+ export OPENAI_API_KEY="your-key"
136
+ uv run agentkit --config path/to/config.yaml run --task "List the files in the current workspace"
137
+ ```
138
+
139
+ ## Python SDK
140
+
141
+ ```python
142
+ from agentkit import create_agent
143
+
144
+ agent = create_agent("path/to/config.yaml")
145
+ report = agent.run("Create notes/todo.txt in the workspace and write today's plan")
146
+ print(report.final_output)
147
+ ```
148
+
149
+ ## Project Layout
150
+
151
+ The core implementation lives under `src/agentkit/`:
152
+
153
+ - `config/`: configuration schema and loader
154
+ - `workspace/`: strict filesystem isolation and workspace layout
155
+ - `llm/`: unified abstractions, provider base/factory, and adapters
156
+ - `tools/`: tool abstractions, registry, and autoloading
157
+ - `agent/`: budgets, reports, runtime, and the agent loop
158
+ - `runlog/`: structured events, event sinks, and JSONL runlogs
159
+ - `cli/`: command-line entry point
@@ -0,0 +1,132 @@
1
+ # AgentKit
2
+
3
+ [中文版本](docs/README.zh-CN.md)
4
+
5
+ AgentKit is a general-purpose framework for tool-using LLM agents. It provides
6
+ workspace isolation, a unified LLM abstraction, a tool-execution loop, and both
7
+ CLI and Python SDK entry points.
8
+
9
+ ## Current Capabilities
10
+
11
+ - Strong workspace isolation: all agent-visible file access goes through
12
+ `WorkspaceFS`
13
+ - Unified LLM abstraction:
14
+ `ConversationItem` / `ConversationState` / `UnifiedLLMRequest` /
15
+ `UnifiedLLMResponse`
16
+ - Provider support (sync, non-streaming, text)
17
+ - OpenAI (`responses` / `chat_completions`)
18
+ - Anthropic (`messages`)
19
+ - Gemini (`generateContent`)
20
+ - Qwen (OpenAI-compatible `chat_completions`)
21
+ - vLLM (OpenAI-compatible `chat_completions`)
22
+ - Registry-based tool system with built-in tools under
23
+ `src/agentkit/tools/library/` and custom tool loading from project
24
+ file/directory entries
25
+ - Agent loop that coordinates model reasoning and tool execution under step/time
26
+ budgets
27
+ - Run logs written to `workspace/logs/run_<run_id>.jsonl`
28
+
29
+ ## Status
30
+
31
+ - The repository includes unit tests for the agent loop, CLI, tool system,
32
+ runlog, and the OpenAI / Anthropic / Gemini / Qwen / vLLM provider adapters
33
+ - There is no smoke-test coverage against live upstream APIs yet, so a final
34
+ end-to-end validation against the target model is still recommended before
35
+ production use
36
+
37
+ ## Documentation
38
+
39
+ Full documentation lives under `docs/agentkit/`.
40
+ The published site is [GitHub Pages](https://inkjourney.github.io/agentkit/) and
41
+ deploys automatically from pushes to `main`.
42
+
43
+ ## Install From Source
44
+
45
+ ```bash
46
+ uv sync
47
+ uv run agentkit --help
48
+ ```
49
+
50
+ ## Install From PyPI
51
+
52
+ ```bash
53
+ pip install base-agentkit
54
+ agentkit --help
55
+ ```
56
+
57
+ The import path and CLI command stay the same after installation:
58
+
59
+ ```python
60
+ from agentkit import create_agent
61
+ ```
62
+
63
+ ## Example Configuration
64
+
65
+ ### vLLM
66
+
67
+ ```yaml
68
+ workspace:
69
+ root: "./vllm_workspace"
70
+
71
+ provider:
72
+ kind: "vllm"
73
+ model: "glm-5"
74
+ openai_api_variant: "chat_completions"
75
+ conversation_mode: "auto"
76
+ base_url: "http://localhost:8000/v1"
77
+ api_key: "empty"
78
+ temperature: 0.8
79
+ timeout_s: 600
80
+ retries: 2
81
+ enable_thinking: true
82
+
83
+ agent:
84
+ system_prompt: "You are a helpful agent. Use tools when needed."
85
+ budget:
86
+ max_steps: 200
87
+ time_budget_s: 1800
88
+ max_input_chars: 180000
89
+
90
+ tools:
91
+ entries:
92
+ - "./tools"
93
+ allowed:
94
+ - "view"
95
+ - "create_file"
96
+ - "str_replace"
97
+ - "word_count"
98
+
99
+ runlog:
100
+ enabled: true
101
+ redact: true
102
+ max_text_chars: 20000
103
+ ```
104
+
105
+ ## Run From The CLI
106
+
107
+ ```bash
108
+ export OPENAI_API_KEY="your-key"
109
+ uv run agentkit --config path/to/config.yaml run --task "List the files in the current workspace"
110
+ ```
111
+
112
+ ## Python SDK
113
+
114
+ ```python
115
+ from agentkit import create_agent
116
+
117
+ agent = create_agent("path/to/config.yaml")
118
+ report = agent.run("Create notes/todo.txt in the workspace and write today's plan")
119
+ print(report.final_output)
120
+ ```
121
+
122
+ ## Project Layout
123
+
124
+ The core implementation lives under `src/agentkit/`:
125
+
126
+ - `config/`: configuration schema and loader
127
+ - `workspace/`: strict filesystem isolation and workspace layout
128
+ - `llm/`: unified abstractions, provider base/factory, and adapters
129
+ - `tools/`: tool abstractions, registry, and autoloading
130
+ - `agent/`: budgets, reports, runtime, and the agent loop
131
+ - `runlog/`: structured events, event sinks, and JSONL runlogs
132
+ - `cli/`: command-line entry point
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "base-agentkit"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "Workspace-isolated Python framework for building tool-using LLM agents."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sii-nyc", email = "253108120111@sii.edu.cn" }]
@@ -30,7 +30,7 @@ agentkit = "agentkit.cli.main:main"
30
30
  [project.urls]
31
31
  Homepage = "https://github.com/Inkjourney/agentkit"
32
32
  Repository = "https://github.com/Inkjourney/agentkit"
33
- Documentation = "https://github.com/Inkjourney/agentkit/tree/main/docs/agentkit"
33
+ Documentation = "https://inkjourney.github.io/agentkit/"
34
34
  Issues = "https://github.com/Inkjourney/agentkit/issues"
35
35
 
36
36
  [dependency-groups]
@@ -25,7 +25,7 @@ from agentkit.llm.types import (
25
25
  )
26
26
  from agentkit.llm.usage import merge_usage, usage_to_payload
27
27
  from agentkit.runlog import JsonlRunLogSink
28
- from agentkit.tools.loader import load_tools_from_library
28
+ from agentkit.tools.loader import load_tools_from_entries, load_tools_from_library
29
29
  from agentkit.tools.registry import ToolRegistry
30
30
  from agentkit.runlog.recorder import RunRecorder
31
31
  from agentkit.workspace.fs import WorkspaceFS
@@ -78,9 +78,10 @@ class Agent:
78
78
  # Build the configured LLM provider
79
79
  provider = build_provider(config.provider)
80
80
 
81
- # Load tools from the workspace library and register them
81
+ # Load built-in tools plus any configured external tool entries.
82
82
  registry = ToolRegistry()
83
83
  registry.register_many(load_tools_from_library(fs))
84
+ registry.register_many(load_tools_from_entries(config.tools.entries, fs))
84
85
 
85
86
  # Create tool runtime with allowlist filtering
86
87
  tool_runtime = AgentToolRuntime(registry, config.tools.allowed)
@@ -31,7 +31,8 @@ def load_config(
31
31
  overrides: Mapping[str, Any] | None = None,
32
32
  ) -> AgentkitConfig:
33
33
  """Load, merge, expand, and validate framework configuration."""
34
- raw = _read_raw_config(path)
34
+ config_path = Path(path).expanduser()
35
+ raw = _read_raw_config(config_path)
35
36
  if overrides:
36
37
  raw = _deep_merge(raw, dict(overrides))
37
38
  raw = _expand_env(raw)
@@ -46,6 +47,10 @@ def load_config(
46
47
  agent = AgentConfig(budget=budget, **agent_data)
47
48
 
48
49
  tools = ToolConfig(**raw.get("tools", {}))
50
+ tools.entries = _resolve_tool_entries(
51
+ tools.entries,
52
+ base_dir=config_path.resolve(strict=False).parent,
53
+ )
49
54
  runlog = RunLogConfig(**raw.get("runlog", {}))
50
55
  except TypeError as exc:
51
56
  raise ConfigError(f"Invalid configuration fields: {exc}") from exc
@@ -106,3 +111,14 @@ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any
106
111
  else:
107
112
  merged[key] = value
108
113
  return merged
114
+
115
+
116
+ def _resolve_tool_entries(entries: list[str], *, base_dir: Path) -> list[str]:
117
+ """Resolve configured tool entry paths relative to the config file."""
118
+ resolved_entries: list[str] = []
119
+ for entry in entries:
120
+ candidate = Path(entry).expanduser()
121
+ if not candidate.is_absolute():
122
+ candidate = base_dir / candidate
123
+ resolved_entries.append(str(candidate.resolve(strict=False)))
124
+ return resolved_entries
@@ -122,6 +122,17 @@ class ToolConfig:
122
122
  allowed: list[str] = field(default_factory=list)
123
123
  entries: list[str] = field(default_factory=list)
124
124
 
125
+ def __post_init__(self) -> None:
126
+ """Validate allowlist and external entry shapes."""
127
+ if not isinstance(self.allowed, list) or any(
128
+ not isinstance(name, str) or not name.strip() for name in self.allowed
129
+ ):
130
+ raise ConfigError("tools.allowed must be a list of non-empty strings.")
131
+ if not isinstance(self.entries, list) or any(
132
+ not isinstance(entry, str) or not entry.strip() for entry in self.entries
133
+ ):
134
+ raise ConfigError("tools.entries must be a list of non-empty strings.")
135
+
125
136
 
126
137
  @dataclass(slots=True)
127
138
  class RunLogConfig:
@@ -13,9 +13,11 @@ DEFAULT_TIME_BUDGET_S = 300
13
13
  DEFAULT_MAX_INPUT_CHARS = 20_000
14
14
 
15
15
  SENSITIVE_KEYS: tuple[str, ...] = (
16
+ "access_token",
16
17
  "api_key",
17
18
  "apikey",
18
- "secret",
19
- "password",
20
19
  "authorization",
20
+ "password",
21
+ "refresh_token",
22
+ "secret",
21
23
  )
@@ -1,7 +1,7 @@
1
1
  """Public tool abstraction and registry exports."""
2
2
 
3
3
  from .base import FunctionTool, Tool
4
- from .loader import load_tools_from_library
4
+ from .loader import load_tools_from_entries, load_tools_from_library
5
5
  from .registry import ToolRegistry
6
6
  from .types import ToolCallOutcome, ToolInvocation, ToolModelError
7
7
 
@@ -12,5 +12,6 @@ __all__ = [
12
12
  "ToolInvocation",
13
13
  "ToolModelError",
14
14
  "ToolRegistry",
15
+ "load_tools_from_entries",
15
16
  "load_tools_from_library",
16
17
  ]
@@ -0,0 +1,246 @@
1
+ """Automatic loading for built-in and user-provided tool modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import importlib.util
7
+ import inspect
8
+ import itertools
9
+ import pkgutil
10
+ import sys
11
+ from importlib.machinery import ModuleSpec
12
+ from pathlib import Path
13
+ from types import ModuleType
14
+ from typing import Any, Iterable
15
+
16
+ from agentkit.errors import ToolError
17
+ from agentkit.tools.base import Tool
18
+ from agentkit.workspace.fs import WorkspaceFS
19
+
20
+ TOOLS_LIBRARY_PACKAGE = "agentkit.tools.library"
21
+ _USER_TOOLS_PACKAGE_PREFIX = "_agentkit_user_tools"
22
+ _USER_TOOLS_COUNTER = itertools.count()
23
+
24
+
25
+ def load_tools_from_library(fs: WorkspaceFS) -> list[Tool]:
26
+ """Load all tools exposed by modules under ``agentkit.tools.library``.
27
+
28
+ Tool modules can expose tools via:
29
+ - ``build_tools(fs)`` (or ``build_tools()``)
30
+ - ``TOOLS`` module variable
31
+
32
+ Args:
33
+ fs: Workspace filesystem injected into callable tool factories.
34
+
35
+ Returns:
36
+ list[Tool]: Flattened list of tools from all library modules.
37
+ """
38
+ package = importlib.import_module(TOOLS_LIBRARY_PACKAGE)
39
+ package_path = getattr(package, "__path__", None)
40
+ if package_path is None:
41
+ return []
42
+
43
+ loaded: list[Tool] = []
44
+ for module_info in sorted(pkgutil.iter_modules(package_path), key=lambda m: m.name):
45
+ if module_info.name.startswith("_"):
46
+ continue
47
+ module_name = f"{TOOLS_LIBRARY_PACKAGE}.{module_info.name}"
48
+ module = importlib.import_module(module_name)
49
+ loaded.extend(_load_from_module(module, fs, module_name=module_name))
50
+ return loaded
51
+
52
+
53
+ def load_tools_from_entries(
54
+ entries: Iterable[str | Path], fs: WorkspaceFS
55
+ ) -> list[Tool]:
56
+ """Load tools from configured file or directory entries.
57
+
58
+ Args:
59
+ entries: Filesystem paths pointing to Python files or directories that
60
+ contain Python tool modules.
61
+ fs: Workspace filesystem injected into callable tool factories.
62
+
63
+ Returns:
64
+ list[Tool]: Flattened list of tools discovered from all configured entries.
65
+
66
+ Raises:
67
+ agentkit.errors.ToolError: If an entry is missing, unsupported, or does not
68
+ define any tools.
69
+ """
70
+ loaded: list[Tool] = []
71
+ package_names: dict[Path, str] = {}
72
+ for entry in entries:
73
+ path = Path(entry).expanduser().resolve(strict=False)
74
+ loaded.extend(_load_tools_from_entry(path, fs, package_names=package_names))
75
+ return loaded
76
+
77
+
78
+ def _load_from_module(module: Any, fs: WorkspaceFS, *, module_name: str) -> list[Tool]:
79
+ """Extract tools from one library module."""
80
+ if hasattr(module, "build_tools"):
81
+ return _coerce_to_tools(getattr(module, "build_tools"), fs, module_name)
82
+ if hasattr(module, "TOOLS"):
83
+ return _coerce_to_tools(getattr(module, "TOOLS"), fs, module_name)
84
+ return []
85
+
86
+
87
+ def _coerce_to_tools(candidate: Any, fs: WorkspaceFS, module_name: str) -> list[Tool]:
88
+ """Normalize supported tool declarations into ``list[Tool]``."""
89
+ if isinstance(candidate, Tool):
90
+ return [candidate]
91
+
92
+ if callable(candidate):
93
+ built: Any
94
+ try:
95
+ signature = inspect.signature(candidate)
96
+ if len(signature.parameters) == 0:
97
+ built = candidate()
98
+ else:
99
+ built = candidate(fs)
100
+ except ValueError:
101
+ built = candidate(fs)
102
+ return _coerce_to_tools(built, fs, module_name)
103
+
104
+ if isinstance(candidate, Iterable) and not isinstance(
105
+ candidate, (str, bytes, dict)
106
+ ):
107
+ tools = list(candidate)
108
+ if all(isinstance(item, Tool) for item in tools):
109
+ return tools
110
+
111
+ raise ToolError(
112
+ f"Module '{module_name}' must expose Tool, Iterable[Tool], build_tools(...), or TOOLS."
113
+ )
114
+
115
+
116
+ def _load_tools_from_entry(
117
+ path: Path,
118
+ fs: WorkspaceFS,
119
+ *,
120
+ package_names: dict[Path, str],
121
+ ) -> list[Tool]:
122
+ """Load tools from one configured filesystem entry."""
123
+ if not path.exists():
124
+ raise ToolError(f"Tool entry not found: {path}")
125
+ if path.is_file():
126
+ return _load_tools_from_file_entry(path, fs, package_names=package_names)
127
+ if path.is_dir():
128
+ return _load_tools_from_directory_entry(path, fs, package_names=package_names)
129
+ raise ToolError(f"Unsupported tool entry type: {path}")
130
+
131
+
132
+ def _load_tools_from_file_entry(
133
+ path: Path,
134
+ fs: WorkspaceFS,
135
+ *,
136
+ package_names: dict[Path, str],
137
+ ) -> list[Tool]:
138
+ """Load tools from one Python file entry."""
139
+ if path.suffix != ".py":
140
+ raise ToolError(f"Tool entry file must be a Python module: {path}")
141
+
142
+ package_name = _ensure_entry_package(path.parent, package_names=package_names)
143
+ module_name = f"{package_name}.{_sanitize_module_name(path.stem)}"
144
+ module = _load_module_from_path(path, module_name=module_name)
145
+ tools = _load_from_module(module, fs, module_name=str(path))
146
+ if not tools:
147
+ raise ToolError(f"Tool entry did not define any tools: {path}")
148
+ return tools
149
+
150
+
151
+ def _load_tools_from_directory_entry(
152
+ path: Path,
153
+ fs: WorkspaceFS,
154
+ *,
155
+ package_names: dict[Path, str],
156
+ ) -> list[Tool]:
157
+ """Load tools from all eligible Python modules in one directory entry."""
158
+ package_name = _ensure_entry_package(path, package_names=package_names)
159
+ package_module = sys.modules[package_name]
160
+ loaded = _load_from_module(package_module, fs, module_name=str(path / "__init__.py"))
161
+
162
+ for child in sorted(path.iterdir(), key=lambda item: item.name):
163
+ if not child.is_file():
164
+ continue
165
+ if child.suffix != ".py":
166
+ continue
167
+ if child.name == "__init__.py" or child.name.startswith("_"):
168
+ continue
169
+ module_name = f"{package_name}.{_sanitize_module_name(child.stem)}"
170
+ module = _load_module_from_path(child, module_name=module_name)
171
+ loaded.extend(_load_from_module(module, fs, module_name=str(child)))
172
+
173
+ if not loaded:
174
+ raise ToolError(f"Tool entry did not define any tools: {path}")
175
+ return loaded
176
+
177
+
178
+ def _ensure_entry_package(path: Path, *, package_names: dict[Path, str]) -> str:
179
+ """Create or reuse a synthetic package namespace for one filesystem root."""
180
+ root = path.resolve(strict=False)
181
+ existing = package_names.get(root)
182
+ if existing is not None:
183
+ return existing
184
+
185
+ package_name = _next_dynamic_package_name(root)
186
+ init_file = root / "__init__.py"
187
+ if init_file.is_file():
188
+ _load_module_from_path(
189
+ init_file,
190
+ module_name=package_name,
191
+ submodule_search_locations=[str(root)],
192
+ )
193
+ else:
194
+ module = ModuleType(package_name)
195
+ module.__file__ = str(root)
196
+ module.__package__ = package_name
197
+ module.__path__ = [str(root)] # type: ignore[attr-defined]
198
+ spec = ModuleSpec(package_name, loader=None, is_package=True)
199
+ spec.submodule_search_locations = [str(root)]
200
+ module.__spec__ = spec
201
+ sys.modules[package_name] = module
202
+
203
+ package_names[root] = package_name
204
+ return package_name
205
+
206
+
207
+ def _load_module_from_path(
208
+ path: Path,
209
+ *,
210
+ module_name: str,
211
+ submodule_search_locations: list[str] | None = None,
212
+ ) -> ModuleType:
213
+ """Import one Python file from disk under a caller-controlled module name."""
214
+ spec = importlib.util.spec_from_file_location(
215
+ module_name,
216
+ path,
217
+ submodule_search_locations=submodule_search_locations,
218
+ )
219
+ if spec is None or spec.loader is None:
220
+ raise ToolError(f"Could not create an import spec for tool module: {path}")
221
+
222
+ module = importlib.util.module_from_spec(spec)
223
+ sys.modules[module_name] = module
224
+ try:
225
+ spec.loader.exec_module(module)
226
+ except Exception as exc:
227
+ sys.modules.pop(module_name, None)
228
+ raise ToolError(f"Failed to import tool module '{path}': {exc}") from exc
229
+ return module
230
+
231
+
232
+ def _next_dynamic_package_name(path: Path) -> str:
233
+ """Return a unique synthetic package name for one configured tool root."""
234
+ suffix = next(_USER_TOOLS_COUNTER)
235
+ slug = _sanitize_module_name(path.stem or "entry")
236
+ return f"{_USER_TOOLS_PACKAGE_PREFIX}_{suffix}_{slug}"
237
+
238
+
239
+ def _sanitize_module_name(name: str) -> str:
240
+ """Normalize arbitrary filenames into valid Python identifier segments."""
241
+ cleaned = "".join(ch if ch.isalnum() or ch == "_" else "_" for ch in name)
242
+ if not cleaned:
243
+ return "tool_module"
244
+ if cleaned[0].isdigit():
245
+ return f"_{cleaned}"
246
+ return cleaned
@@ -1,142 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: base-agentkit
3
- Version: 0.1.0
4
- Summary: Workspace-isolated Python framework for building tool-using LLM agents.
5
- Keywords: agents,llm,tool-calling,workspace,ai
6
- Author: sii-nyc
7
- Author-email: sii-nyc <253108120111@sii.edu.cn>
8
- License-Expression: Apache-2.0
9
- License-File: LICENSE
10
- Classifier: Development Status :: 3 - Alpha
11
- Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Typing :: Typed
17
- Requires-Dist: openai>=2.21.0
18
- Requires-Dist: pydantic>=2.12.5
19
- Requires-Dist: pyyaml>=6.0.3
20
- Requires-Dist: requests>=2.32.5
21
- Requires-Python: >=3.12
22
- Project-URL: Homepage, https://github.com/Inkjourney/agentkit
23
- Project-URL: Repository, https://github.com/Inkjourney/agentkit
24
- Project-URL: Documentation, https://github.com/Inkjourney/agentkit/tree/main/docs/agentkit
25
- Project-URL: Issues, https://github.com/Inkjourney/agentkit/issues
26
- Description-Content-Type: text/markdown
27
-
28
- # AgentKit
29
-
30
- 一个基于 LLM 的通用 Agent 框架,支持 workspace 隔离、统一 LLM 抽象、工具调用循环、CLI 与 Python SDK。
31
-
32
- ## 当前能力
33
-
34
- - workspace 强隔离:所有读写通过 `WorkspaceFS`
35
- - 统一 LLM 抽象:`ConversationItem` / `ConversationState` / `UnifiedLLMRequest` / `UnifiedLLMResponse`
36
- - Provider 支持(同步、非流式、文本)
37
- - OpenAI (`responses` / `chat_completions`)
38
- - Anthropic (`messages`)
39
- - Gemini (`generateContent`)
40
- - Qwen(OpenAI-compatible `chat_completions`)
41
- - vLLM(OpenAI-compatible `chat_completions`)
42
- - 工具系统:注册式工具机制,工具模块放在 `src/agentkit/tools/library/`
43
- - Agent loop:模型推理与工具执行闭环,受 step/time budget 约束
44
- - runlog:每次任务写入 `workspace/logs/run_<run_id>.jsonl`
45
-
46
- ## 状态
47
-
48
- - 当前仓库包含针对 Agent loop、CLI、工具系统、runlog 以及 OpenAI / Anthropic / Gemini / Qwen / vLLM provider 适配层的单元测试
49
- - 尚未提供针对真实上游 API 的集成 smoke test,因此正式发布前仍建议用目标模型做一次端到端验证
50
-
51
- ## 文档
52
-
53
- 详细使用说明和完整文档见 `docs/agentkit/`。
54
-
55
- ## 从源码安装
56
-
57
- ```bash
58
- uv sync
59
- uv run agentkit --help
60
- ```
61
-
62
- ## 从 PyPI 安装
63
-
64
- ```bash
65
- pip install base-agentkit
66
- agentkit --help
67
- ```
68
-
69
- 安装后的导入路径和 CLI 名称保持不变:
70
-
71
- ```python
72
- from agentkit import create_agent
73
- ```
74
-
75
- ## 配置示例
76
-
77
- ### vLLM
78
-
79
- ```yaml
80
- workspace:
81
- root: "./vllm_workspace"
82
-
83
- provider:
84
- kind: "vllm"
85
- model: "glm-5"
86
- openai_api_variant: "chat_completions"
87
- conversation_mode: "auto"
88
- base_url: "http://localhost:8000/v1"
89
- api_key: "empty"
90
- temperature: 0.8
91
- timeout_s: 600
92
- retries: 2
93
- enable_thinking: true
94
-
95
- agent:
96
- system_prompt: "You are a helpful agent. Use tools when needed."
97
- budget:
98
- max_steps: 200
99
- time_budget_s: 1800
100
- max_input_chars: 180000
101
-
102
- tools:
103
- allowed:
104
- - "view"
105
- - "create_file"
106
- - "str_replace"
107
- - "word_count"
108
-
109
- runlog:
110
- enabled: true
111
- redact: true
112
- max_text_chars: 20000
113
- ```
114
-
115
- ## CLI 运行
116
-
117
- ```bash
118
- export OPENAI_API_KEY="your-key"
119
- uv run agentkit --config path/to/config.yaml run --task "列出当前 workspace 的文件"
120
- ```
121
-
122
- ## Python SDK
123
-
124
- ```python
125
- from agentkit import create_agent
126
-
127
- agent = create_agent("path/to/config.yaml")
128
- report = agent.run("在 workspace 里新建 notes/todo.txt 并写入今天计划")
129
- print(report.final_output)
130
- ```
131
-
132
- ## 项目结构
133
-
134
- 核心实现位于 `src/agentkit/`:
135
-
136
- - `config/`: 配置结构与加载
137
- - `workspace/`: 强隔离文件系统和目录布局
138
- - `llm/`: 统一抽象、provider base/factory 与四平台 adapter
139
- - `tools/`: 工具抽象、注册表与自动加载
140
- - `agent/`: budget、report、runtime、Agent loop
141
- - `runlog/`: 结构化事件、event sink 与 JSONL runlog
142
- - `cli/`: 命令行入口
@@ -1,115 +0,0 @@
1
- # AgentKit
2
-
3
- 一个基于 LLM 的通用 Agent 框架,支持 workspace 隔离、统一 LLM 抽象、工具调用循环、CLI 与 Python SDK。
4
-
5
- ## 当前能力
6
-
7
- - workspace 强隔离:所有读写通过 `WorkspaceFS`
8
- - 统一 LLM 抽象:`ConversationItem` / `ConversationState` / `UnifiedLLMRequest` / `UnifiedLLMResponse`
9
- - Provider 支持(同步、非流式、文本)
10
- - OpenAI (`responses` / `chat_completions`)
11
- - Anthropic (`messages`)
12
- - Gemini (`generateContent`)
13
- - Qwen(OpenAI-compatible `chat_completions`)
14
- - vLLM(OpenAI-compatible `chat_completions`)
15
- - 工具系统:注册式工具机制,工具模块放在 `src/agentkit/tools/library/`
16
- - Agent loop:模型推理与工具执行闭环,受 step/time budget 约束
17
- - runlog:每次任务写入 `workspace/logs/run_<run_id>.jsonl`
18
-
19
- ## 状态
20
-
21
- - 当前仓库包含针对 Agent loop、CLI、工具系统、runlog 以及 OpenAI / Anthropic / Gemini / Qwen / vLLM provider 适配层的单元测试
22
- - 尚未提供针对真实上游 API 的集成 smoke test,因此正式发布前仍建议用目标模型做一次端到端验证
23
-
24
- ## 文档
25
-
26
- 详细使用说明和完整文档见 `docs/agentkit/`。
27
-
28
- ## 从源码安装
29
-
30
- ```bash
31
- uv sync
32
- uv run agentkit --help
33
- ```
34
-
35
- ## 从 PyPI 安装
36
-
37
- ```bash
38
- pip install base-agentkit
39
- agentkit --help
40
- ```
41
-
42
- 安装后的导入路径和 CLI 名称保持不变:
43
-
44
- ```python
45
- from agentkit import create_agent
46
- ```
47
-
48
- ## 配置示例
49
-
50
- ### vLLM
51
-
52
- ```yaml
53
- workspace:
54
- root: "./vllm_workspace"
55
-
56
- provider:
57
- kind: "vllm"
58
- model: "glm-5"
59
- openai_api_variant: "chat_completions"
60
- conversation_mode: "auto"
61
- base_url: "http://localhost:8000/v1"
62
- api_key: "empty"
63
- temperature: 0.8
64
- timeout_s: 600
65
- retries: 2
66
- enable_thinking: true
67
-
68
- agent:
69
- system_prompt: "You are a helpful agent. Use tools when needed."
70
- budget:
71
- max_steps: 200
72
- time_budget_s: 1800
73
- max_input_chars: 180000
74
-
75
- tools:
76
- allowed:
77
- - "view"
78
- - "create_file"
79
- - "str_replace"
80
- - "word_count"
81
-
82
- runlog:
83
- enabled: true
84
- redact: true
85
- max_text_chars: 20000
86
- ```
87
-
88
- ## CLI 运行
89
-
90
- ```bash
91
- export OPENAI_API_KEY="your-key"
92
- uv run agentkit --config path/to/config.yaml run --task "列出当前 workspace 的文件"
93
- ```
94
-
95
- ## Python SDK
96
-
97
- ```python
98
- from agentkit import create_agent
99
-
100
- agent = create_agent("path/to/config.yaml")
101
- report = agent.run("在 workspace 里新建 notes/todo.txt 并写入今天计划")
102
- print(report.final_output)
103
- ```
104
-
105
- ## 项目结构
106
-
107
- 核心实现位于 `src/agentkit/`:
108
-
109
- - `config/`: 配置结构与加载
110
- - `workspace/`: 强隔离文件系统和目录布局
111
- - `llm/`: 统一抽象、provider base/factory 与四平台 adapter
112
- - `tools/`: 工具抽象、注册表与自动加载
113
- - `agent/`: budget、report、runtime、Agent loop
114
- - `runlog/`: 结构化事件、event sink 与 JSONL runlog
115
- - `cli/`: 命令行入口
@@ -1,81 +0,0 @@
1
- """Automatic tool loading from the tool library directory."""
2
-
3
- from __future__ import annotations
4
-
5
- import importlib
6
- import inspect
7
- import pkgutil
8
- from typing import Any, Iterable
9
-
10
- from agentkit.errors import ToolError
11
- from agentkit.tools.base import Tool
12
- from agentkit.workspace.fs import WorkspaceFS
13
-
14
- TOOLS_LIBRARY_PACKAGE = "agentkit.tools.library"
15
-
16
-
17
- def load_tools_from_library(fs: WorkspaceFS) -> list[Tool]:
18
- """Load all tools exposed by modules under ``agentkit.tools.library``.
19
-
20
- Tool modules can expose tools via:
21
- - ``build_tools(fs)`` (or ``build_tools()``)
22
- - ``TOOLS`` module variable
23
-
24
- Args:
25
- fs: Workspace filesystem injected into callable tool factories.
26
-
27
- Returns:
28
- list[Tool]: Flattened list of tools from all library modules.
29
- """
30
- package = importlib.import_module(TOOLS_LIBRARY_PACKAGE)
31
- package_path = getattr(package, "__path__", None)
32
- if package_path is None:
33
- return []
34
-
35
- loaded: list[Tool] = []
36
- for module_info in sorted(pkgutil.iter_modules(package_path), key=lambda m: m.name):
37
- if module_info.name.startswith("_"):
38
- continue
39
- module_name = f"{TOOLS_LIBRARY_PACKAGE}.{module_info.name}"
40
- module = importlib.import_module(module_name)
41
- loaded.extend(_load_from_module(module, fs, module_name=module_name))
42
- return loaded
43
-
44
-
45
- def _load_from_module(module: Any, fs: WorkspaceFS, *, module_name: str) -> list[Tool]:
46
- """Extract tools from one library module."""
47
- if hasattr(module, "build_tools"):
48
- return _coerce_to_tools(getattr(module, "build_tools"), fs, module_name)
49
- if hasattr(module, "TOOLS"):
50
- return _coerce_to_tools(getattr(module, "TOOLS"), fs, module_name)
51
- return []
52
-
53
-
54
- def _coerce_to_tools(candidate: Any, fs: WorkspaceFS, module_name: str) -> list[Tool]:
55
- """Normalize supported tool declarations into ``list[Tool]``."""
56
- if isinstance(candidate, Tool):
57
- return [candidate]
58
-
59
- if callable(candidate):
60
- built: Any
61
- try:
62
- signature = inspect.signature(candidate)
63
- if len(signature.parameters) == 0:
64
- built = candidate()
65
- else:
66
- built = candidate(fs)
67
- except ValueError:
68
- built = candidate(fs)
69
- return _coerce_to_tools(built, fs, module_name)
70
-
71
- if isinstance(candidate, Iterable) and not isinstance(
72
- candidate, (str, bytes, dict)
73
- ):
74
- tools = list(candidate)
75
- if all(isinstance(item, Tool) for item in tools):
76
- return tools
77
-
78
- raise ToolError(
79
- f"Module '{module_name}' must expose Tool, Iterable[Tool], build_tools(...), or TOOLS."
80
- )
81
-
File without changes