langchain-agentkit 0.4.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.
- langchain_agentkit-0.4.0/.github/workflows/ci.yml +50 -0
- langchain_agentkit-0.4.0/.github/workflows/publish.yml +31 -0
- langchain_agentkit-0.4.0/.gitignore +11 -0
- langchain_agentkit-0.4.0/LICENSE +21 -0
- langchain_agentkit-0.4.0/PKG-INFO +300 -0
- langchain_agentkit-0.4.0/README.md +281 -0
- langchain_agentkit-0.4.0/examples/custom_state_type.py +103 -0
- langchain_agentkit-0.4.0/examples/manual_wiring.py +60 -0
- langchain_agentkit-0.4.0/examples/multi_agent.py +70 -0
- langchain_agentkit-0.4.0/examples/root_with_checkpointer.py +59 -0
- langchain_agentkit-0.4.0/examples/standalone_node.py +37 -0
- langchain_agentkit-0.4.0/examples/subgraph_with_checkpointer.py +64 -0
- langchain_agentkit-0.4.0/pyproject-skillkit.toml +28 -0
- langchain_agentkit-0.4.0/pyproject.toml +47 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/__init__.py +62 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/_handler_validation.py +81 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/agent.py +285 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/agent_kit.py +125 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/frontmatter.py +38 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/middleware.py +49 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/node.py +271 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/prompts/base_agent.md +12 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/prompts/skills_system.md +21 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/prompts/task_management.md +21 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/skill_kit.py +226 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/skills_middleware.py +70 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/state.py +26 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/task_tools.py +237 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/tasks_middleware.py +137 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/types.py +49 -0
- langchain_agentkit-0.4.0/src/langchain_agentkit/validate.py +46 -0
- langchain_agentkit-0.4.0/src/langchain_skillkit/__init__.py +35 -0
- langchain_agentkit-0.4.0/tests/__init__.py +0 -0
- langchain_agentkit-0.4.0/tests/fixtures/prompts/nodes/analyst.md +8 -0
- langchain_agentkit-0.4.0/tests/fixtures/prompts/nodes/researcher.md +9 -0
- langchain_agentkit-0.4.0/tests/fixtures/skills/market-sizing/SKILL.md +17 -0
- langchain_agentkit-0.4.0/tests/fixtures/skills/market-sizing/calculator.py +16 -0
- langchain_agentkit-0.4.0/tests/fixtures/skills_extra/competitive-analysis/SKILL.md +9 -0
- langchain_agentkit-0.4.0/tests/test_agent.py +353 -0
- langchain_agentkit-0.4.0/tests/test_agent_kit.py +207 -0
- langchain_agentkit-0.4.0/tests/test_frontmatter.py +66 -0
- langchain_agentkit-0.4.0/tests/test_node.py +278 -0
- langchain_agentkit-0.4.0/tests/test_skill_kit.py +160 -0
- langchain_agentkit-0.4.0/tests/test_skills_middleware.py +103 -0
- langchain_agentkit-0.4.0/tests/test_task_tools.py +253 -0
- langchain_agentkit-0.4.0/tests/test_tasks_middleware.py +211 -0
- langchain_agentkit-0.4.0/tests/test_types.py +67 -0
- langchain_agentkit-0.4.0/tests/test_validate.py +122 -0
- langchain_agentkit-0.4.0/uv.lock +1402 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python: ["3.11", "3.13"]
|
|
14
|
+
name: Test (Python ${{ matrix.python }})
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
|
|
20
|
+
- name: Install Python
|
|
21
|
+
run: uv python install ${{ matrix.python }}
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv sync --extra dev
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: uv run pytest --tb=short -q
|
|
28
|
+
|
|
29
|
+
lint:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
name: Lint & Types
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- uses: astral-sh/setup-uv@v5
|
|
36
|
+
|
|
37
|
+
- name: Install Python
|
|
38
|
+
run: uv python install 3.13
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: uv sync --extra dev
|
|
42
|
+
|
|
43
|
+
- name: Ruff check
|
|
44
|
+
run: uv run ruff check src/ tests/
|
|
45
|
+
|
|
46
|
+
- name: Ruff format
|
|
47
|
+
run: uv run ruff format --check src/ tests/
|
|
48
|
+
|
|
49
|
+
- name: Mypy
|
|
50
|
+
run: uv run mypy src/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
name: Build & Publish
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
contents: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
|
|
19
|
+
- name: Install Python
|
|
20
|
+
run: uv python install 3.13
|
|
21
|
+
|
|
22
|
+
- name: Build package
|
|
23
|
+
run: uv build
|
|
24
|
+
|
|
25
|
+
- name: Publish to PyPI
|
|
26
|
+
run: uv publish --trusted-publishing always
|
|
27
|
+
|
|
28
|
+
- name: Create GitHub Release
|
|
29
|
+
run: gh release create ${{ github.ref_name }} dist/* --generate-notes
|
|
30
|
+
env:
|
|
31
|
+
GH_TOKEN: ${{ github.token }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 rsmdt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-agentkit
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Composable middleware framework for LangGraph agents
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: langchain-core>=0.3
|
|
9
|
+
Requires-Dist: langgraph>=0.4
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: langchain-openai>=0.3; extra == 'dev'
|
|
13
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
17
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# langchain-agentkit
|
|
21
|
+
|
|
22
|
+
Composable middleware framework for LangGraph agents.
|
|
23
|
+
|
|
24
|
+
[](https://pypi.org/project/langchain-agentkit/)
|
|
25
|
+
[](https://opensource.org/licenses/MIT)
|
|
26
|
+
|
|
27
|
+
Build LangGraph agents with reusable middleware that composes tools and prompts. Two layers to choose from:
|
|
28
|
+
|
|
29
|
+
- **`agent` metaclass** — declare a class, get a complete ReAct agent with middleware-composed tools and prompts
|
|
30
|
+
- **`AgentKit`** — primitive composition engine for full control over graph topology
|
|
31
|
+
|
|
32
|
+
> **Migrating from `langchain-skillkit`?** See [Migration](#migrating-from-langchain-skillkit) below. The old import path still works with a deprecation warning.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Requires **Python 3.11+**, `langchain-core>=0.3`, `langgraph>=0.4`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install langchain-agentkit
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### The `agent` metaclass (recommended)
|
|
45
|
+
|
|
46
|
+
Declare a class that inherits from `agent` to get a `StateGraph` with an automatic ReAct loop:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from langchain_core.messages import HumanMessage, SystemMessage
|
|
50
|
+
from langchain_openai import ChatOpenAI
|
|
51
|
+
from langchain_agentkit import agent, SkillsMiddleware, TasksMiddleware
|
|
52
|
+
|
|
53
|
+
class researcher(agent):
|
|
54
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
55
|
+
tools = [web_search]
|
|
56
|
+
middleware = [SkillsMiddleware("skills/"), TasksMiddleware()]
|
|
57
|
+
prompt = "You are a research assistant."
|
|
58
|
+
|
|
59
|
+
async def handler(state, *, llm, prompt):
|
|
60
|
+
messages = [SystemMessage(content=prompt)] + state["messages"]
|
|
61
|
+
response = await llm.ainvoke(messages)
|
|
62
|
+
return {"messages": [response], "sender": "researcher"}
|
|
63
|
+
|
|
64
|
+
# Compile and use
|
|
65
|
+
graph = researcher.compile()
|
|
66
|
+
result = graph.invoke({"messages": [HumanMessage("Size the B2B SaaS market")]})
|
|
67
|
+
|
|
68
|
+
# With checkpointer for interrupt() support
|
|
69
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
70
|
+
graph = researcher.compile(checkpointer=InMemorySaver())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `AgentKit` for manual graph wiring
|
|
74
|
+
|
|
75
|
+
Use `AgentKit` when you need full control over graph topology — multi-node graphs, shared `ToolNode`, custom routing:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from langchain_agentkit import AgentKit, SkillsMiddleware, TasksMiddleware
|
|
79
|
+
|
|
80
|
+
kit = AgentKit([
|
|
81
|
+
SkillsMiddleware("skills/"),
|
|
82
|
+
TasksMiddleware(),
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
# In any graph node:
|
|
86
|
+
all_tools = my_tools + kit.tools
|
|
87
|
+
system_prompt = kit.prompt(state, config)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Standalone `SkillKit`
|
|
91
|
+
|
|
92
|
+
Use `SkillKit` directly for skill discovery without the middleware layer:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from langchain_agentkit import SkillKit
|
|
96
|
+
|
|
97
|
+
kit = SkillKit("skills/")
|
|
98
|
+
tools = kit.tools # [Skill, SkillRead]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
See [`examples/`](examples/) for complete working code:
|
|
104
|
+
|
|
105
|
+
- **[`standalone_node.py`](examples/standalone_node.py)** — Simplest usage: declare a node class, compile, invoke
|
|
106
|
+
- **[`manual_wiring.py`](examples/manual_wiring.py)** — Use `SkillKit` as a standalone toolkit with full graph control
|
|
107
|
+
- **[`multi_agent.py`](examples/multi_agent.py)** — Compose multiple agents in a parent graph
|
|
108
|
+
- **[`root_with_checkpointer.py`](examples/root_with_checkpointer.py)** — Multi-turn conversations with `interrupt()` and `Command(resume=...)`
|
|
109
|
+
- **[`subgraph_with_checkpointer.py`](examples/subgraph_with_checkpointer.py)** — Subgraph inherits parent's checkpointer automatically
|
|
110
|
+
- **[`custom_state_type.py`](examples/custom_state_type.py)** — Custom state shape via handler annotation + subgraph schema translation
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### `agent`
|
|
115
|
+
|
|
116
|
+
Declarative agent builder. Subclassing produces a `StateGraph`. Call `.compile()` to get a runnable graph.
|
|
117
|
+
|
|
118
|
+
**Class attributes:**
|
|
119
|
+
|
|
120
|
+
| Attribute | Required | Description |
|
|
121
|
+
|-----------|----------|-------------|
|
|
122
|
+
| `llm` | Yes | Language model instance |
|
|
123
|
+
| `tools` | No | List of LangChain tools |
|
|
124
|
+
| `middleware` | No | Ordered list of `Middleware` instances |
|
|
125
|
+
| `prompt` | No | System prompt — inline string, file path, or list of either |
|
|
126
|
+
|
|
127
|
+
**Handler signature:**
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
async def handler(state, *, llm, tools, prompt, config, runtime): ...
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`state` is positional. Everything after `*` is keyword-only and injected by name — declare only what you need:
|
|
134
|
+
|
|
135
|
+
| Parameter | Type | Description |
|
|
136
|
+
|-----------|------|-------------|
|
|
137
|
+
| `state` | `dict` | LangGraph state (positional, required) |
|
|
138
|
+
| `llm` | `BaseChatModel` | LLM pre-bound with all tools via `bind_tools()` |
|
|
139
|
+
| `tools` | `list[BaseTool]` | All tools (user tools + middleware tools) |
|
|
140
|
+
| `prompt` | `str` | Fully composed prompt (template + middleware sections) |
|
|
141
|
+
| `config` | `RunnableConfig` | LangGraph config for the current invocation |
|
|
142
|
+
| `runtime` | `Any` | LangGraph runtime context (passed through from graph kwargs) |
|
|
143
|
+
|
|
144
|
+
Both sync and async handlers are supported — sync handlers are detected via `inspect.isawaitable` and awaited automatically.
|
|
145
|
+
|
|
146
|
+
**Custom state types** — annotate the handler's `state` parameter:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
class MyState(TypedDict, total=False):
|
|
150
|
+
messages: Annotated[list, add_messages]
|
|
151
|
+
draft: dict | None
|
|
152
|
+
|
|
153
|
+
class my_agent(agent):
|
|
154
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
155
|
+
|
|
156
|
+
async def handler(state: MyState, *, llm):
|
|
157
|
+
...
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Without an annotation, `AgentState` is used by default.
|
|
161
|
+
|
|
162
|
+
### `Middleware` protocol
|
|
163
|
+
|
|
164
|
+
Any class with `tools` (property) and `prompt(state, config)` (method) satisfies the protocol via structural subtyping — no base class needed:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
class MyMiddleware:
|
|
168
|
+
@property
|
|
169
|
+
def tools(self) -> list[BaseTool]:
|
|
170
|
+
return [my_tool]
|
|
171
|
+
|
|
172
|
+
def prompt(self, state: dict, config: RunnableConfig) -> str | None:
|
|
173
|
+
return "You have access to my_tool."
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Built-in middleware:**
|
|
177
|
+
|
|
178
|
+
| Middleware | Tools | Prompt |
|
|
179
|
+
|-----------|-------|--------|
|
|
180
|
+
| `SkillsMiddleware(skills_dirs)` | `Skill`, `SkillRead` | Progressive disclosure skill list with load instructions |
|
|
181
|
+
| `TasksMiddleware()` | `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` | Base agent behavior + task context with status icons |
|
|
182
|
+
|
|
183
|
+
### `TasksMiddleware`
|
|
184
|
+
|
|
185
|
+
Task management middleware with `Command`-based tools that update graph state via LangGraph's `ToolNode`.
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# Default — auto-creates task tools
|
|
189
|
+
mw = TasksMiddleware()
|
|
190
|
+
|
|
191
|
+
# Custom tools
|
|
192
|
+
mw = TasksMiddleware(task_tools=[my_create, my_update])
|
|
193
|
+
|
|
194
|
+
# Custom task formatter
|
|
195
|
+
mw = TasksMiddleware(formatter=my_format_function)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Task tools use `InjectedState` to read tasks from state and return `Command(update={"tasks": [...]})` to apply changes. Task state is updated locally within the agent's graph, visible to the prompt on every ReAct loop iteration. When used as a subgraph, state flows back to the parent graph on completion.
|
|
199
|
+
|
|
200
|
+
You can also create task tools directly:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from langchain_agentkit import create_task_tools
|
|
204
|
+
|
|
205
|
+
tools = create_task_tools() # [TaskCreate, TaskUpdate, TaskList, TaskGet]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### `AgentKit(middleware, prompt=None)`
|
|
209
|
+
|
|
210
|
+
Composition engine that merges tools and prompts from middleware.
|
|
211
|
+
|
|
212
|
+
- **`tools`** — All tools from all middleware, deduplicated by name (first middleware wins, cached)
|
|
213
|
+
- **`prompt(state, config)`** — Template + middleware sections, joined with double newline (dynamic per call)
|
|
214
|
+
|
|
215
|
+
Prompt templates can be inline strings, file paths, or a list of either:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
kit = AgentKit(middleware, prompt="You are helpful.")
|
|
219
|
+
kit = AgentKit(middleware, prompt=Path("prompts/system.txt"))
|
|
220
|
+
kit = AgentKit(middleware, prompt=["prompts/base.txt", "Extra instructions"])
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### `node`
|
|
224
|
+
|
|
225
|
+
The original skill-aware metaclass. Uses `skills` attribute instead of `middleware`. Consider migrating to `agent` for the full middleware composition model — `agent` adds `middleware`, `prompt`, and `config` injection.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
class my_agent(node):
|
|
229
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
230
|
+
tools = [web_search]
|
|
231
|
+
skills = "skills/" # str, list[str], or SkillKit instance
|
|
232
|
+
|
|
233
|
+
async def handler(state, *, llm, tools, runtime): ...
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `AgentState`
|
|
237
|
+
|
|
238
|
+
Minimal LangGraph state type with task support:
|
|
239
|
+
|
|
240
|
+
| Field | Type | Description |
|
|
241
|
+
|-------|------|-------------|
|
|
242
|
+
| `messages` | `Annotated[list, add_messages]` | Conversation history with LangGraph message reducer |
|
|
243
|
+
| `sender` | `str` | Name of the last node that produced output |
|
|
244
|
+
| `tasks` | `list[dict[str, Any]]` | Task list managed by `TasksMiddleware` tools |
|
|
245
|
+
|
|
246
|
+
Extend with your own fields:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
class MyState(AgentState):
|
|
250
|
+
current_project: str
|
|
251
|
+
iteration_count: int
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Security
|
|
255
|
+
|
|
256
|
+
- **Path traversal prevention**: Skill file paths resolved to absolute and checked against skill directories. Reference file names reject `.` and `..` patterns.
|
|
257
|
+
- **Name validation**: Skill names validated per [AgentSkills.io spec](https://agentskills.io/specification) — lowercase alphanumeric + hyphens, 1-64 chars.
|
|
258
|
+
- **Tool scoping**: Each agent only has access to the tools declared in its `tools` attribute plus middleware-provided tools.
|
|
259
|
+
- **Prompt trust boundary**: Prompt templates and middleware prompt sections are set by the developer at construction time, not by end-user input.
|
|
260
|
+
|
|
261
|
+
## Migrating from `langchain-skillkit`
|
|
262
|
+
|
|
263
|
+
`langchain-agentkit` v0.4.0 is the successor to `langchain-skillkit`. The old import path works with a deprecation warning:
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
# Still works — emits DeprecationWarning
|
|
267
|
+
from langchain_skillkit import node, SkillKit, AgentState
|
|
268
|
+
|
|
269
|
+
# Update to:
|
|
270
|
+
from langchain_agentkit import node, SkillKit, AgentState
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**What changed:**
|
|
274
|
+
|
|
275
|
+
| Before (`langchain-skillkit`) | After (`langchain-agentkit`) |
|
|
276
|
+
|------------------------------|------------------------------|
|
|
277
|
+
| `from langchain_skillkit import ...` | `from langchain_agentkit import ...` |
|
|
278
|
+
| `node` with `skills` attribute | `node` (unchanged) + new `agent` with `middleware` |
|
|
279
|
+
| `SkillKit` only | `SkillKit` + `SkillsMiddleware` + `TasksMiddleware` |
|
|
280
|
+
| No middleware system | `Middleware` protocol + `AgentKit` composition |
|
|
281
|
+
| Handler injectables: `llm`, `tools`, `runtime` | `agent` adds: `prompt`, `config` |
|
|
282
|
+
|
|
283
|
+
**Migration steps:**
|
|
284
|
+
|
|
285
|
+
1. Update imports from `langchain_skillkit` to `langchain_agentkit`
|
|
286
|
+
2. Existing `node` subclasses work unchanged
|
|
287
|
+
3. Optionally migrate `node` to `agent` for middleware support:
|
|
288
|
+
- Replace `skills = "skills/"` with `middleware = [SkillsMiddleware("skills/")]`
|
|
289
|
+
- Add `prompt` and `config` injectables as needed
|
|
290
|
+
|
|
291
|
+
## Contributing
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
git clone https://github.com/rsmdt/langchain-agentkit.git
|
|
295
|
+
cd langchain-agentkit
|
|
296
|
+
uv sync --extra dev
|
|
297
|
+
uv run pytest --tb=short -q
|
|
298
|
+
uv run ruff check src/ tests/
|
|
299
|
+
uv run mypy src/
|
|
300
|
+
```
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# langchain-agentkit
|
|
2
|
+
|
|
3
|
+
Composable middleware framework for LangGraph agents.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/langchain-agentkit/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Build LangGraph agents with reusable middleware that composes tools and prompts. Two layers to choose from:
|
|
9
|
+
|
|
10
|
+
- **`agent` metaclass** — declare a class, get a complete ReAct agent with middleware-composed tools and prompts
|
|
11
|
+
- **`AgentKit`** — primitive composition engine for full control over graph topology
|
|
12
|
+
|
|
13
|
+
> **Migrating from `langchain-skillkit`?** See [Migration](#migrating-from-langchain-skillkit) below. The old import path still works with a deprecation warning.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Requires **Python 3.11+**, `langchain-core>=0.3`, `langgraph>=0.4`.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install langchain-agentkit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### The `agent` metaclass (recommended)
|
|
26
|
+
|
|
27
|
+
Declare a class that inherits from `agent` to get a `StateGraph` with an automatic ReAct loop:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from langchain_core.messages import HumanMessage, SystemMessage
|
|
31
|
+
from langchain_openai import ChatOpenAI
|
|
32
|
+
from langchain_agentkit import agent, SkillsMiddleware, TasksMiddleware
|
|
33
|
+
|
|
34
|
+
class researcher(agent):
|
|
35
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
36
|
+
tools = [web_search]
|
|
37
|
+
middleware = [SkillsMiddleware("skills/"), TasksMiddleware()]
|
|
38
|
+
prompt = "You are a research assistant."
|
|
39
|
+
|
|
40
|
+
async def handler(state, *, llm, prompt):
|
|
41
|
+
messages = [SystemMessage(content=prompt)] + state["messages"]
|
|
42
|
+
response = await llm.ainvoke(messages)
|
|
43
|
+
return {"messages": [response], "sender": "researcher"}
|
|
44
|
+
|
|
45
|
+
# Compile and use
|
|
46
|
+
graph = researcher.compile()
|
|
47
|
+
result = graph.invoke({"messages": [HumanMessage("Size the B2B SaaS market")]})
|
|
48
|
+
|
|
49
|
+
# With checkpointer for interrupt() support
|
|
50
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
51
|
+
graph = researcher.compile(checkpointer=InMemorySaver())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `AgentKit` for manual graph wiring
|
|
55
|
+
|
|
56
|
+
Use `AgentKit` when you need full control over graph topology — multi-node graphs, shared `ToolNode`, custom routing:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from langchain_agentkit import AgentKit, SkillsMiddleware, TasksMiddleware
|
|
60
|
+
|
|
61
|
+
kit = AgentKit([
|
|
62
|
+
SkillsMiddleware("skills/"),
|
|
63
|
+
TasksMiddleware(),
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
# In any graph node:
|
|
67
|
+
all_tools = my_tools + kit.tools
|
|
68
|
+
system_prompt = kit.prompt(state, config)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Standalone `SkillKit`
|
|
72
|
+
|
|
73
|
+
Use `SkillKit` directly for skill discovery without the middleware layer:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from langchain_agentkit import SkillKit
|
|
77
|
+
|
|
78
|
+
kit = SkillKit("skills/")
|
|
79
|
+
tools = kit.tools # [Skill, SkillRead]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Examples
|
|
83
|
+
|
|
84
|
+
See [`examples/`](examples/) for complete working code:
|
|
85
|
+
|
|
86
|
+
- **[`standalone_node.py`](examples/standalone_node.py)** — Simplest usage: declare a node class, compile, invoke
|
|
87
|
+
- **[`manual_wiring.py`](examples/manual_wiring.py)** — Use `SkillKit` as a standalone toolkit with full graph control
|
|
88
|
+
- **[`multi_agent.py`](examples/multi_agent.py)** — Compose multiple agents in a parent graph
|
|
89
|
+
- **[`root_with_checkpointer.py`](examples/root_with_checkpointer.py)** — Multi-turn conversations with `interrupt()` and `Command(resume=...)`
|
|
90
|
+
- **[`subgraph_with_checkpointer.py`](examples/subgraph_with_checkpointer.py)** — Subgraph inherits parent's checkpointer automatically
|
|
91
|
+
- **[`custom_state_type.py`](examples/custom_state_type.py)** — Custom state shape via handler annotation + subgraph schema translation
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### `agent`
|
|
96
|
+
|
|
97
|
+
Declarative agent builder. Subclassing produces a `StateGraph`. Call `.compile()` to get a runnable graph.
|
|
98
|
+
|
|
99
|
+
**Class attributes:**
|
|
100
|
+
|
|
101
|
+
| Attribute | Required | Description |
|
|
102
|
+
|-----------|----------|-------------|
|
|
103
|
+
| `llm` | Yes | Language model instance |
|
|
104
|
+
| `tools` | No | List of LangChain tools |
|
|
105
|
+
| `middleware` | No | Ordered list of `Middleware` instances |
|
|
106
|
+
| `prompt` | No | System prompt — inline string, file path, or list of either |
|
|
107
|
+
|
|
108
|
+
**Handler signature:**
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
async def handler(state, *, llm, tools, prompt, config, runtime): ...
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`state` is positional. Everything after `*` is keyword-only and injected by name — declare only what you need:
|
|
115
|
+
|
|
116
|
+
| Parameter | Type | Description |
|
|
117
|
+
|-----------|------|-------------|
|
|
118
|
+
| `state` | `dict` | LangGraph state (positional, required) |
|
|
119
|
+
| `llm` | `BaseChatModel` | LLM pre-bound with all tools via `bind_tools()` |
|
|
120
|
+
| `tools` | `list[BaseTool]` | All tools (user tools + middleware tools) |
|
|
121
|
+
| `prompt` | `str` | Fully composed prompt (template + middleware sections) |
|
|
122
|
+
| `config` | `RunnableConfig` | LangGraph config for the current invocation |
|
|
123
|
+
| `runtime` | `Any` | LangGraph runtime context (passed through from graph kwargs) |
|
|
124
|
+
|
|
125
|
+
Both sync and async handlers are supported — sync handlers are detected via `inspect.isawaitable` and awaited automatically.
|
|
126
|
+
|
|
127
|
+
**Custom state types** — annotate the handler's `state` parameter:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
class MyState(TypedDict, total=False):
|
|
131
|
+
messages: Annotated[list, add_messages]
|
|
132
|
+
draft: dict | None
|
|
133
|
+
|
|
134
|
+
class my_agent(agent):
|
|
135
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
136
|
+
|
|
137
|
+
async def handler(state: MyState, *, llm):
|
|
138
|
+
...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Without an annotation, `AgentState` is used by default.
|
|
142
|
+
|
|
143
|
+
### `Middleware` protocol
|
|
144
|
+
|
|
145
|
+
Any class with `tools` (property) and `prompt(state, config)` (method) satisfies the protocol via structural subtyping — no base class needed:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
class MyMiddleware:
|
|
149
|
+
@property
|
|
150
|
+
def tools(self) -> list[BaseTool]:
|
|
151
|
+
return [my_tool]
|
|
152
|
+
|
|
153
|
+
def prompt(self, state: dict, config: RunnableConfig) -> str | None:
|
|
154
|
+
return "You have access to my_tool."
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Built-in middleware:**
|
|
158
|
+
|
|
159
|
+
| Middleware | Tools | Prompt |
|
|
160
|
+
|-----------|-------|--------|
|
|
161
|
+
| `SkillsMiddleware(skills_dirs)` | `Skill`, `SkillRead` | Progressive disclosure skill list with load instructions |
|
|
162
|
+
| `TasksMiddleware()` | `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` | Base agent behavior + task context with status icons |
|
|
163
|
+
|
|
164
|
+
### `TasksMiddleware`
|
|
165
|
+
|
|
166
|
+
Task management middleware with `Command`-based tools that update graph state via LangGraph's `ToolNode`.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# Default — auto-creates task tools
|
|
170
|
+
mw = TasksMiddleware()
|
|
171
|
+
|
|
172
|
+
# Custom tools
|
|
173
|
+
mw = TasksMiddleware(task_tools=[my_create, my_update])
|
|
174
|
+
|
|
175
|
+
# Custom task formatter
|
|
176
|
+
mw = TasksMiddleware(formatter=my_format_function)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Task tools use `InjectedState` to read tasks from state and return `Command(update={"tasks": [...]})` to apply changes. Task state is updated locally within the agent's graph, visible to the prompt on every ReAct loop iteration. When used as a subgraph, state flows back to the parent graph on completion.
|
|
180
|
+
|
|
181
|
+
You can also create task tools directly:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from langchain_agentkit import create_task_tools
|
|
185
|
+
|
|
186
|
+
tools = create_task_tools() # [TaskCreate, TaskUpdate, TaskList, TaskGet]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `AgentKit(middleware, prompt=None)`
|
|
190
|
+
|
|
191
|
+
Composition engine that merges tools and prompts from middleware.
|
|
192
|
+
|
|
193
|
+
- **`tools`** — All tools from all middleware, deduplicated by name (first middleware wins, cached)
|
|
194
|
+
- **`prompt(state, config)`** — Template + middleware sections, joined with double newline (dynamic per call)
|
|
195
|
+
|
|
196
|
+
Prompt templates can be inline strings, file paths, or a list of either:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
kit = AgentKit(middleware, prompt="You are helpful.")
|
|
200
|
+
kit = AgentKit(middleware, prompt=Path("prompts/system.txt"))
|
|
201
|
+
kit = AgentKit(middleware, prompt=["prompts/base.txt", "Extra instructions"])
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### `node`
|
|
205
|
+
|
|
206
|
+
The original skill-aware metaclass. Uses `skills` attribute instead of `middleware`. Consider migrating to `agent` for the full middleware composition model — `agent` adds `middleware`, `prompt`, and `config` injection.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
class my_agent(node):
|
|
210
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
211
|
+
tools = [web_search]
|
|
212
|
+
skills = "skills/" # str, list[str], or SkillKit instance
|
|
213
|
+
|
|
214
|
+
async def handler(state, *, llm, tools, runtime): ...
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### `AgentState`
|
|
218
|
+
|
|
219
|
+
Minimal LangGraph state type with task support:
|
|
220
|
+
|
|
221
|
+
| Field | Type | Description |
|
|
222
|
+
|-------|------|-------------|
|
|
223
|
+
| `messages` | `Annotated[list, add_messages]` | Conversation history with LangGraph message reducer |
|
|
224
|
+
| `sender` | `str` | Name of the last node that produced output |
|
|
225
|
+
| `tasks` | `list[dict[str, Any]]` | Task list managed by `TasksMiddleware` tools |
|
|
226
|
+
|
|
227
|
+
Extend with your own fields:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
class MyState(AgentState):
|
|
231
|
+
current_project: str
|
|
232
|
+
iteration_count: int
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Security
|
|
236
|
+
|
|
237
|
+
- **Path traversal prevention**: Skill file paths resolved to absolute and checked against skill directories. Reference file names reject `.` and `..` patterns.
|
|
238
|
+
- **Name validation**: Skill names validated per [AgentSkills.io spec](https://agentskills.io/specification) — lowercase alphanumeric + hyphens, 1-64 chars.
|
|
239
|
+
- **Tool scoping**: Each agent only has access to the tools declared in its `tools` attribute plus middleware-provided tools.
|
|
240
|
+
- **Prompt trust boundary**: Prompt templates and middleware prompt sections are set by the developer at construction time, not by end-user input.
|
|
241
|
+
|
|
242
|
+
## Migrating from `langchain-skillkit`
|
|
243
|
+
|
|
244
|
+
`langchain-agentkit` v0.4.0 is the successor to `langchain-skillkit`. The old import path works with a deprecation warning:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
# Still works — emits DeprecationWarning
|
|
248
|
+
from langchain_skillkit import node, SkillKit, AgentState
|
|
249
|
+
|
|
250
|
+
# Update to:
|
|
251
|
+
from langchain_agentkit import node, SkillKit, AgentState
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**What changed:**
|
|
255
|
+
|
|
256
|
+
| Before (`langchain-skillkit`) | After (`langchain-agentkit`) |
|
|
257
|
+
|------------------------------|------------------------------|
|
|
258
|
+
| `from langchain_skillkit import ...` | `from langchain_agentkit import ...` |
|
|
259
|
+
| `node` with `skills` attribute | `node` (unchanged) + new `agent` with `middleware` |
|
|
260
|
+
| `SkillKit` only | `SkillKit` + `SkillsMiddleware` + `TasksMiddleware` |
|
|
261
|
+
| No middleware system | `Middleware` protocol + `AgentKit` composition |
|
|
262
|
+
| Handler injectables: `llm`, `tools`, `runtime` | `agent` adds: `prompt`, `config` |
|
|
263
|
+
|
|
264
|
+
**Migration steps:**
|
|
265
|
+
|
|
266
|
+
1. Update imports from `langchain_skillkit` to `langchain_agentkit`
|
|
267
|
+
2. Existing `node` subclasses work unchanged
|
|
268
|
+
3. Optionally migrate `node` to `agent` for middleware support:
|
|
269
|
+
- Replace `skills = "skills/"` with `middleware = [SkillsMiddleware("skills/")]`
|
|
270
|
+
- Add `prompt` and `config` injectables as needed
|
|
271
|
+
|
|
272
|
+
## Contributing
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
git clone https://github.com/rsmdt/langchain-agentkit.git
|
|
276
|
+
cd langchain-agentkit
|
|
277
|
+
uv sync --extra dev
|
|
278
|
+
uv run pytest --tb=short -q
|
|
279
|
+
uv run ruff check src/ tests/
|
|
280
|
+
uv run mypy src/
|
|
281
|
+
```
|