agentx-kit 0.4.0__tar.gz → 0.5.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.
- agentx_kit-0.5.0/.agentx/llm_cache.sqlite +0 -0
- agentx_kit-0.5.0/.claude-plugin/marketplace.json +15 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/PKG-INFO +38 -1
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/README.md +37 -0
- agentx_kit-0.5.0/integrations/claude-plugin/.claude-plugin/plugin.json +9 -0
- agentx_kit-0.5.0/integrations/claude-plugin/.mcp.json +8 -0
- agentx_kit-0.5.0/integrations/claude-plugin/README.md +29 -0
- agentx_kit-0.5.0/integrations/claude-plugin/commands/new-agent.md +14 -0
- agentx_kit-0.5.0/integrations/vscode/.vscodeignore +4 -0
- agentx_kit-0.5.0/integrations/vscode/README.md +34 -0
- agentx_kit-0.5.0/integrations/vscode/extension.js +87 -0
- agentx_kit-0.5.0/integrations/vscode/package.json +34 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/pyproject.toml +1 -1
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/__init__.py +7 -1
- agentx_kit-0.5.0/src/agentx/cache.py +166 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/cli.py +32 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/connector/build.py +2 -1
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/connector/recommend.py +4 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/dashboard/app.py +15 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/generator.py +1 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/spec.py +2 -1
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/main.py.j2 +8 -0
- agentx_kit-0.5.0/tests/test_cache.py +60 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/.github/workflows/publish.yml +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/.gitignore +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/DESIGN.md +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/LICENSE +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/RESEARCH.md +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/config.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/connector/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/connector/server.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/dashboard/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/frameworks/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/frameworks/crewai_agent.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/frameworks/langchain_agent.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/guardrails.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/insights/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/insights/analyze.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/insights/log.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/insights/optimize.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/insights/tokens.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/memory/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/memory/store.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/observability.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/prompts/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/prompts/templates.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/providers/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/providers/base.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/providers/factory.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/providers/registry.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/rag/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/rag/pipeline.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/reliability.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/prompts_store.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/Dockerfile.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/README.md.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/ci.yml.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/docker-compose.yml.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/dockerignore.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/env.example.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/evals/dataset.json.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/evals/run_evals.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/gitignore.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/mcp_servers.json.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/__init__.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/agents.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/config.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/guardrails.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/memory.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/observability.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/prompts.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/rag.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/server.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pkg/tools.py.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/pyproject.toml.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/templates/skills_seed.json.j2 +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/scaffold/wizard.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/skills/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/skills/registry.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/structured.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/tools/__init__.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/tools/builtin.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/src/agentx/tools/mcp.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/tests/test_connector.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/tests/test_enterprise.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/tests/test_insights.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/tests/test_prompts.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/tests/test_providers.py +0 -0
- {agentx_kit-0.4.0 → agentx_kit-0.5.0}/tests/test_scaffold.py +0 -0
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentx-kit",
|
|
3
|
+
"owner": { "name": "AgentX", "url": "https://github.com/muhammadyahiya/agentx-kit" },
|
|
4
|
+
"metadata": {
|
|
5
|
+
"description": "AgentX-Kit — scaffold agent projects from a prompt, in Claude Code.",
|
|
6
|
+
"version": "0.1.0"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "agentx-kit",
|
|
11
|
+
"source": "./integrations/claude-plugin",
|
|
12
|
+
"description": "Scaffold complete LangChain/CrewAI agent projects from a single problem statement via AgentX-Kit's MCP tools."
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: An open-source, provider-agnostic agentic framework + interactive project scaffolder for LangChain and CrewAI. Pick your LLM provider, agents, RAG, memory, MCP tools and skills — generate a ready-to-run uv project.
|
|
5
5
|
Project-URL: Homepage, https://github.com/muhammadyahiya/agentx-kit
|
|
6
6
|
Project-URL: Repository, https://github.com/muhammadyahiya/agentx-kit
|
|
@@ -291,6 +291,43 @@ The assistant calls AgentX-Kit's tools and you get a complete, runnable project:
|
|
|
291
291
|
|
|
292
292
|
So from one sentence the assistant produces a pre-wired project (prompts already seeded from your use case), ready to `uv sync && uv run`.
|
|
293
293
|
|
|
294
|
+
## 🧩 Editor & assistant integrations
|
|
295
|
+
The same connector powers ready-made integrations (see [`integrations/`](integrations/)):
|
|
296
|
+
|
|
297
|
+
- **VS Code extension** ([`integrations/vscode`](integrations/vscode)) — commands for
|
|
298
|
+
*New Agent Project*, *Open Prompt Dashboard*, *Add Prompt*, *Cache Stats*, and
|
|
299
|
+
*Register MCP Server for Copilot* (writes `.vscode/mcp.json`). Build with `vsce package`.
|
|
300
|
+
- **GitHub Copilot** (agent mode) — add the MCP server via `.vscode/mcp.json`:
|
|
301
|
+
```jsonc
|
|
302
|
+
{ "servers": { "agentx-kit": { "command": "agentx", "args": ["mcp"] } } }
|
|
303
|
+
```
|
|
304
|
+
(the VS Code command above writes this for you), then ask Copilot to build an agent.
|
|
305
|
+
- **Claude Code plugin** ([`integrations/claude-plugin`](integrations/claude-plugin)):
|
|
306
|
+
```text
|
|
307
|
+
/plugin marketplace add muhammadyahiya/agentx-kit
|
|
308
|
+
/plugin install agentx-kit@agentx-kit
|
|
309
|
+
/agentx-kit:new-agent a support agent that answers from our docs and serves an API
|
|
310
|
+
```
|
|
311
|
+
- **Claude Desktop / Codex** — add the connector config from `agentx mcp --print-config`.
|
|
312
|
+
|
|
313
|
+
## 💾 Response caching (cost & latency saver)
|
|
314
|
+
Caching is the top 2026 token-optimization lever. Turn on a **global LLM response
|
|
315
|
+
cache** and every provider call is served from a local store on repeat — no code changes:
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
from agentx import enable_caching, cache_stats
|
|
319
|
+
enable_caching() # all get_chat_model(...) calls are cached
|
|
320
|
+
...
|
|
321
|
+
print(cache_stats()) # {'hit_rate': 0.6, 'tokens_saved': 12000, 'est_usd_saved': 0.024, ...}
|
|
322
|
+
```
|
|
323
|
+
```bash
|
|
324
|
+
agentx cache stats # hit rate + estimated tokens/$ saved
|
|
325
|
+
agentx cache clear
|
|
326
|
+
```
|
|
327
|
+
Generated projects can enable it automatically (it's part of `--enterprise`), and the
|
|
328
|
+
**dashboard's Trends tab shows live hit-rate and $ saved**. TTL-capable, SQLite-backed
|
|
329
|
+
at `.agentx/llm_cache.sqlite`.
|
|
330
|
+
|
|
294
331
|
## 🏢 Enterprise pack
|
|
295
332
|
Generate a production-shaped project with one flag — informed by a survey of
|
|
296
333
|
CrewAI/LangGraph/create-llama/AgentStack/agno/pydantic-ai (see [RESEARCH.md](RESEARCH.md)):
|
|
@@ -187,6 +187,43 @@ The assistant calls AgentX-Kit's tools and you get a complete, runnable project:
|
|
|
187
187
|
|
|
188
188
|
So from one sentence the assistant produces a pre-wired project (prompts already seeded from your use case), ready to `uv sync && uv run`.
|
|
189
189
|
|
|
190
|
+
## 🧩 Editor & assistant integrations
|
|
191
|
+
The same connector powers ready-made integrations (see [`integrations/`](integrations/)):
|
|
192
|
+
|
|
193
|
+
- **VS Code extension** ([`integrations/vscode`](integrations/vscode)) — commands for
|
|
194
|
+
*New Agent Project*, *Open Prompt Dashboard*, *Add Prompt*, *Cache Stats*, and
|
|
195
|
+
*Register MCP Server for Copilot* (writes `.vscode/mcp.json`). Build with `vsce package`.
|
|
196
|
+
- **GitHub Copilot** (agent mode) — add the MCP server via `.vscode/mcp.json`:
|
|
197
|
+
```jsonc
|
|
198
|
+
{ "servers": { "agentx-kit": { "command": "agentx", "args": ["mcp"] } } }
|
|
199
|
+
```
|
|
200
|
+
(the VS Code command above writes this for you), then ask Copilot to build an agent.
|
|
201
|
+
- **Claude Code plugin** ([`integrations/claude-plugin`](integrations/claude-plugin)):
|
|
202
|
+
```text
|
|
203
|
+
/plugin marketplace add muhammadyahiya/agentx-kit
|
|
204
|
+
/plugin install agentx-kit@agentx-kit
|
|
205
|
+
/agentx-kit:new-agent a support agent that answers from our docs and serves an API
|
|
206
|
+
```
|
|
207
|
+
- **Claude Desktop / Codex** — add the connector config from `agentx mcp --print-config`.
|
|
208
|
+
|
|
209
|
+
## 💾 Response caching (cost & latency saver)
|
|
210
|
+
Caching is the top 2026 token-optimization lever. Turn on a **global LLM response
|
|
211
|
+
cache** and every provider call is served from a local store on repeat — no code changes:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from agentx import enable_caching, cache_stats
|
|
215
|
+
enable_caching() # all get_chat_model(...) calls are cached
|
|
216
|
+
...
|
|
217
|
+
print(cache_stats()) # {'hit_rate': 0.6, 'tokens_saved': 12000, 'est_usd_saved': 0.024, ...}
|
|
218
|
+
```
|
|
219
|
+
```bash
|
|
220
|
+
agentx cache stats # hit rate + estimated tokens/$ saved
|
|
221
|
+
agentx cache clear
|
|
222
|
+
```
|
|
223
|
+
Generated projects can enable it automatically (it's part of `--enterprise`), and the
|
|
224
|
+
**dashboard's Trends tab shows live hit-rate and $ saved**. TTL-capable, SQLite-backed
|
|
225
|
+
at `.agentx/llm_cache.sqlite`.
|
|
226
|
+
|
|
190
227
|
## 🏢 Enterprise pack
|
|
191
228
|
Generate a production-shaped project with one flag — informed by a survey of
|
|
192
229
|
CrewAI/LangGraph/create-llama/AgentStack/agno/pydantic-ai (see [RESEARCH.md](RESEARCH.md)):
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentx-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold complete provider-agnostic LangChain/CrewAI agent projects from a single problem statement, via AgentX-Kit's MCP tools.",
|
|
5
|
+
"author": { "name": "AgentX" },
|
|
6
|
+
"homepage": "https://github.com/muhammadyahiya/agentx-kit",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": ["agents", "scaffold", "langchain", "crewai", "mcp"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# AgentX-Kit — Claude Code plugin
|
|
2
|
+
|
|
3
|
+
Bundles AgentX-Kit's MCP server + a `/agentx-kit:new-agent` slash command so you
|
|
4
|
+
can scaffold a complete agent project from a single problem statement inside
|
|
5
|
+
Claude Code.
|
|
6
|
+
|
|
7
|
+
## Prerequisite
|
|
8
|
+
```bash
|
|
9
|
+
pip install "agentx-kit[connector]" # provides `agentx mcp`
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Install (from this repo's marketplace)
|
|
13
|
+
```text
|
|
14
|
+
/plugin marketplace add muhammadyahiya/agentx-kit
|
|
15
|
+
/plugin install agentx-kit@agentx-kit
|
|
16
|
+
```
|
|
17
|
+
Then use it:
|
|
18
|
+
```text
|
|
19
|
+
/agentx-kit:new-agent a customer-support agent that answers from our docs and serves an API
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Or just add the MCP server (no plugin)
|
|
23
|
+
```bash
|
|
24
|
+
claude mcp add agentx-kit -- agentx mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The plugin ships:
|
|
28
|
+
- `.mcp.json` — registers the `agentx-kit` MCP server (`agentx mcp`).
|
|
29
|
+
- `commands/new-agent.md` — the `/agentx-kit:new-agent` workflow.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scaffold a complete AgentX-Kit agent project from a problem statement
|
|
3
|
+
argument-hint: <describe the agent / use case you want to build>
|
|
4
|
+
---
|
|
5
|
+
Build a complete, runnable agent project for this request using the AgentX-Kit MCP tools:
|
|
6
|
+
|
|
7
|
+
**$ARGUMENTS**
|
|
8
|
+
|
|
9
|
+
Steps:
|
|
10
|
+
1. Call `recommend_project` with the problem statement and briefly show the recommended stack (framework, provider, agents, features) + rationale.
|
|
11
|
+
2. Ask the user to confirm or adjust (provider/framework/enterprise), then call `create_agent_project` with the problem statement and any overrides.
|
|
12
|
+
3. Report the target directory, the generated file tree, and the exact run steps it returns. Offer to open key files (main.py, agents.py, prompts.json).
|
|
13
|
+
|
|
14
|
+
Keep it concise; the tools do the heavy lifting.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# AgentX-Kit — VS Code extension
|
|
2
|
+
|
|
3
|
+
Scaffold agent projects, open the prompt dashboard, and wire AgentX-Kit into
|
|
4
|
+
**GitHub Copilot** (agent mode) — without leaving VS Code.
|
|
5
|
+
|
|
6
|
+
## Prerequisite
|
|
7
|
+
```bash
|
|
8
|
+
pip install "agentx-kit[all]" # provides the `agentx` CLI the extension calls
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands (⇧⌘P)
|
|
12
|
+
- **AgentX: New Agent Project** — name + use case → `agentx new`
|
|
13
|
+
- **AgentX: Open Prompt Dashboard** — `agentx dashboard`
|
|
14
|
+
- **AgentX: Add Agent Prompt** — `agentx prompt set … -d`
|
|
15
|
+
- **AgentX: Show Response-Cache Stats** — `agentx cache stats`
|
|
16
|
+
- **AgentX: Register MCP Server for Copilot** — writes `.vscode/mcp.json` so Copilot
|
|
17
|
+
agent mode can call AgentX-Kit's tools (e.g. *"build a support agent over our docs"*).
|
|
18
|
+
|
|
19
|
+
Set a custom CLI path with the `agentx.command` setting.
|
|
20
|
+
|
|
21
|
+
## Build / install locally
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @vscode/vsce
|
|
24
|
+
cd integrations/vscode
|
|
25
|
+
vsce package # -> agentx-kit-0.1.0.vsix
|
|
26
|
+
code --install-extension agentx-kit-0.1.0.vsix
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Publish (needs a Marketplace publisher + PAT)
|
|
30
|
+
```bash
|
|
31
|
+
vsce login <publisher>
|
|
32
|
+
vsce publish
|
|
33
|
+
```
|
|
34
|
+
See https://code.visualstudio.com/api/working-with-extensions/publishing-extension.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// AgentX-Kit VS Code extension.
|
|
2
|
+
// Thin wrapper over the `agentx` CLI + MCP registration for Copilot/agent mode.
|
|
3
|
+
// Pure JS (no build step). Requires `pip install agentx-kit` on PATH.
|
|
4
|
+
const vscode = require("vscode");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
function cli() {
|
|
9
|
+
return vscode.workspace.getConfiguration("agentx").get("command", "agentx");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function runInTerminal(name, commandLine) {
|
|
13
|
+
const term = vscode.window.createTerminal({ name });
|
|
14
|
+
term.show();
|
|
15
|
+
term.sendText(commandLine);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function workspaceRoot() {
|
|
19
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
20
|
+
return folders && folders.length ? folders[0].uri.fsPath : process.cwd();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function newProject() {
|
|
24
|
+
const name = await vscode.window.showInputBox({
|
|
25
|
+
prompt: "Project name", value: "my-agent",
|
|
26
|
+
});
|
|
27
|
+
if (!name) return;
|
|
28
|
+
const problem = await vscode.window.showInputBox({
|
|
29
|
+
prompt: "Describe the use case (optional — seeds the agent's prompt)", value: "",
|
|
30
|
+
});
|
|
31
|
+
const enterprise = await vscode.window.showQuickPick(["No", "Yes (tracing, guardrails, FastAPI, Docker, CI, evals, cache)"], {
|
|
32
|
+
placeHolder: "Enterprise pack?",
|
|
33
|
+
});
|
|
34
|
+
let cmd = `${cli()} new --yes --name ${JSON.stringify(name)}`;
|
|
35
|
+
if (problem) cmd += ` --prompt ${JSON.stringify(problem)}`;
|
|
36
|
+
if (enterprise && enterprise.startsWith("Yes")) cmd += " --enterprise";
|
|
37
|
+
runInTerminal("AgentX: new", cmd);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function dashboard() {
|
|
41
|
+
runInTerminal("AgentX: dashboard", `${cli()} dashboard`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function addPrompt() {
|
|
45
|
+
const agent = await vscode.window.showInputBox({ prompt: "Agent name", value: "assistant" });
|
|
46
|
+
if (!agent) return;
|
|
47
|
+
const text = await vscode.window.showInputBox({ prompt: "System prompt" });
|
|
48
|
+
if (text === undefined) return;
|
|
49
|
+
runInTerminal("AgentX: prompt", `${cli()} prompt set ${JSON.stringify(agent)} --text ${JSON.stringify(text)} -d`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cacheStats() {
|
|
53
|
+
runInTerminal("AgentX: cache", `${cli()} cache stats`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Write .vscode/mcp.json so GitHub Copilot (agent mode) / VS Code can use AgentX-Kit's MCP server.
|
|
57
|
+
async function registerMcp() {
|
|
58
|
+
const root = workspaceRoot();
|
|
59
|
+
const dir = path.join(root, ".vscode");
|
|
60
|
+
const file = path.join(dir, "mcp.json");
|
|
61
|
+
let config = { servers: {} };
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(file)) config = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
64
|
+
} catch (e) { /* start fresh on parse error */ }
|
|
65
|
+
config.servers = config.servers || {};
|
|
66
|
+
config.servers["agentx-kit"] = { command: cli(), args: ["mcp"] };
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
fs.writeFileSync(file, JSON.stringify(config, null, 2));
|
|
69
|
+
vscode.window.showInformationMessage(
|
|
70
|
+
"AgentX-Kit MCP server registered in .vscode/mcp.json. Open Copilot Chat (Agent mode) and ask it to build an agent from a problem statement."
|
|
71
|
+
);
|
|
72
|
+
const doc = await vscode.workspace.openTextDocument(file);
|
|
73
|
+
vscode.window.showTextDocument(doc);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function activate(context) {
|
|
77
|
+
const reg = (id, fn) => context.subscriptions.push(vscode.commands.registerCommand(id, fn));
|
|
78
|
+
reg("agentx.newProject", newProject);
|
|
79
|
+
reg("agentx.dashboard", dashboard);
|
|
80
|
+
reg("agentx.addPrompt", addPrompt);
|
|
81
|
+
reg("agentx.cacheStats", cacheStats);
|
|
82
|
+
reg("agentx.registerMcp", registerMcp);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function deactivate() {}
|
|
86
|
+
|
|
87
|
+
module.exports = { activate, deactivate };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentx-kit",
|
|
3
|
+
"displayName": "AgentX-Kit",
|
|
4
|
+
"description": "Scaffold provider-agnostic LangChain/CrewAI agent projects, open the prompt dashboard, and wire AgentX-Kit into Copilot — from VS Code.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"publisher": "agentx",
|
|
7
|
+
"engines": { "vscode": "^1.85.0" },
|
|
8
|
+
"categories": ["Machine Learning", "Snippets", "Other"],
|
|
9
|
+
"keywords": ["ai", "agents", "llm", "langchain", "crewai", "mcp", "scaffold", "copilot"],
|
|
10
|
+
"icon": "icon.png",
|
|
11
|
+
"repository": { "type": "git", "url": "https://github.com/muhammadyahiya/agentx-kit" },
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"main": "./extension.js",
|
|
14
|
+
"activationEvents": [],
|
|
15
|
+
"contributes": {
|
|
16
|
+
"commands": [
|
|
17
|
+
{ "command": "agentx.newProject", "title": "AgentX: New Agent Project" },
|
|
18
|
+
{ "command": "agentx.dashboard", "title": "AgentX: Open Prompt Dashboard" },
|
|
19
|
+
{ "command": "agentx.addPrompt", "title": "AgentX: Add Agent Prompt" },
|
|
20
|
+
{ "command": "agentx.cacheStats", "title": "AgentX: Show Response-Cache Stats" },
|
|
21
|
+
{ "command": "agentx.registerMcp", "title": "AgentX: Register MCP Server for Copilot" }
|
|
22
|
+
],
|
|
23
|
+
"configuration": {
|
|
24
|
+
"title": "AgentX-Kit",
|
|
25
|
+
"properties": {
|
|
26
|
+
"agentx.command": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"default": "agentx",
|
|
29
|
+
"description": "Path to the agentx CLI (from `pip install agentx-kit`)."
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
# PyPI distribution name (import name + CLI stay `agentx`; `agentx` was taken).
|
|
3
3
|
name = "agentx-kit"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.5.0"
|
|
5
5
|
description = "An open-source, provider-agnostic agentic framework + interactive project scaffolder for LangChain and CrewAI. Pick your LLM provider, agents, RAG, memory, MCP tools and skills — generate a ready-to-run uv project."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.10,<3.14"
|
|
@@ -16,7 +16,7 @@ is enough to get started.
|
|
|
16
16
|
"""
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
__version__ = "0.
|
|
19
|
+
__version__ = "0.5.0"
|
|
20
20
|
|
|
21
21
|
from .providers import ( # noqa: E402
|
|
22
22
|
ProviderSpec,
|
|
@@ -39,6 +39,7 @@ from .insights import ( # noqa: E402
|
|
|
39
39
|
estimate_cost,
|
|
40
40
|
optimize_prompt,
|
|
41
41
|
)
|
|
42
|
+
from .cache import cache_stats, clear_cache, disable_caching, enable_caching # noqa: E402
|
|
42
43
|
|
|
43
44
|
__all__ = [
|
|
44
45
|
"__version__",
|
|
@@ -63,4 +64,9 @@ __all__ = [
|
|
|
63
64
|
"optimize_prompt",
|
|
64
65
|
"count_tokens",
|
|
65
66
|
"estimate_cost",
|
|
67
|
+
# response caching
|
|
68
|
+
"enable_caching",
|
|
69
|
+
"disable_caching",
|
|
70
|
+
"cache_stats",
|
|
71
|
+
"clear_cache",
|
|
66
72
|
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""LLM response caching — cut cost & latency across all providers.
|
|
2
|
+
|
|
3
|
+
Caching is the top token-optimization lever (2026): repeat/near-repeat calls are
|
|
4
|
+
served from a local store instead of the model. This is an **exact** response
|
|
5
|
+
cache implemented as a LangChain ``BaseCache`` and installed globally, so every
|
|
6
|
+
``get_chat_model(...)`` call benefits automatically — no code changes.
|
|
7
|
+
|
|
8
|
+
from agentx.cache import enable_caching, cache_stats
|
|
9
|
+
enable_caching() # all subsequent LLM calls are cached
|
|
10
|
+
...
|
|
11
|
+
print(cache_stats()) # hits, misses, est. tokens/$ saved
|
|
12
|
+
|
|
13
|
+
Persistence is a small SQLite file (default ``.agentx/llm_cache.sqlite``) with an
|
|
14
|
+
optional TTL. Stats track hit/miss and estimated tokens + USD saved (derived from
|
|
15
|
+
cached completion sizes — see ``agentx.insights.tokens``).
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import sqlite3
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
_DEFAULT_PATH = ".agentx/llm_cache.sqlite"
|
|
27
|
+
_lock = threading.Lock()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _key(prompt: str, llm_string: str) -> str:
|
|
31
|
+
return hashlib.sha256(f"{llm_string}\x00{prompt}".encode("utf-8")).hexdigest()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _model_from_llm_string(llm_string: str) -> str:
|
|
35
|
+
# llm_string is a serialized model descriptor; best-effort model name for costing.
|
|
36
|
+
for token in ("gpt-4o-mini", "gpt-4o", "gpt-4.1", "claude-3-5", "gemini-1.5", "llama"):
|
|
37
|
+
if token in llm_string:
|
|
38
|
+
return token
|
|
39
|
+
return "gpt-4o-mini"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AgentXCache:
|
|
43
|
+
"""A LangChain ``BaseCache`` backed by SQLite, with TTL + savings stats.
|
|
44
|
+
|
|
45
|
+
Implements ``lookup``/``update`` (and ``aclear``) so it can be passed to
|
|
46
|
+
``langchain_core.globals.set_llm_cache``.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, path: str | Path = _DEFAULT_PATH, ttl: int | None = None):
|
|
50
|
+
self.path = Path(path)
|
|
51
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
self.ttl = ttl
|
|
53
|
+
self._init_db()
|
|
54
|
+
|
|
55
|
+
def _conn(self) -> sqlite3.Connection:
|
|
56
|
+
return sqlite3.connect(str(self.path))
|
|
57
|
+
|
|
58
|
+
def _init_db(self) -> None:
|
|
59
|
+
with _lock, self._conn() as c:
|
|
60
|
+
c.execute(
|
|
61
|
+
"CREATE TABLE IF NOT EXISTS cache "
|
|
62
|
+
"(key TEXT PRIMARY KEY, value TEXT, ts REAL, model TEXT, out_tokens INT)"
|
|
63
|
+
)
|
|
64
|
+
c.execute("CREATE TABLE IF NOT EXISTS stats (name TEXT PRIMARY KEY, val REAL)")
|
|
65
|
+
for name in ("hits", "misses", "tokens_saved"):
|
|
66
|
+
c.execute("INSERT OR IGNORE INTO stats(name, val) VALUES (?, 0)", (name,))
|
|
67
|
+
|
|
68
|
+
def _bump(self, conn: sqlite3.Connection, name: str, by: float = 1) -> None:
|
|
69
|
+
conn.execute("UPDATE stats SET val = val + ? WHERE name = ?", (by, name))
|
|
70
|
+
|
|
71
|
+
# ----- BaseCache interface -----
|
|
72
|
+
def lookup(self, prompt: str, llm_string: str) -> Any | None:
|
|
73
|
+
import warnings
|
|
74
|
+
|
|
75
|
+
from langchain_core.load import loads
|
|
76
|
+
|
|
77
|
+
key = _key(prompt, llm_string)
|
|
78
|
+
with _lock, self._conn() as c:
|
|
79
|
+
row = c.execute("SELECT value, ts, out_tokens FROM cache WHERE key = ?", (key,)).fetchone()
|
|
80
|
+
if not row:
|
|
81
|
+
self._bump(c, "misses")
|
|
82
|
+
return None
|
|
83
|
+
value, ts, out_tokens = row
|
|
84
|
+
if self.ttl is not None and (time.time() - ts) > self.ttl:
|
|
85
|
+
c.execute("DELETE FROM cache WHERE key = ?", (key,))
|
|
86
|
+
self._bump(c, "misses")
|
|
87
|
+
return None
|
|
88
|
+
self._bump(c, "hits")
|
|
89
|
+
self._bump(c, "tokens_saved", out_tokens or 0)
|
|
90
|
+
try:
|
|
91
|
+
# We wrote these values ourselves, so deserialization is trusted.
|
|
92
|
+
with warnings.catch_warnings():
|
|
93
|
+
warnings.simplefilter("ignore")
|
|
94
|
+
return loads(value)
|
|
95
|
+
except Exception: # noqa: BLE001 - corrupt entry
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
def update(self, prompt: str, llm_string: str, return_val: Any) -> None:
|
|
99
|
+
from langchain_core.load import dumps
|
|
100
|
+
|
|
101
|
+
from .insights.tokens import count_tokens
|
|
102
|
+
|
|
103
|
+
key = _key(prompt, llm_string)
|
|
104
|
+
model = _model_from_llm_string(llm_string)
|
|
105
|
+
text = ""
|
|
106
|
+
try:
|
|
107
|
+
text = " ".join(getattr(g, "text", "") or "" for g in return_val)
|
|
108
|
+
except Exception: # noqa: BLE001
|
|
109
|
+
text = ""
|
|
110
|
+
out_tokens = count_tokens(text, model)
|
|
111
|
+
try:
|
|
112
|
+
payload = dumps(return_val)
|
|
113
|
+
except Exception: # noqa: BLE001 - non-serializable result; skip caching
|
|
114
|
+
return
|
|
115
|
+
with _lock, self._conn() as c:
|
|
116
|
+
c.execute(
|
|
117
|
+
"INSERT OR REPLACE INTO cache(key, value, ts, model, out_tokens) VALUES (?, ?, ?, ?, ?)",
|
|
118
|
+
(key, payload, time.time(), model, out_tokens),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def clear(self, **kwargs: Any) -> None:
|
|
122
|
+
with _lock, self._conn() as c:
|
|
123
|
+
c.execute("DELETE FROM cache")
|
|
124
|
+
c.execute("UPDATE stats SET val = 0")
|
|
125
|
+
|
|
126
|
+
def stats(self) -> dict:
|
|
127
|
+
with _lock, self._conn() as c:
|
|
128
|
+
rows = dict(c.execute("SELECT name, val FROM stats").fetchall())
|
|
129
|
+
entries = c.execute("SELECT COUNT(*) FROM cache").fetchone()[0]
|
|
130
|
+
hits, misses = int(rows.get("hits", 0)), int(rows.get("misses", 0))
|
|
131
|
+
total = hits + misses
|
|
132
|
+
tokens_saved = int(rows.get("tokens_saved", 0))
|
|
133
|
+
# Conservative blended estimate: $0.002 / 1K output tokens saved.
|
|
134
|
+
cost_saved = round(tokens_saved / 1000 * 0.002, 6)
|
|
135
|
+
return {
|
|
136
|
+
"entries": entries,
|
|
137
|
+
"hits": hits,
|
|
138
|
+
"misses": misses,
|
|
139
|
+
"hit_rate": round(hits / total, 3) if total else 0.0,
|
|
140
|
+
"tokens_saved": tokens_saved,
|
|
141
|
+
"est_usd_saved": cost_saved,
|
|
142
|
+
"path": str(self.path),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def enable_caching(path: str | Path = _DEFAULT_PATH, ttl: int | None = None) -> AgentXCache:
|
|
147
|
+
"""Install a global LLM response cache. All providers benefit automatically."""
|
|
148
|
+
from langchain_core.globals import set_llm_cache
|
|
149
|
+
|
|
150
|
+
cache = AgentXCache(path, ttl=ttl)
|
|
151
|
+
set_llm_cache(cache)
|
|
152
|
+
return cache
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def disable_caching() -> None:
|
|
156
|
+
from langchain_core.globals import set_llm_cache
|
|
157
|
+
|
|
158
|
+
set_llm_cache(None)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def cache_stats(path: str | Path = _DEFAULT_PATH) -> dict:
|
|
162
|
+
return AgentXCache(path).stats()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def clear_cache(path: str | Path = _DEFAULT_PATH) -> None:
|
|
166
|
+
AgentXCache(path).clear()
|
|
@@ -68,6 +68,38 @@ def dashboard(
|
|
|
68
68
|
raise typer.Exit(1) from exc
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
cache_app = typer.Typer(help="Inspect/clear the local LLM response cache.", no_args_is_help=True)
|
|
72
|
+
app.add_typer(cache_app, name="cache")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@cache_app.command("stats")
|
|
76
|
+
def cache_stats_cmd(
|
|
77
|
+
path: Path = typer.Option(".agentx/llm_cache.sqlite", "--path", help="Cache DB path."),
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Show cache hit rate and estimated tokens/$ saved."""
|
|
80
|
+
from .cache import cache_stats
|
|
81
|
+
|
|
82
|
+
s = cache_stats(path)
|
|
83
|
+
table = Table(title="LLM response cache")
|
|
84
|
+
table.add_column("metric", style="cyan")
|
|
85
|
+
table.add_column("value")
|
|
86
|
+
for k in ("entries", "hits", "misses", "hit_rate", "tokens_saved", "est_usd_saved"):
|
|
87
|
+
table.add_row(k, str(s[k]))
|
|
88
|
+
console.print(table)
|
|
89
|
+
console.print(f"[dim]{s['path']}[/]")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@cache_app.command("clear")
|
|
93
|
+
def cache_clear_cmd(
|
|
94
|
+
path: Path = typer.Option(".agentx/llm_cache.sqlite", "--path", help="Cache DB path."),
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Clear all cached responses and reset stats."""
|
|
97
|
+
from .cache import clear_cache
|
|
98
|
+
|
|
99
|
+
clear_cache(path)
|
|
100
|
+
console.print("[green]✓[/] Cache cleared.")
|
|
101
|
+
|
|
102
|
+
|
|
71
103
|
@app.command()
|
|
72
104
|
def mcp(
|
|
73
105
|
print_config: bool = typer.Option(False, "--print-config", help="Print MCP client config for Claude/Codex/Copilot and exit."),
|
|
@@ -10,7 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
from ..scaffold import AgentSpec, ProjectSpec, generate_project
|
|
11
11
|
from .recommend import recommend_spec
|
|
12
12
|
|
|
13
|
-
_ALL_FEATURES = ["rag", "memory", "mcp", "skills", "observability", "guardrails", "serve", "docker", "ci", "evals"]
|
|
13
|
+
_ALL_FEATURES = ["rag", "memory", "mcp", "skills", "observability", "guardrails", "serve", "docker", "ci", "evals", "cache"]
|
|
14
14
|
_KEY_FILES_MAX = 6000
|
|
15
15
|
|
|
16
16
|
|
|
@@ -26,6 +26,7 @@ def _apply_features(spec: ProjectSpec, features: list[str]) -> None:
|
|
|
26
26
|
spec.docker = "docker" in fl
|
|
27
27
|
spec.ci = "ci" in fl
|
|
28
28
|
spec.evals = "evals" in fl
|
|
29
|
+
spec.use_cache = "cache" in fl
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def build_project_from_statement(
|
|
@@ -65,6 +65,8 @@ def recommend_spec(problem_statement: str) -> dict:
|
|
|
65
65
|
production = _has(text, "production", "enterprise", "scalable", "observability",
|
|
66
66
|
"monitor", "trace", "secure", "reliable", "deploy", "high traffic")
|
|
67
67
|
coding = _has(text, "coding", "write code", "code generation", "programming task")
|
|
68
|
+
cache = _has(text, "cache", "cost", "cheap", "latency", "high traffic", "high-traffic",
|
|
69
|
+
"fast response", "reduce cost", "save money", "repeated")
|
|
68
70
|
|
|
69
71
|
features: list[str] = []
|
|
70
72
|
if rag:
|
|
@@ -77,6 +79,8 @@ def recommend_spec(problem_statement: str) -> dict:
|
|
|
77
79
|
features.append("skills")
|
|
78
80
|
if serve or production:
|
|
79
81
|
features.append("serve")
|
|
82
|
+
if cache or production:
|
|
83
|
+
features.append("cache")
|
|
80
84
|
if production:
|
|
81
85
|
features += ["observability", "guardrails", "docker", "ci", "evals"]
|
|
82
86
|
# de-dupe, stable order
|
|
@@ -218,6 +218,21 @@ def _trends_panel():
|
|
|
218
218
|
c2.metric("Total tokens", f"{agg['total_tokens']:,}")
|
|
219
219
|
c3.metric("Total cost", f"${agg['total_cost_usd']:.4f}")
|
|
220
220
|
c4.metric("Avg latency", f"{agg['avg_latency_ms']} ms")
|
|
221
|
+
|
|
222
|
+
# Response-cache savings (if caching has been used in this project).
|
|
223
|
+
cache_path = _PROJECT / ".agentx" / "llm_cache.sqlite"
|
|
224
|
+
if cache_path.exists():
|
|
225
|
+
try:
|
|
226
|
+
from agentx.cache import cache_stats
|
|
227
|
+
|
|
228
|
+
cs = cache_stats(cache_path)
|
|
229
|
+
st.markdown("###### 💾 Response cache")
|
|
230
|
+
d1, d2, d3 = st.columns(3)
|
|
231
|
+
d1.metric("Hit rate", f"{cs['hit_rate']:.0%}", help=f"{cs['hits']} hits / {cs['misses']} misses")
|
|
232
|
+
d2.metric("Tokens saved", f"{cs['tokens_saved']:,}")
|
|
233
|
+
d3.metric("Est. $ saved", f"${cs['est_usd_saved']:.4f}")
|
|
234
|
+
except Exception: # noqa: BLE001
|
|
235
|
+
pass
|
|
221
236
|
rows = [r for r in log.events() if r.get("kind") == "run"]
|
|
222
237
|
if not rows:
|
|
223
238
|
st.info("No runs logged yet — use **Test run** to populate trends.")
|
|
@@ -52,6 +52,7 @@ class ProjectSpec(BaseModel):
|
|
|
52
52
|
docker: bool = False # Dockerfile + docker-compose.yml
|
|
53
53
|
ci: bool = False # GitHub Actions (lint + test [+ eval])
|
|
54
54
|
evals: bool = False # LLM-as-judge eval harness (+ CI gate)
|
|
55
|
+
use_cache: bool = False # global LLM response cache (cost/latency saver)
|
|
55
56
|
create_venv: bool = True
|
|
56
57
|
run_sync: bool = False
|
|
57
58
|
# When set, generated pyproject depends on agentx from this local path
|
|
@@ -61,7 +62,7 @@ class ProjectSpec(BaseModel):
|
|
|
61
62
|
def enable_enterprise(self) -> "ProjectSpec":
|
|
62
63
|
"""Turn on the full enterprise feature set in one call."""
|
|
63
64
|
self.observability = self.guardrails = self.serve = True
|
|
64
|
-
self.docker = self.ci = self.evals = True
|
|
65
|
+
self.docker = self.ci = self.evals = self.use_cache = True
|
|
65
66
|
return self
|
|
66
67
|
|
|
67
68
|
@property
|
|
@@ -8,6 +8,8 @@ from dotenv import load_dotenv
|
|
|
8
8
|
{% endif %}
|
|
9
9
|
{% if spec.guardrails %}from .guardrails import guard_input, guard_output
|
|
10
10
|
{% endif %}
|
|
11
|
+
{% if spec.use_cache %}from agentx.cache import enable_caching
|
|
12
|
+
{% endif %}
|
|
11
13
|
{% if spec.framework == 'langgraph' %}
|
|
12
14
|
from agentx.frameworks import run_agent
|
|
13
15
|
from .agents import build_agents
|
|
@@ -17,6 +19,9 @@ def main() -> None:
|
|
|
17
19
|
load_dotenv()
|
|
18
20
|
{% if spec.observability %}
|
|
19
21
|
init_observability()
|
|
22
|
+
{% endif %}
|
|
23
|
+
{% if spec.use_cache %}
|
|
24
|
+
enable_caching() # cache LLM responses → lower cost & latency
|
|
20
25
|
{% endif %}
|
|
21
26
|
agents = build_agents()
|
|
22
27
|
agent_name, agent = next(iter(agents.items()))
|
|
@@ -52,6 +57,9 @@ def main() -> None:
|
|
|
52
57
|
load_dotenv()
|
|
53
58
|
{% if spec.observability %}
|
|
54
59
|
init_observability()
|
|
60
|
+
{% endif %}
|
|
61
|
+
{% if spec.use_cache %}
|
|
62
|
+
enable_caching() # cache LLM responses → lower cost & latency
|
|
55
63
|
{% endif %}
|
|
56
64
|
print("🧬 {{ spec.slug }} (CrewAI). Type 'quit' to exit.\n")
|
|
57
65
|
while True:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Tests for the LLM response cache (BaseCache-backed, SQLite). No live LLM."""
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from agentx.cache import AgentXCache, cache_stats, clear_cache, enable_caching
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _gens(text: str):
|
|
8
|
+
from langchain_core.outputs import Generation
|
|
9
|
+
|
|
10
|
+
return [Generation(text=text)]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_cache_lookup_miss_then_hit(tmp_path):
|
|
14
|
+
cache = AgentXCache(tmp_path / "c.sqlite")
|
|
15
|
+
assert cache.lookup("hello", "llm::gpt-4o-mini") is None # miss
|
|
16
|
+
cache.update("hello", "llm::gpt-4o-mini", _gens("hi there"))
|
|
17
|
+
got = cache.lookup("hello", "llm::gpt-4o-mini")
|
|
18
|
+
assert got is not None
|
|
19
|
+
assert got[0].text == "hi there"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_cache_stats_track_hits_and_savings(tmp_path):
|
|
23
|
+
cache = AgentXCache(tmp_path / "c.sqlite")
|
|
24
|
+
cache.update("q", "llm::gpt-4o-mini", _gens("a fairly long cached answer " * 5))
|
|
25
|
+
cache.lookup("q", "llm::gpt-4o-mini") # hit
|
|
26
|
+
cache.lookup("nope", "llm::gpt-4o-mini") # miss
|
|
27
|
+
s = cache.stats()
|
|
28
|
+
assert s["hits"] == 1 and s["misses"] == 1
|
|
29
|
+
assert s["hit_rate"] == 0.5
|
|
30
|
+
assert s["tokens_saved"] > 0
|
|
31
|
+
assert s["est_usd_saved"] >= 0.0
|
|
32
|
+
assert s["entries"] == 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_cache_ttl_expiry(tmp_path):
|
|
36
|
+
cache = AgentXCache(tmp_path / "c.sqlite", ttl=1)
|
|
37
|
+
cache.update("k", "llm::x", _gens("v"))
|
|
38
|
+
assert cache.lookup("k", "llm::x") is not None
|
|
39
|
+
time.sleep(1.2)
|
|
40
|
+
assert cache.lookup("k", "llm::x") is None # expired
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_clear_cache(tmp_path):
|
|
44
|
+
p = tmp_path / "c.sqlite"
|
|
45
|
+
cache = AgentXCache(p)
|
|
46
|
+
cache.update("k", "llm::x", _gens("v"))
|
|
47
|
+
clear_cache(p)
|
|
48
|
+
assert cache_stats(p)["entries"] == 0
|
|
49
|
+
assert cache.lookup("k", "llm::x") is None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_enable_caching_installs_global(tmp_path):
|
|
53
|
+
from langchain_core.globals import get_llm_cache
|
|
54
|
+
|
|
55
|
+
from agentx.cache import disable_caching
|
|
56
|
+
|
|
57
|
+
cache = enable_caching(tmp_path / "c.sqlite")
|
|
58
|
+
assert get_llm_cache() is cache
|
|
59
|
+
disable_caching()
|
|
60
|
+
assert get_llm_cache() is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|