chakpy 0.3.2__tar.gz → 0.3.4__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 (127) hide show
  1. {chakpy-0.3.2/chakpy.egg-info → chakpy-0.3.4}/PKG-INFO +40 -15
  2. {chakpy-0.3.2 → chakpy-0.3.4}/README.md +28 -11
  3. {chakpy-0.3.2 → chakpy-0.3.4}/chak/__init__.py +5 -1
  4. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/__init__.py +24 -2
  5. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/manager.py +2 -1
  6. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/__init__.py +3 -1
  7. chakpy-0.3.4/chak/tools/std/sandbox.py +190 -0
  8. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/search.py +30 -8
  9. {chakpy-0.3.2 → chakpy-0.3.4/chakpy.egg-info}/PKG-INFO +40 -15
  10. {chakpy-0.3.2 → chakpy-0.3.4}/chakpy.egg-info/SOURCES.txt +2 -0
  11. {chakpy-0.3.2 → chakpy-0.3.4}/chakpy.egg-info/requires.txt +12 -3
  12. chakpy-0.3.4/examples/tool_calling_std.py +279 -0
  13. {chakpy-0.3.2 → chakpy-0.3.4}/pyproject.toml +17 -4
  14. {chakpy-0.3.2 → chakpy-0.3.4}/LICENSE +0 -0
  15. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/__init__.py +0 -0
  16. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/audio.py +0 -0
  17. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/base.py +0 -0
  18. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/csv.py +0 -0
  19. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/doc.py +0 -0
  20. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/excel.py +0 -0
  21. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/image.py +0 -0
  22. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/link.py +0 -0
  23. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/pdf.py +0 -0
  24. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/txt.py +0 -0
  25. {chakpy-0.3.2 → chakpy-0.3.4}/chak/attachment/video.py +0 -0
  26. {chakpy-0.3.2 → chakpy-0.3.4}/chak/context/__init__.py +0 -0
  27. {chakpy-0.3.2 → chakpy-0.3.4}/chak/context/handlers/__init__.py +0 -0
  28. {chakpy-0.3.2 → chakpy-0.3.4}/chak/context/handlers/base.py +0 -0
  29. {chakpy-0.3.2 → chakpy-0.3.4}/chak/context/handlers/fifo.py +0 -0
  30. {chakpy-0.3.2 → chakpy-0.3.4}/chak/context/handlers/lru.py +0 -0
  31. {chakpy-0.3.2 → chakpy-0.3.4}/chak/context/handlers/noop.py +0 -0
  32. {chakpy-0.3.2 → chakpy-0.3.4}/chak/context/handlers/summarize.py +0 -0
  33. {chakpy-0.3.2 → chakpy-0.3.4}/chak/conversation.py +0 -0
  34. {chakpy-0.3.2 → chakpy-0.3.4}/chak/exceptions.py +0 -0
  35. {chakpy-0.3.2 → chakpy-0.3.4}/chak/message.py +0 -0
  36. {chakpy-0.3.2 → chakpy-0.3.4}/chak/metadata.py +0 -0
  37. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/__init__.py +0 -0
  38. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/__init__.py +0 -0
  39. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/anthropic.py +0 -0
  40. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/azure.py +0 -0
  41. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/baidu.py +0 -0
  42. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/bailian.py +0 -0
  43. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/base.py +0 -0
  44. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/deepseek.py +0 -0
  45. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/google.py +0 -0
  46. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/iflytek.py +0 -0
  47. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/minimax.py +0 -0
  48. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/mistral.py +0 -0
  49. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/moonshot.py +0 -0
  50. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/ollama.py +0 -0
  51. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/openai.py +0 -0
  52. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/siliconflow.py +0 -0
  53. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/tencent.py +0 -0
  54. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/vllm.py +0 -0
  55. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/volcengine.py +0 -0
  56. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/xai.py +0 -0
  57. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/llm/zhipu.py +0 -0
  58. {chakpy-0.3.2 → chakpy-0.3.4}/chak/providers/types.py +0 -0
  59. {chakpy-0.3.2 → chakpy-0.3.4}/chak/schemas.py +0 -0
  60. {chakpy-0.3.2 → chakpy-0.3.4}/chak/server/__init__.py +0 -0
  61. {chakpy-0.3.2 → chakpy-0.3.4}/chak/server/app.py +0 -0
  62. {chakpy-0.3.2 → chakpy-0.3.4}/chak/server/config.py +0 -0
  63. {chakpy-0.3.2 → chakpy-0.3.4}/chak/server/playground.html +0 -0
  64. {chakpy-0.3.2 → chakpy-0.3.4}/chak/server/schemas.py +0 -0
  65. {chakpy-0.3.2 → chakpy-0.3.4}/chak/server/websocket.py +0 -0
  66. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/mcp/__init__.py +0 -0
  67. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/mcp/client.py +0 -0
  68. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/mcp/server.py +0 -0
  69. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/mcp/tool.py +0 -0
  70. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/native/__init__.py +0 -0
  71. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/native/function.py +0 -0
  72. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/native/object.py +0 -0
  73. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/skills/__init__.py +0 -0
  74. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/skills/claude.py +0 -0
  75. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/skills/object.py +0 -0
  76. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/bash.py +0 -0
  77. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/filesystem.py +0 -0
  78. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/http.py +0 -0
  79. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/pdf.py +0 -0
  80. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/python.py +0 -0
  81. {chakpy-0.3.2 → chakpy-0.3.4}/chak/tools/std/web.py +0 -0
  82. {chakpy-0.3.2 → chakpy-0.3.4}/chak/utils/__init__.py +0 -0
  83. {chakpy-0.3.2 → chakpy-0.3.4}/chak/utils/logger.py +0 -0
  84. {chakpy-0.3.2 → chakpy-0.3.4}/chak/utils/model_info.py +0 -0
  85. {chakpy-0.3.2 → chakpy-0.3.4}/chak/utils/models.py +0 -0
  86. {chakpy-0.3.2 → chakpy-0.3.4}/chak/utils/uri.py +0 -0
  87. {chakpy-0.3.2 → chakpy-0.3.4}/chakpy.egg-info/dependency_links.txt +0 -0
  88. {chakpy-0.3.2 → chakpy-0.3.4}/chakpy.egg-info/top_level.txt +0 -0
  89. {chakpy-0.3.2 → chakpy-0.3.4}/examples/__init__.py +0 -0
  90. {chakpy-0.3.2 → chakpy-0.3.4}/examples/chat_attachment.py +0 -0
  91. {chakpy-0.3.2 → chakpy-0.3.4}/examples/chat_event.py +0 -0
  92. {chakpy-0.3.2 → chakpy-0.3.4}/examples/chat_reasoning.py +0 -0
  93. {chakpy-0.3.2 → chakpy-0.3.4}/examples/chat_stream.py +0 -0
  94. {chakpy-0.3.2 → chakpy-0.3.4}/examples/context_handler_fifo.py +0 -0
  95. {chakpy-0.3.2 → chakpy-0.3.4}/examples/context_handler_lru.py +0 -0
  96. {chakpy-0.3.2 → chakpy-0.3.4}/examples/context_handler_summarization.py +0 -0
  97. {chakpy-0.3.2 → chakpy-0.3.4}/examples/custom_payload_demo.py +0 -0
  98. {chakpy-0.3.2 → chakpy-0.3.4}/examples/event_stream_chat_demo.py +0 -0
  99. {chakpy-0.3.2 → chakpy-0.3.4}/examples/message_filtering_demo.py +0 -0
  100. {chakpy-0.3.2 → chakpy-0.3.4}/examples/multimodal_chat_documents.py +0 -0
  101. {chakpy-0.3.2 → chakpy-0.3.4}/examples/multimodal_chat_image.py +0 -0
  102. {chakpy-0.3.2 → chakpy-0.3.4}/examples/my_calculator_mcp_server.py +0 -0
  103. {chakpy-0.3.2 → chakpy-0.3.4}/examples/simple_chat_bailian_qwen.py +0 -0
  104. {chakpy-0.3.2 → chakpy-0.3.4}/examples/simple_chat_ollama_qwen.py +0 -0
  105. {chakpy-0.3.2 → chakpy-0.3.4}/examples/simple_chat_openai_gpt4.py +0 -0
  106. {chakpy-0.3.2 → chakpy-0.3.4}/examples/simple_gateway_server.py +0 -0
  107. {chakpy-0.3.2 → chakpy-0.3.4}/examples/structured_output_multimodal.py +0 -0
  108. {chakpy-0.3.2 → chakpy-0.3.4}/examples/structured_output_simple.py +0 -0
  109. {chakpy-0.3.2 → chakpy-0.3.4}/examples/test_attachments.py +0 -0
  110. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_approval_web_demo.py +0 -0
  111. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_chat_functions.py +0 -0
  112. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_chat_functions_pydantic.py +0 -0
  113. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_chat_mcp_http.py +0 -0
  114. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_chat_mcp_sse.py +0 -0
  115. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_chat_mcp_stdio.py +0 -0
  116. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_chat_objects_pydantic.py +0 -0
  117. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_chat_objects_stateful.py +0 -0
  118. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_claude_skill.py +0 -0
  119. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_hitl_demo.py +0 -0
  120. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_parallel_demo.py +0 -0
  121. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_skills_large_scale.py +0 -0
  122. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_skills_scenario.py +0 -0
  123. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_calling_skills_simple.py +0 -0
  124. {chakpy-0.3.2 → chakpy-0.3.4}/examples/tool_dynamic_management_demo.py +0 -0
  125. {chakpy-0.3.2 → chakpy-0.3.4}/examples/turn_id_tracking.py +0 -0
  126. {chakpy-0.3.2 → chakpy-0.3.4}/examples/usage_token_tracking.py +0 -0
  127. {chakpy-0.3.2 → chakpy-0.3.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chakpy
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: A simple, yet elegant, LLM API routing library
5
5
  Author: Chak Contributors
6
6
  License: MIT
@@ -25,9 +25,6 @@ Requires-Dist: anthropic>=0.40.0
25
25
  Requires-Dist: pydantic>=2.0.0
26
26
  Requires-Dist: dashscope>=1.20.0
27
27
  Requires-Dist: httpx>=0.24.0
28
- Requires-Dist: pymupdf4llm>=0.0.17
29
- Requires-Dist: pymupdf-layout>=1.27.0
30
- Requires-Dist: markdown>=3.4.0
31
28
  Requires-Dist: boto3>=1.26.0
32
29
  Requires-Dist: tiktoken>=0.5.0
33
30
  Requires-Dist: loguru>=0.7.0
@@ -51,6 +48,16 @@ Requires-Dist: readability-lxml>=0.8.1; extra == "web"
51
48
  Requires-Dist: firecrawl-py>=1.0.0; extra == "web"
52
49
  Requires-Dist: tavily-python>=0.3.0; extra == "web"
53
50
  Requires-Dist: ddgs>=0.1.0; extra == "web"
51
+ Provides-Extra: tools
52
+ Requires-Dist: pymupdf4llm>=0.0.17; extra == "tools"
53
+ Requires-Dist: pymupdf-layout>=1.27.0; extra == "tools"
54
+ Requires-Dist: markdown>=3.4.0; extra == "tools"
55
+ Requires-Dist: beautifulsoup4>=4.12.0; extra == "tools"
56
+ Requires-Dist: readability-lxml>=0.8.1; extra == "tools"
57
+ Requires-Dist: firecrawl-py>=1.0.0; extra == "tools"
58
+ Requires-Dist: tavily-python>=0.3.0; extra == "tools"
59
+ Requires-Dist: ddgs>=0.1.0; extra == "tools"
60
+ Requires-Dist: e2b>=2.0.0; extra == "tools"
54
61
  Provides-Extra: all
55
62
  Requires-Dist: fastapi>=0.104.0; extra == "all"
56
63
  Requires-Dist: uvicorn>=0.24.0; extra == "all"
@@ -70,6 +77,7 @@ Requires-Dist: ddgs>=0.1.0; extra == "all"
70
77
  Requires-Dist: pymupdf4llm>=0.0.17; extra == "all"
71
78
  Requires-Dist: pymupdf-layout>=1.27.0; extra == "all"
72
79
  Requires-Dist: markdown>=3.4.0; extra == "all"
80
+ Requires-Dist: e2b>=2.0.0; extra == "all"
73
81
  Dynamic: license-file
74
82
 
75
83
  <div align="center">
@@ -98,6 +106,8 @@ chak is not another liteLLM, one-api, or OpenRouter, but a client library that a
98
106
 
99
107
  ## 🌵 What's New
100
108
 
109
+ - **2026-04-27 | v0.3.4** - e2b Sandbox:
110
+ - **Sandbox tool**: New `Sandbox` built-in tool — run code in an isolated e2b cloud VM. See [Built-in Standard Tools](#built-in-standard-tools).
101
111
  - **2026-04-22 | v0.3.2** - Built-in standard tools:
102
112
  - **Built-in standard tools**: 7 atomic-level tools now ship with chak — `Bash`, `Python`, `FileSystem`, `Web`, `Search`, `Http`, `Pdf`. Drop them directly into `tools=[]` to give any agent baseline capabilities. See [Built-in Standard Tools](#built-in-standard-tools).
103
113
  - **2026-04-08 | v0.3.1** - Human-in-the-Loop upgrade & built-in execution tools:
@@ -342,6 +352,9 @@ OpenAI, Google Gemini, Azure OpenAI, Anthropic Claude, Alibaba Bailian, Baidu We
342
352
  # Basic installation (SDK only)
343
353
  pip install chakpy
344
354
 
355
+ # With built-in standard tools (Pdf, Web, Search, Sandbox)
356
+ pip install chakpy[tools]
357
+
345
358
  # With server support
346
359
  pip install chakpy[server]
347
360
 
@@ -349,6 +362,13 @@ pip install chakpy[server]
349
362
  pip install chakpy[all]
350
363
  ```
351
364
 
365
+ | Extra | What it installs |
366
+ |-------|------------------|
367
+ | `tools` | pymupdf4llm, beautifulsoup4, readability-lxml, ddgs, e2b (and optional API clients for Firecrawl/Tavily) |
368
+ | `server` | fastapi, uvicorn, websockets, pyyaml |
369
+ | `web` | beautifulsoup4, readability-lxml, firecrawl-py, tavily-python, ddgs |
370
+ | `all` | everything above |
371
+
352
372
  ### Chat with global models in a few lines
353
373
 
354
374
  ```python
@@ -615,10 +635,12 @@ See full example: [examples/tool_calling_claude_skill.py](examples/tool_calling_
615
635
 
616
636
  ### Built-in Standard Tools
617
637
 
618
- chak ships 7 atomic tools that cover the most common agent needs out of the box. Import and pass them directly — no wrapping, no configuration required.
638
+ chak ships 8 atomic tools that cover the most common agent needs out of the box. Import and pass them directly — no wrapping, no configuration required.
639
+
640
+ > Tools with optional dependencies require `pip install chakpy[tools]` (or install individual packages as needed — each tool prints an actionable `pip install` message if a dependency is missing).
619
641
 
620
642
  ```python
621
- from chak.tools.std import Bash, Python, FileSystem, Web, Search, Http, Pdf
643
+ from chak.tools.std import Bash, Python, FileSystem, Web, Search, Http, Pdf, Sandbox
622
644
 
623
645
  conv = chak.Conversation(
624
646
  "openai/gpt-4o",
@@ -629,18 +651,21 @@ conv = chak.Conversation(
629
651
  response = await conv.asend("Search for the latest Python release, then save a summary to ./notes.txt")
630
652
  ```
631
653
 
632
- | Tool | Class | What it does |
633
- |------|-------|--------------|
634
- | Shell | `Bash` | Run any shell command. Cross-platform (PowerShell on Windows, bash on Unix). |
635
- | Code Interpreter | `Python` | Write and run Python code snippets in the active venv. |
636
- | File System | `FileSystem` | Read, write, edit, list, search, and delete files and directories. |
637
- | Web | `Web` | Fetch and parse web pages into clean text (Firecrawl Jina httpx fallback). |
638
- | Search | `Search` | Search the web and return structured results (TavilyBraveDuckDuckGo fallback). |
639
- | HTTP Client | `Http` | Full HTTP client GET, POST, PUT, PATCH, DELETE with headers and body. |
640
- | PDF | `Pdf` | Extract text and tables from PDF files (local path or URL). |
654
+ | Tool | Class | What it does | Optional deps |
655
+ |------|-------|--------------|---------------|
656
+ | Shell | `Bash` | Run any shell command. Cross-platform (PowerShell on Windows, bash on Unix). | — |
657
+ | Code Interpreter | `Python` | Write and run Python code snippets in the active venv. | — |
658
+ | File System | `FileSystem` | Read, write, edit, list, search, and delete files and directories. | — |
659
+ | HTTP Client | `Http` | Full HTTP client GET, POST, PUT, PATCH, DELETE with headers and body. | — |
660
+ | Web | `Web` | Fetch and parse web pages into clean text (FirecrawlJinahttpx fallback). | `firecrawl-py` (Layer 1, needs API key), `beautifulsoup4`, `readability-lxml` |
661
+ | Search | `Search` | Search the web and return structured results (Tavily Brave DuckDuckGo fallback). | `ddgs`, `tavily-python` |
662
+ | PDF | `Pdf` | Extract text and tables from PDF files (local path or URL). | `pymupdf4llm` |
663
+ | Sandbox | `Sandbox` | Run multi-file code projects in an isolated e2b cloud sandbox. Supports any language reachable from bash. | `e2b`, `E2B_API_KEY` |
641
664
 
642
665
  These tools are intentionally minimal and composable. Combine them freely or mix with your own custom tools.
643
666
 
667
+ See [`examples/tool_calling_std.py`](examples/tool_calling_std.py) for a runnable demo of every tool, including an `all` mode that runs them all in sequence.
668
+
644
669
  > ⚠️ **Security**: `Bash` and `Python` execute with host-process permissions. For production deployments, use `hitl_handler` for human approval, or run inside a sandboxed environment. `Bash` supports `deny_patterns`, `sensitive_files`, and `working_dir` parameters for fine-grained access control.
645
670
 
646
671
  ### Pass Functions
@@ -24,6 +24,8 @@ chak is not another liteLLM, one-api, or OpenRouter, but a client library that a
24
24
 
25
25
  ## 🌵 What's New
26
26
 
27
+ - **2026-04-27 | v0.3.4** - e2b Sandbox:
28
+ - **Sandbox tool**: New `Sandbox` built-in tool — run code in an isolated e2b cloud VM. See [Built-in Standard Tools](#built-in-standard-tools).
27
29
  - **2026-04-22 | v0.3.2** - Built-in standard tools:
28
30
  - **Built-in standard tools**: 7 atomic-level tools now ship with chak — `Bash`, `Python`, `FileSystem`, `Web`, `Search`, `Http`, `Pdf`. Drop them directly into `tools=[]` to give any agent baseline capabilities. See [Built-in Standard Tools](#built-in-standard-tools).
29
31
  - **2026-04-08 | v0.3.1** - Human-in-the-Loop upgrade & built-in execution tools:
@@ -268,6 +270,9 @@ OpenAI, Google Gemini, Azure OpenAI, Anthropic Claude, Alibaba Bailian, Baidu We
268
270
  # Basic installation (SDK only)
269
271
  pip install chakpy
270
272
 
273
+ # With built-in standard tools (Pdf, Web, Search, Sandbox)
274
+ pip install chakpy[tools]
275
+
271
276
  # With server support
272
277
  pip install chakpy[server]
273
278
 
@@ -275,6 +280,13 @@ pip install chakpy[server]
275
280
  pip install chakpy[all]
276
281
  ```
277
282
 
283
+ | Extra | What it installs |
284
+ |-------|------------------|
285
+ | `tools` | pymupdf4llm, beautifulsoup4, readability-lxml, ddgs, e2b (and optional API clients for Firecrawl/Tavily) |
286
+ | `server` | fastapi, uvicorn, websockets, pyyaml |
287
+ | `web` | beautifulsoup4, readability-lxml, firecrawl-py, tavily-python, ddgs |
288
+ | `all` | everything above |
289
+
278
290
  ### Chat with global models in a few lines
279
291
 
280
292
  ```python
@@ -541,10 +553,12 @@ See full example: [examples/tool_calling_claude_skill.py](examples/tool_calling_
541
553
 
542
554
  ### Built-in Standard Tools
543
555
 
544
- chak ships 7 atomic tools that cover the most common agent needs out of the box. Import and pass them directly — no wrapping, no configuration required.
556
+ chak ships 8 atomic tools that cover the most common agent needs out of the box. Import and pass them directly — no wrapping, no configuration required.
557
+
558
+ > Tools with optional dependencies require `pip install chakpy[tools]` (or install individual packages as needed — each tool prints an actionable `pip install` message if a dependency is missing).
545
559
 
546
560
  ```python
547
- from chak.tools.std import Bash, Python, FileSystem, Web, Search, Http, Pdf
561
+ from chak.tools.std import Bash, Python, FileSystem, Web, Search, Http, Pdf, Sandbox
548
562
 
549
563
  conv = chak.Conversation(
550
564
  "openai/gpt-4o",
@@ -555,18 +569,21 @@ conv = chak.Conversation(
555
569
  response = await conv.asend("Search for the latest Python release, then save a summary to ./notes.txt")
556
570
  ```
557
571
 
558
- | Tool | Class | What it does |
559
- |------|-------|--------------|
560
- | Shell | `Bash` | Run any shell command. Cross-platform (PowerShell on Windows, bash on Unix). |
561
- | Code Interpreter | `Python` | Write and run Python code snippets in the active venv. |
562
- | File System | `FileSystem` | Read, write, edit, list, search, and delete files and directories. |
563
- | Web | `Web` | Fetch and parse web pages into clean text (Firecrawl Jina httpx fallback). |
564
- | Search | `Search` | Search the web and return structured results (TavilyBraveDuckDuckGo fallback). |
565
- | HTTP Client | `Http` | Full HTTP client GET, POST, PUT, PATCH, DELETE with headers and body. |
566
- | PDF | `Pdf` | Extract text and tables from PDF files (local path or URL). |
572
+ | Tool | Class | What it does | Optional deps |
573
+ |------|-------|--------------|---------------|
574
+ | Shell | `Bash` | Run any shell command. Cross-platform (PowerShell on Windows, bash on Unix). | — |
575
+ | Code Interpreter | `Python` | Write and run Python code snippets in the active venv. | — |
576
+ | File System | `FileSystem` | Read, write, edit, list, search, and delete files and directories. | — |
577
+ | HTTP Client | `Http` | Full HTTP client GET, POST, PUT, PATCH, DELETE with headers and body. | — |
578
+ | Web | `Web` | Fetch and parse web pages into clean text (FirecrawlJinahttpx fallback). | `firecrawl-py` (Layer 1, needs API key), `beautifulsoup4`, `readability-lxml` |
579
+ | Search | `Search` | Search the web and return structured results (Tavily Brave DuckDuckGo fallback). | `ddgs`, `tavily-python` |
580
+ | PDF | `Pdf` | Extract text and tables from PDF files (local path or URL). | `pymupdf4llm` |
581
+ | Sandbox | `Sandbox` | Run multi-file code projects in an isolated e2b cloud sandbox. Supports any language reachable from bash. | `e2b`, `E2B_API_KEY` |
567
582
 
568
583
  These tools are intentionally minimal and composable. Combine them freely or mix with your own custom tools.
569
584
 
585
+ See [`examples/tool_calling_std.py`](examples/tool_calling_std.py) for a runnable demo of every tool, including an `all` mode that runs them all in sequence.
586
+
570
587
  > ⚠️ **Security**: `Bash` and `Python` execute with host-process permissions. For production deployments, use `hitl_handler` for human approval, or run inside a sandboxed environment. `Bash` supports `deny_patterns`, `sensitive_files`, and `working_dir` parameters for fine-grained access control.
571
588
 
572
589
  ### Pass Functions
@@ -16,7 +16,11 @@ Example:
16
16
  >>> conv = chak.Conversation('deepseek@https://custom.api.com:deepseek-chat', api_key='xxx')
17
17
  """
18
18
 
19
- __version__ = "0.3.2"
19
+ try:
20
+ from importlib.metadata import version, PackageNotFoundError
21
+ __version__ = version("chakpy")
22
+ except PackageNotFoundError:
23
+ __version__ = "0.0.0.dev"
20
24
 
21
25
  # Context handlers
22
26
  from .context.handlers import (
@@ -59,6 +59,24 @@ __all__ = [
59
59
  ]
60
60
 
61
61
 
62
+ def _is_duck_tool(obj) -> bool:
63
+ """Return True if obj implements the chak duck-typed tool interface.
64
+
65
+ A duck-typed tool must have:
66
+ - ``name``: a str property or attribute
67
+ - ``to_openai_tool``: a callable that returns the OpenAI schema dict
68
+ - ``call``: an async callable that executes the tool
69
+ """
70
+ name_ok = isinstance(getattr(obj, 'name', None), str) or isinstance(
71
+ getattr(type(obj), 'name', None), property
72
+ )
73
+ return (
74
+ name_ok
75
+ and callable(getattr(obj, 'to_openai_tool', None))
76
+ and callable(getattr(obj, 'call', None))
77
+ )
78
+
79
+
62
80
  def wrap_tools(tools: List[ToolLike]) -> List:
63
81
  """
64
82
  自动包装工具列表
@@ -116,8 +134,12 @@ def wrap_tools(tools: List[ToolLike]) -> List:
116
134
  """
117
135
  wrapped = []
118
136
  for tool in tools:
119
- if isinstance(tool, (MCPTool, NativeFunctionTool, NativeObjectTool, SkillObjectTool, ClaudeSkill, Bash, Python)):
120
- # 已经是工具对象,直接使用
137
+ if isinstance(tool, (MCPTool, NativeFunctionTool, NativeObjectTool, SkillObjectTool, ClaudeSkill)):
138
+ # Already a fully-wrapped tool object, use as-is
139
+ wrapped.append(tool)
140
+ elif _is_duck_tool(tool):
141
+ # Duck-typed tool: has name/to_openai_tool/call — treat as direct tool
142
+ # (e.g. Bash, Python, Sandbox, or any custom std tool)
121
143
  wrapped.append(tool)
122
144
  elif isinstance(tool, SkillBase):
123
145
  # 是 Skill 实例,包装成 SkillObjectTool
@@ -967,7 +967,8 @@ class ToolManager:
967
967
  # Construct final AIMessage from accumulated content, preserving LLM usage metadata
968
968
  final_message = AIMessage(
969
969
  content=accumulated_content,
970
- metadata=_dict_to_metadata(last_metadata)
970
+ metadata=_dict_to_metadata(last_metadata),
971
+ attachments=all_attachments,
971
972
  )
972
973
  new_messages.append(final_message)
973
974
 
@@ -10,6 +10,7 @@ All first-party atomic tools that ship with chak live here:
10
10
  - Search: search the web (Tavily → Brave → DuckDuckGo fallback)
11
11
  - Http: full HTTP client (GET/POST/PUT/PATCH/DELETE)
12
12
  - Pdf: extract text/tables from PDF files (local path or URL)
13
+ - Sandbox: execute multi-file code projects in an isolated e2b cloud sandbox
13
14
  """
14
15
 
15
16
  from .bash import Bash
@@ -19,5 +20,6 @@ from .web import Web
19
20
  from .search import Search
20
21
  from .http import Http
21
22
  from .pdf import Pdf
23
+ from .sandbox import Sandbox
22
24
 
23
- __all__ = ["Bash", "Python", "FileSystem", "Web", "Search", "Http", "Pdf"]
25
+ __all__ = ["Bash", "Python", "FileSystem", "Web", "Search", "Http", "Pdf", "Sandbox"]
@@ -0,0 +1,190 @@
1
+ """
2
+ Sandbox: Built-in e2b cloud sandbox execution tool for chak
3
+
4
+ Executes code in an isolated e2b cloud sandbox (pure shell mode via e2b.Sandbox).
5
+ One-shot design: spin up → write files → run shell script → return output → destroy.
6
+
7
+ Usage:
8
+ from chak.tools.std import Sandbox
9
+
10
+ sb = Sandbox()
11
+ conv = Conversation(model, tools=[sb])
12
+
13
+ Requires:
14
+ e2b package (pip install e2b)
15
+ E2B_API_KEY environment variable (or api_key passed to constructor)
16
+ """
17
+
18
+ import asyncio
19
+ import os
20
+ from typing import Any, Dict, List, Optional
21
+
22
+
23
+ class Sandbox:
24
+ """Built-in e2b cloud sandbox execution tool.
25
+
26
+ Writes source files into an isolated cloud sandbox, then runs a bash
27
+ shell script that controls the full execution sequence (install packages,
28
+ run code, etc.). The sandbox is created fresh for each call and
29
+ destroyed afterwards.
30
+
31
+ Example::
32
+
33
+ sb = Sandbox()
34
+ result = await sb.call({
35
+ "shell_script": "pip install httpx -q\\npython /project/main.py",
36
+ "files": [{"path": "/project/main.py", "content": "import httpx; print(httpx.get('https://example.com').status_code)"}]
37
+ })
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ api_key: Optional[str] = None,
43
+ timeout: int = 120,
44
+ ):
45
+ """
46
+ Args:
47
+ api_key: E2B API key. Falls back to the ``E2B_API_KEY``
48
+ environment variable when not provided.
49
+ timeout: Maximum seconds to wait for the shell script to finish.
50
+ Defaults to 120.
51
+ """
52
+ self._api_key = api_key or os.getenv("E2B_API_KEY")
53
+ self._timeout = timeout
54
+
55
+ # ------------------------------------------------------------------
56
+ # Duck-typing interface required by ToolManager
57
+ # ------------------------------------------------------------------
58
+
59
+ @property
60
+ def name(self) -> str:
61
+ """Tool name exposed to the LLM."""
62
+ return "sandbox"
63
+
64
+ @property
65
+ def description(self) -> str:
66
+ """Short description shown to the LLM."""
67
+ return (
68
+ "Execute code in an isolated cloud sandbox and return its output. "
69
+ "IMPORTANT: Call this tool EXACTLY ONCE per task — never in parallel or sequentially. "
70
+ "Each call creates a completely fresh environment; files do NOT persist between calls. "
71
+ "The entire pipeline (install packages, write files, run code) must be done in a single call. "
72
+ "Write all source files via 'files', then run a bash shell script via 'shell_script'. "
73
+ "Supports any language reachable from bash (Python, Node, Ruby, etc.)."
74
+ )
75
+
76
+ def to_openai_tool(self) -> Dict[str, Any]:
77
+ """Return the OpenAI function-calling schema for this tool."""
78
+ return {
79
+ "type": "function",
80
+ "function": {
81
+ "name": self.name,
82
+ "description": self.description,
83
+ "parameters": {
84
+ "type": "object",
85
+ "properties": {
86
+ "shell_script": {
87
+ "type": "string",
88
+ "description": (
89
+ "Bash shell script to execute inside the sandbox. "
90
+ "Each line is a shell command — install packages, "
91
+ "create directories, run code, etc. "
92
+ "All lines share the same bash session. "
93
+ "Example: \"pip install httpx -q\\npython /project/main.py\""
94
+ ),
95
+ },
96
+ "files": {
97
+ "type": "array",
98
+ "description": (
99
+ "Source files to write into the sandbox before "
100
+ "the shell script runs. "
101
+ "Provide the full content of each file here — "
102
+ "do NOT use heredocs in shell_script for large files."
103
+ ),
104
+ "items": {
105
+ "type": "object",
106
+ "properties": {
107
+ "path": {
108
+ "type": "string",
109
+ "description": "Absolute path inside the sandbox, e.g. /project/main.py",
110
+ },
111
+ "content": {
112
+ "type": "string",
113
+ "description": "Full source code content of the file.",
114
+ },
115
+ },
116
+ "required": ["path", "content"],
117
+ },
118
+ },
119
+ },
120
+ "required": ["shell_script"],
121
+ },
122
+ },
123
+ }
124
+
125
+ async def call(self, arguments: Dict[str, Any], **kwargs) -> str:
126
+ """Execute the sandbox task asynchronously.
127
+
128
+ Args:
129
+ arguments: Dict with ``shell_script`` (required) and optionally
130
+ ``files`` (list of ``{path, content}`` dicts).
131
+ **kwargs: Ignored (present for interface compatibility).
132
+
133
+ Returns:
134
+ Combined stdout + stderr output, or an error string on failure.
135
+ """
136
+ shell_script: str = arguments.get("shell_script", "").strip()
137
+ files: List[Dict[str, str]] = arguments.get("files") or []
138
+
139
+ return await asyncio.to_thread(self._run, shell_script, files)
140
+
141
+ # ------------------------------------------------------------------
142
+ # Internal helpers
143
+ # ------------------------------------------------------------------
144
+
145
+ def _run(self, shell_script: str, files: List[Dict[str, str]]) -> str:
146
+ """Spin up sandbox, write files, run shell script.
147
+
148
+ Called from a worker thread via :func:`asyncio.to_thread`.
149
+ """
150
+ try:
151
+ from e2b import Sandbox as E2BSandbox # pyright: ignore[reportMissingTypeStubs]
152
+ except ImportError:
153
+ return "Error: 'e2b' package is not installed. Run: pip install e2b"
154
+
155
+ if not self._api_key:
156
+ return (
157
+ "Error: E2B API key not found. "
158
+ "Set E2B_API_KEY environment variable or pass api_key to Sandbox()."
159
+ )
160
+
161
+ try:
162
+ with E2BSandbox.create(api_key=self._api_key) as sbx:
163
+ # 1. Write source files
164
+ for f in files:
165
+ path = f.get("path", "")
166
+ content = f.get("content", "")
167
+ if path:
168
+ sbx.files.write(path, content) # pyright: ignore[reportUnknownMemberType]
169
+
170
+ # 2. Write the shell script to a file and execute via bash
171
+ sbx.files.write( # pyright: ignore[reportUnknownMemberType]
172
+ "/tmp/_chak_script.sh",
173
+ "#!/bin/bash\nset -e\n" + shell_script,
174
+ )
175
+ result = sbx.commands.run(
176
+ "bash /tmp/_chak_script.sh",
177
+ timeout=self._timeout,
178
+ )
179
+
180
+ output = result.stdout or ""
181
+ if result.stderr:
182
+ output += "\n[stderr]\n" + result.stderr
183
+ return output.strip() or "(no output)"
184
+
185
+ except Exception as exc:
186
+ return f"Error running sandbox: {exc}"
187
+
188
+ def __repr__(self) -> str:
189
+ key = "set" if self._api_key else "missing"
190
+ return f"<Sandbox api_key={key} timeout={self._timeout}>"
@@ -128,25 +128,40 @@ class Search:
128
128
  return "Error: query must not be empty"
129
129
 
130
130
  n = int(max_results) if max_results is not None else self._max_results
131
+ failures: list[str] = []
131
132
 
132
133
  # Layer 1: Tavily (AI-optimized, best for agent use)
133
134
  if self._tavily_key:
134
135
  results = await asyncio.to_thread(self._search_tavily, query, n)
135
136
  if results:
136
137
  return self._format(results)
138
+ failures.append("Tavily: failed (check API key or network)")
139
+ else:
140
+ failures.append("Tavily: skipped (no tavily_key provided)")
137
141
 
138
142
  # Layer 2: Brave Search (paid, high quality)
139
143
  if self._brave_key:
140
144
  results = await self._search_brave(query, n)
141
145
  if results:
142
146
  return self._format(results)
147
+ failures.append("Brave: failed (check API key or network)")
148
+ else:
149
+ failures.append("Brave: skipped (no brave_key provided)")
143
150
 
144
151
  # Layer 3: DuckDuckGo (free, no key)
145
- results = await self._search_ddgs(query, n)
152
+ results, ddgs_err = await self._search_ddgs(query, n)
146
153
  if results:
147
154
  return self._format(results)
155
+ failures.append(f"DuckDuckGo: {ddgs_err}")
148
156
 
149
- return f"Error: all search backends failed for query: {query!r}"
157
+ detail = "; ".join(failures)
158
+ return (
159
+ f"Error: all search backends failed for query: {query!r}. "
160
+ f"Reasons: {detail}. "
161
+ f"To fix: install at least one backend — "
162
+ f"`pip install ddgs` (free, no key needed) or "
163
+ f"`pip install tavily-python` (requires TAVILY_API_KEY)."
164
+ )
150
165
 
151
166
  except Exception as e:
152
167
  return f"Error searching for {query!r}: {e}"
@@ -213,8 +228,15 @@ class Search:
213
228
  ))
214
229
  return results or None
215
230
 
216
- async def _search_ddgs(self, query: str, n: int) -> Optional[List[SearchResult]]:
217
- """Query DuckDuckGo via duckduckgo-search (ddgs) library."""
231
+ async def _search_ddgs(
232
+ self, query: str, n: int
233
+ ) -> tuple[Optional[List[SearchResult]], str]:
234
+ """Query DuckDuckGo via duckduckgo-search (ddgs) library.
235
+
236
+ Returns:
237
+ (results, error_msg) — results is None on failure;
238
+ error_msg describes the failure reason.
239
+ """
218
240
  try:
219
241
  from ddgs import DDGS
220
242
 
@@ -230,9 +252,9 @@ class Search:
230
252
 
231
253
  items = await asyncio.to_thread(_run)
232
254
  except ImportError:
233
- return None # duckduckgo-search not installed
234
- except Exception:
235
- return None
255
+ return None, "package not installed — run `pip install ddgs`"
256
+ except Exception as e:
257
+ return None, f"runtime error: {e}"
236
258
 
237
259
  results = []
238
260
  for item in items[:n]:
@@ -241,7 +263,7 @@ class Search:
241
263
  url=item.get("href", item.get("url", "")),
242
264
  snippet=item.get("body", ""),
243
265
  ))
244
- return results or None
266
+ return (results or None), "no results returned"
245
267
 
246
268
  def __repr__(self) -> str:
247
269
  tv = "yes" if self._tavily_key else "no"