haiku.rag 0.12.0__tar.gz → 0.13.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.

Potentially problematic release.


This version of haiku.rag might be problematic. Click here for more details.

Files changed (96) hide show
  1. haiku_rag-0.13.0/.dockerignore +66 -0
  2. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/.gitignore +5 -1
  3. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/PKG-INFO +21 -11
  4. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/README.md +17 -7
  5. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/mkdocs.yml +1 -0
  6. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/pyproject.toml +6 -6
  7. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/benchmark.py +1 -1
  8. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/llm_judge.py +1 -1
  9. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/__init__.py +3 -3
  10. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/client.py +52 -55
  11. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/app.py +19 -10
  12. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/chunker.py +1 -1
  13. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/cli.py +74 -33
  14. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/client.py +83 -14
  15. haiku_rag-0.13.0/src/haiku/rag/config/__init__.py +54 -0
  16. haiku_rag-0.13.0/src/haiku/rag/config/loader.py +151 -0
  17. haiku_rag-0.13.0/src/haiku/rag/config/models.py +78 -0
  18. haiku_rag-0.13.0/src/haiku/rag/embeddings/__init__.py +41 -0
  19. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/embeddings/base.py +10 -2
  20. haiku_rag-0.12.0/src/haiku/rag/embeddings/vllm.py → haiku_rag-0.13.0/src/haiku/rag/embeddings/ollama.py +9 -1
  21. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/embeddings/openai.py +8 -0
  22. haiku_rag-0.12.0/src/haiku/rag/embeddings/ollama.py → haiku_rag-0.13.0/src/haiku/rag/embeddings/vllm.py +11 -1
  23. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/embeddings/voyageai.py +8 -0
  24. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/common.py +2 -2
  25. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/mcp.py +14 -8
  26. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/monitor.py +17 -4
  27. haiku_rag-0.13.0/src/haiku/rag/qa/__init__.py +33 -0
  28. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/agent.py +4 -2
  29. haiku_rag-0.13.0/src/haiku/rag/reranking/__init__.py +45 -0
  30. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/reranking/base.py +1 -1
  31. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/reranking/cohere.py +2 -2
  32. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/reranking/mxbai.py +1 -1
  33. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/reranking/vllm.py +1 -1
  34. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/engine.py +19 -12
  35. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/repositories/chunk.py +12 -8
  36. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/repositories/document.py +4 -4
  37. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/repositories/settings.py +19 -9
  38. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/utils.py +9 -9
  39. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/uv.lock +67 -65
  40. haiku_rag-0.12.0/src/haiku/rag/config.py +0 -90
  41. haiku_rag-0.12.0/src/haiku/rag/embeddings/__init__.py +0 -35
  42. haiku_rag-0.12.0/src/haiku/rag/migration.py +0 -316
  43. haiku_rag-0.12.0/src/haiku/rag/qa/__init__.py +0 -20
  44. haiku_rag-0.12.0/src/haiku/rag/reranking/__init__.py +0 -37
  45. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/.pre-commit-config.yaml +0 -0
  46. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/.python-version +0 -0
  47. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/LICENSE +0 -0
  48. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/server.json +0 -0
  49. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/__init__.py +0 -0
  50. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/config.py +0 -0
  51. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/datasets/__init__.py +0 -0
  52. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/datasets/repliqa.py +0 -0
  53. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/datasets/wix.py +0 -0
  54. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/evaluations/prompts.py +0 -0
  55. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/__init__.py +0 -0
  56. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/context.py +0 -0
  57. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/models.py +0 -0
  58. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/prompts.py +0 -0
  59. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/skills.py +0 -0
  60. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/storage.py +0 -0
  61. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/a2a/worker.py +0 -0
  62. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/__init__.py +0 -0
  63. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/base.py +0 -0
  64. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/models.py +0 -0
  65. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/nodes/__init__.py +0 -0
  66. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/nodes/analysis.py +0 -0
  67. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/nodes/plan.py +0 -0
  68. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/nodes/search.py +0 -0
  69. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/nodes/synthesize.py +0 -0
  70. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/graph/prompts.py +0 -0
  71. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/logging.py +0 -0
  72. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/deep/__init__.py +0 -0
  73. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/deep/dependencies.py +0 -0
  74. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/deep/graph.py +0 -0
  75. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/deep/models.py +0 -0
  76. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/deep/nodes.py +0 -0
  77. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/deep/prompts.py +0 -0
  78. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/deep/state.py +0 -0
  79. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/qa/prompts.py +0 -0
  80. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/reader.py +0 -0
  81. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/__init__.py +0 -0
  82. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/common.py +0 -0
  83. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/dependencies.py +0 -0
  84. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/graph.py +0 -0
  85. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/models.py +0 -0
  86. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/prompts.py +0 -0
  87. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/state.py +0 -0
  88. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/research/stream.py +0 -0
  89. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/__init__.py +0 -0
  90. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/models/__init__.py +0 -0
  91. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/models/chunk.py +0 -0
  92. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/models/document.py +0 -0
  93. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/repositories/__init__.py +0 -0
  94. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/upgrades/__init__.py +0 -0
  95. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/upgrades/v0_10_1.py +0 -0
  96. {haiku_rag-0.12.0 → haiku_rag-0.13.0}/src/haiku/rag/store/upgrades/v0_9_3.py +0 -0
@@ -0,0 +1,66 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments (uv best practice)
24
+ .venv/
25
+ venv/
26
+ env/
27
+
28
+ # Node.js
29
+ node_modules/
30
+ .next/
31
+ npm-debug.log*
32
+ yarn-debug.log*
33
+ yarn-error.log*
34
+
35
+ # Data
36
+ *.lancedb/
37
+ data/
38
+
39
+ # Docs
40
+ mkdocs.yml
41
+ docs/
42
+
43
+ # IDE
44
+ .vscode/
45
+ .idea/
46
+ *.swp
47
+ *.swo
48
+ *~
49
+
50
+ # OS
51
+ .DS_Store
52
+ Thumbs.db
53
+
54
+ # Git
55
+ .git/
56
+ .gitignore
57
+
58
+ # Development
59
+ tests/
60
+ .pytest_cache/
61
+ .coverage
62
+ htmlcov/
63
+ src/evaluations/
64
+ server.json
65
+ # Examples
66
+ examples/
@@ -16,8 +16,9 @@ tests/data/
16
16
  .pytest_cache/
17
17
  .ruff_cache/
18
18
 
19
- # environment variables
19
+ # environment variables and config files
20
20
  .env
21
+ haiku.rag.yaml
21
22
  TODO.md
22
23
  PLAN.md
23
24
  DEVNOTES.md
@@ -25,3 +26,6 @@ DEVNOTES.md
25
26
  # mcp registry
26
27
  .mcpregistry_github_token
27
28
  .mcpregistry_registry_token
29
+
30
+ # MkDocs site directory when doing local docs builds
31
+ site/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiku.rag
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: Agentic Retrieval Augmented Generation (RAG) with LanceDB
5
5
  Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
6
6
  License: MIT
@@ -13,9 +13,8 @@ Classifier: Operating System :: MacOS
13
13
  Classifier: Operating System :: Microsoft :: Windows :: Windows 10
14
14
  Classifier: Operating System :: Microsoft :: Windows :: Windows 11
15
15
  Classifier: Operating System :: POSIX :: Linux
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
16
  Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Typing :: Typed
20
19
  Requires-Python: >=3.12
21
20
  Requires-Dist: docling>=2.56.1
@@ -24,8 +23,9 @@ Requires-Dist: httpx>=0.28.1
24
23
  Requires-Dist: lancedb>=0.25.2
25
24
  Requires-Dist: pydantic-ai>=1.0.18
26
25
  Requires-Dist: pydantic-graph>=1.0.18
27
- Requires-Dist: pydantic>=2.12.1
26
+ Requires-Dist: pydantic>=2.12.2
28
27
  Requires-Dist: python-dotenv>=1.1.1
28
+ Requires-Dist: pyyaml>=6.0.1
29
29
  Requires-Dist: rich>=14.2.0
30
30
  Requires-Dist: tiktoken>=0.12.0
31
31
  Requires-Dist: typer>=0.19.2
@@ -44,7 +44,7 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
44
44
 
45
45
  `haiku.rag` is a Retrieval-Augmented Generation (RAG) library built to work with LanceDB as a local vector database. It uses LanceDB for storing embeddings and performs semantic (vector) search as well as full-text search combined through native hybrid search with Reciprocal Rank Fusion. Both open-source (Ollama) as well as commercial (OpenAI, VoyageAI) embedding providers are supported.
46
46
 
47
- > **Note**: Starting with version 0.7.0, haiku.rag uses LanceDB instead of SQLite. If you have an existing SQLite database, use `haiku-rag migrate old_database.sqlite` to migrate your data safely.
47
+ > **Note**: Configuration now uses YAML files instead of environment variables. If you're upgrading from an older version, run `haiku-rag init-config --from-env` to migrate your `.env` file to `haiku.rag.yaml`. See [Configuration](https://ggozad.github.io/haiku.rag/configuration/) for details.
48
48
 
49
49
  ## Features
50
50
 
@@ -65,6 +65,7 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
65
65
 
66
66
  ```bash
67
67
  # Install
68
+ # Python 3.12 or newer required
68
69
  uv pip install haiku.rag
69
70
 
70
71
  # Add documents
@@ -98,14 +99,12 @@ haiku-rag research \
98
99
  # Rebuild database (re-chunk and re-embed all documents)
99
100
  haiku-rag rebuild
100
101
 
101
- # Migrate from SQLite to LanceDB
102
- haiku-rag migrate old_database.sqlite
103
-
104
102
  # Start server with file monitoring
105
- export MONITOR_DIRECTORIES="/path/to/docs"
106
- haiku-rag serve
103
+ haiku-rag serve --monitor
107
104
  ```
108
105
 
106
+ To customize settings, create a `haiku.rag.yaml` config file (see [Configuration](https://ggozad.github.io/haiku.rag/configuration/)).
107
+
109
108
  ## Python Usage
110
109
 
111
110
  ```python
@@ -197,18 +196,29 @@ haiku-rag a2aclient
197
196
  ```
198
197
 
199
198
  The A2A agent provides:
199
+
200
200
  - Multi-turn dialogue with context
201
201
  - Intelligent multi-search for complex questions
202
202
  - Source citations with titles and URIs
203
203
  - Full document retrieval on request
204
204
 
205
+ ## Examples
206
+
207
+ See the [examples directory](examples/) for working examples:
208
+
209
+ - **[Interactive Research Assistant](examples/ag-ui-research/)** - Full-stack research assistant with Pydantic AI and AG-UI featuring human-in-the-loop approval and real-time state synchronization
210
+ - **[Docker Setup](examples/docker/)** - Complete Docker deployment with file monitoring, MCP server, and A2A agent
211
+ - **[A2A Security](examples/a2a-security/)** - Authentication examples (API key, OAuth2, GitHub)
212
+
205
213
  ## Documentation
206
214
 
207
215
  Full documentation at: https://ggozad.github.io/haiku.rag/
208
216
 
209
217
  - [Installation](https://ggozad.github.io/haiku.rag/installation/) - Provider setup
210
- - [Configuration](https://ggozad.github.io/haiku.rag/configuration/) - Environment variables
218
+ - [Configuration](https://ggozad.github.io/haiku.rag/configuration/) - YAML configuration
211
219
  - [CLI](https://ggozad.github.io/haiku.rag/cli/) - Command reference
212
220
  - [Python API](https://ggozad.github.io/haiku.rag/python/) - Complete API docs
213
221
  - [Agents](https://ggozad.github.io/haiku.rag/agents/) - QA agent and multi-agent research
222
+ - [MCP Server](https://ggozad.github.io/haiku.rag/mcp/) - Model Context Protocol integration
223
+ - [A2A Agent](https://ggozad.github.io/haiku.rag/a2a/) - Agent-to-Agent protocol support
214
224
  - [Benchmarks](https://ggozad.github.io/haiku.rag/benchmarks/) - Performance Benchmarks
@@ -4,7 +4,7 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
4
4
 
5
5
  `haiku.rag` is a Retrieval-Augmented Generation (RAG) library built to work with LanceDB as a local vector database. It uses LanceDB for storing embeddings and performs semantic (vector) search as well as full-text search combined through native hybrid search with Reciprocal Rank Fusion. Both open-source (Ollama) as well as commercial (OpenAI, VoyageAI) embedding providers are supported.
6
6
 
7
- > **Note**: Starting with version 0.7.0, haiku.rag uses LanceDB instead of SQLite. If you have an existing SQLite database, use `haiku-rag migrate old_database.sqlite` to migrate your data safely.
7
+ > **Note**: Configuration now uses YAML files instead of environment variables. If you're upgrading from an older version, run `haiku-rag init-config --from-env` to migrate your `.env` file to `haiku.rag.yaml`. See [Configuration](https://ggozad.github.io/haiku.rag/configuration/) for details.
8
8
 
9
9
  ## Features
10
10
 
@@ -25,6 +25,7 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
25
25
 
26
26
  ```bash
27
27
  # Install
28
+ # Python 3.12 or newer required
28
29
  uv pip install haiku.rag
29
30
 
30
31
  # Add documents
@@ -58,14 +59,12 @@ haiku-rag research \
58
59
  # Rebuild database (re-chunk and re-embed all documents)
59
60
  haiku-rag rebuild
60
61
 
61
- # Migrate from SQLite to LanceDB
62
- haiku-rag migrate old_database.sqlite
63
-
64
62
  # Start server with file monitoring
65
- export MONITOR_DIRECTORIES="/path/to/docs"
66
- haiku-rag serve
63
+ haiku-rag serve --monitor
67
64
  ```
68
65
 
66
+ To customize settings, create a `haiku.rag.yaml` config file (see [Configuration](https://ggozad.github.io/haiku.rag/configuration/)).
67
+
69
68
  ## Python Usage
70
69
 
71
70
  ```python
@@ -157,18 +156,29 @@ haiku-rag a2aclient
157
156
  ```
158
157
 
159
158
  The A2A agent provides:
159
+
160
160
  - Multi-turn dialogue with context
161
161
  - Intelligent multi-search for complex questions
162
162
  - Source citations with titles and URIs
163
163
  - Full document retrieval on request
164
164
 
165
+ ## Examples
166
+
167
+ See the [examples directory](examples/) for working examples:
168
+
169
+ - **[Interactive Research Assistant](examples/ag-ui-research/)** - Full-stack research assistant with Pydantic AI and AG-UI featuring human-in-the-loop approval and real-time state synchronization
170
+ - **[Docker Setup](examples/docker/)** - Complete Docker deployment with file monitoring, MCP server, and A2A agent
171
+ - **[A2A Security](examples/a2a-security/)** - Authentication examples (API key, OAuth2, GitHub)
172
+
165
173
  ## Documentation
166
174
 
167
175
  Full documentation at: https://ggozad.github.io/haiku.rag/
168
176
 
169
177
  - [Installation](https://ggozad.github.io/haiku.rag/installation/) - Provider setup
170
- - [Configuration](https://ggozad.github.io/haiku.rag/configuration/) - Environment variables
178
+ - [Configuration](https://ggozad.github.io/haiku.rag/configuration/) - YAML configuration
171
179
  - [CLI](https://ggozad.github.io/haiku.rag/cli/) - Command reference
172
180
  - [Python API](https://ggozad.github.io/haiku.rag/python/) - Complete API docs
173
181
  - [Agents](https://ggozad.github.io/haiku.rag/agents/) - QA agent and multi-agent research
182
+ - [MCP Server](https://ggozad.github.io/haiku.rag/mcp/) - Model Context Protocol integration
183
+ - [A2A Agent](https://ggozad.github.io/haiku.rag/a2a/) - Agent-to-Agent protocol support
174
184
  - [Benchmarks](https://ggozad.github.io/haiku.rag/benchmarks/) - Performance Benchmarks
@@ -57,6 +57,7 @@ plugins:
57
57
  nav:
58
58
  - haiku.rag:
59
59
  - index.md
60
+ - Getting started: tutorial.md
60
61
  - Installation: installation.md
61
62
  - Configuration: configuration.md
62
63
  - CLI: cli.md
@@ -2,7 +2,7 @@
2
2
 
3
3
  name = "haiku.rag"
4
4
  description = "Agentic Retrieval Augmented Generation (RAG) with LanceDB"
5
- version = "0.12.0"
5
+ version = "0.13.0"
6
6
  authors = [{ name = "Yiorgis Gozadinos", email = "ggozadinos@gmail.com" }]
7
7
  license = { text = "MIT" }
8
8
  readme = { file = "README.md", content-type = "text/markdown" }
@@ -16,9 +16,8 @@ classifiers = [
16
16
  "Operating System :: Microsoft :: Windows :: Windows 11",
17
17
  "Operating System :: MacOS",
18
18
  "Operating System :: POSIX :: Linux",
19
- "Programming Language :: Python :: 3.10",
20
- "Programming Language :: Python :: 3.11",
21
19
  "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
22
21
  "Typing :: Typed",
23
22
  ]
24
23
 
@@ -27,10 +26,11 @@ dependencies = [
27
26
  "fastmcp>=2.12.4",
28
27
  "httpx>=0.28.1",
29
28
  "lancedb>=0.25.2",
30
- "pydantic>=2.12.1",
29
+ "pydantic>=2.12.2",
31
30
  "pydantic-ai>=1.0.18",
32
31
  "pydantic-graph>=1.0.18",
33
32
  "python-dotenv>=1.1.1",
33
+ "pyyaml>=6.0.1",
34
34
  "rich>=14.2.0",
35
35
  "tiktoken>=0.12.0",
36
36
  "typer>=0.19.2",
@@ -50,7 +50,7 @@ requires = ["hatchling"]
50
50
  build-backend = "hatchling.build"
51
51
 
52
52
  [tool.hatch.build]
53
- exclude = ["/docs", "/examples", "/tests", "/.github"]
53
+ exclude = ["/docs", "/examples", "/tests", "/docker", "/.github"]
54
54
 
55
55
  [tool.hatch.build.targets.wheel]
56
56
  packages = ["src/haiku"]
@@ -63,7 +63,7 @@ dev = [
63
63
  "mkdocs-material>=9.6.14",
64
64
  "pydantic-evals>=1.0.8",
65
65
  "pre-commit>=4.2.0",
66
- "pyright>=1.1.405",
66
+ "pyright>=1.1.406",
67
67
  "pytest>=8.4.2",
68
68
  "pytest-asyncio>=1.2.0",
69
69
  "pytest-cov>=7.0.0",
@@ -174,7 +174,7 @@ async def run_qa_benchmark(
174
174
 
175
175
  judge_model = OpenAIChatModel(
176
176
  model_name=QA_JUDGE_MODEL,
177
- provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"),
177
+ provider=OllamaProvider(base_url=f"{Config.providers.ollama.base_url}/v1"),
178
178
  )
179
179
 
180
180
  evaluation_dataset = EvalDataset[str, str, dict[str, str]](
@@ -41,7 +41,7 @@ class LLMJudge:
41
41
  # Create Ollama model
42
42
  ollama_model = OpenAIChatModel(
43
43
  model_name=model,
44
- provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"),
44
+ provider=OllamaProvider(base_url=f"{Config.providers.ollama.base_url}/v1"),
45
45
  )
46
46
 
47
47
  # Create Pydantic AI agent
@@ -57,12 +57,12 @@ def create_a2a_app(
57
57
  """
58
58
  base_storage = InMemoryStorage()
59
59
  storage = LRUMemoryStorage(
60
- storage=base_storage, max_contexts=Config.A2A_MAX_CONTEXTS
60
+ storage=base_storage, max_contexts=Config.a2a.max_contexts
61
61
  )
62
62
  broker = InMemoryBroker()
63
63
 
64
64
  # Create the agent with native search tool
65
- model = get_model(Config.QA_PROVIDER, Config.QA_MODEL)
65
+ model = get_model(Config.qa.provider, Config.qa.model)
66
66
  agent = Agent(
67
67
  model=model,
68
68
  deps_type=AgentDependencies,
@@ -120,7 +120,7 @@ def create_a2a_app(
120
120
  # Create FastA2A app with custom worker lifecycle
121
121
  @asynccontextmanager
122
122
  async def lifespan(app):
123
- logger.info(f"Started A2A server (max contexts: {Config.A2A_MAX_CONTEXTS})")
123
+ logger.info(f"Started A2A server (max contexts: {Config.a2a.max_contexts})")
124
124
  async with app.task_manager:
125
125
  async with worker.run():
126
126
  yield
@@ -7,9 +7,18 @@ from rich.console import Console
7
7
  from rich.markdown import Markdown
8
8
  from rich.prompt import Prompt
9
9
 
10
+ try:
11
+ from fasta2a.client import A2AClient as FastA2AClient
12
+ from fasta2a.schema import Message, TextPart
13
+ except ImportError as e:
14
+ raise ImportError(
15
+ "A2A support requires the 'a2a' extra. "
16
+ "Install with: uv pip install 'haiku.rag[a2a]'"
17
+ ) from e
18
+
10
19
 
11
20
  class A2AClient:
12
- """Simple A2A protocol client."""
21
+ """Interactive A2A protocol client."""
13
22
 
14
23
  def __init__(self, base_url: str = "http://localhost:8000"):
15
24
  """Initialize A2A client.
@@ -18,11 +27,12 @@ class A2AClient:
18
27
  base_url: Base URL of the A2A server
19
28
  """
20
29
  self.base_url = base_url.rstrip("/")
21
- self.client = httpx.AsyncClient(timeout=60.0)
30
+ http_client = httpx.AsyncClient(timeout=60.0)
31
+ self._client = FastA2AClient(base_url=base_url, http_client=http_client)
22
32
 
23
33
  async def close(self):
24
34
  """Close the HTTP client."""
25
- await self.client.aclose()
35
+ await self._client.http_client.aclose()
26
36
 
27
37
  async def get_agent_card(self) -> dict[str, Any]:
28
38
  """Fetch the agent card from the A2A server.
@@ -30,7 +40,9 @@ class A2AClient:
30
40
  Returns:
31
41
  Agent card dictionary with agent capabilities and metadata
32
42
  """
33
- response = await self.client.get(f"{self.base_url}/.well-known/agent-card.json")
43
+ response = await self._client.http_client.get(
44
+ f"{self.base_url}/.well-known/agent-card.json"
45
+ )
34
46
  response.raise_for_status()
35
47
  return response.json()
36
48
 
@@ -53,46 +65,38 @@ class A2AClient:
53
65
  if context_id is None:
54
66
  context_id = str(uuid.uuid4())
55
67
 
56
- message_id = str(uuid.uuid4())
57
-
58
- payload: dict[str, Any] = {
59
- "jsonrpc": "2.0",
60
- "method": "message/send",
61
- "params": {
62
- "contextId": context_id,
63
- "message": {
64
- "kind": "message",
65
- "role": "user",
66
- "messageId": message_id,
67
- "parts": [{"kind": "text", "text": text}],
68
- },
69
- },
70
- "id": 1,
71
- }
68
+ message = Message(
69
+ kind="message",
70
+ role="user",
71
+ message_id=str(uuid.uuid4()),
72
+ parts=[TextPart(kind="text", text=text)],
73
+ )
72
74
 
75
+ metadata: dict[str, Any] = {"contextId": context_id}
73
76
  if skill_id:
74
- payload["params"]["skillId"] = skill_id
77
+ metadata["skillId"] = skill_id
75
78
 
76
- response = await self.client.post(
77
- self.base_url,
78
- json=payload,
79
- headers={"Content-Type": "application/json"},
80
- )
81
- response.raise_for_status()
82
- initial_response = response.json()
79
+ response = await self._client.send_message(message, metadata=metadata)
83
80
 
84
- # Extract task ID from response
85
- result = initial_response.get("result", {})
86
- task_id = result.get("id")
81
+ if "error" in response:
82
+ return {"error": response["error"]}
87
83
 
88
- if not task_id:
89
- return initial_response
84
+ result = response.get("result")
85
+ if not result:
86
+ return {"result": result}
90
87
 
91
- # Poll for task completion
92
- return await self.wait_for_task(task_id)
88
+ # Result can be either Task or Message - check if it's a Task with an id
89
+ if result.get("kind") == "task":
90
+ task_id = result.get("id")
91
+ if task_id:
92
+ # Poll for task completion
93
+ return await self.wait_for_task(task_id)
94
+
95
+ # Return the message directly
96
+ return {"result": result}
93
97
 
94
98
  async def wait_for_task(
95
- self, task_id: str, max_wait: int = 60, poll_interval: float = 0.5
99
+ self, task_id: str, max_wait: int = 120, poll_interval: float = 0.5
96
100
  ) -> dict[str, Any]:
97
101
  """Poll for task completion.
98
102
 
@@ -109,27 +113,19 @@ class A2AClient:
109
113
  start_time = time.time()
110
114
 
111
115
  while time.time() - start_time < max_wait:
112
- payload = {
113
- "jsonrpc": "2.0",
114
- "method": "tasks/get",
115
- "params": {"id": task_id},
116
- "id": 2,
117
- }
118
-
119
- response = await self.client.post(
120
- self.base_url,
121
- json=payload,
122
- headers={"Content-Type": "application/json"},
123
- )
124
- response.raise_for_status()
125
- task = response.json()
126
-
127
- result = task.get("result", {})
128
- status = result.get("status", {})
129
- state = status.get("state")
116
+ task_response = await self._client.get_task(task_id)
117
+
118
+ if "error" in task_response:
119
+ return {"error": task_response["error"]}
120
+
121
+ task = task_response.get("result")
122
+ if not task:
123
+ raise Exception("No task in response")
124
+
125
+ state = task.get("status", {}).get("state")
130
126
 
131
127
  if state == "completed":
132
- return task
128
+ return {"result": task}
133
129
  elif state == "failed":
134
130
  raise Exception(f"Task failed: {task}")
135
131
 
@@ -191,6 +187,7 @@ def print_response(response: dict[str, Any], console: Console):
191
187
 
192
188
  # Print artifacts summary with details
193
189
  if artifacts:
190
+ console.rule("[dim]Artifacts generated[/dim]")
194
191
  summary_lines = []
195
192
 
196
193
  for artifact in artifacts:
@@ -160,13 +160,20 @@ class HaikuRAGApp:
160
160
  self, source: str, title: str | None = None, metadata: dict | None = None
161
161
  ):
162
162
  async with HaikuRAG(db_path=self.db_path) as self.client:
163
- doc = await self.client.create_document_from_source(
163
+ result = await self.client.create_document_from_source(
164
164
  source, title=title, metadata=metadata
165
165
  )
166
- self._rich_print_document(doc, truncate=True)
167
- self.console.print(
168
- f"[bold green]Document {doc.id} added successfully.[/bold green]"
169
- )
166
+ if isinstance(result, list):
167
+ for doc in result:
168
+ self._rich_print_document(doc, truncate=True)
169
+ self.console.print(
170
+ f"[bold green]{len(result)} documents added successfully.[/bold green]"
171
+ )
172
+ else:
173
+ self._rich_print_document(result, truncate=True)
174
+ self.console.print(
175
+ f"[bold green]Document {result.id} added successfully.[/bold green]"
176
+ )
170
177
 
171
178
  async def get_document(self, doc_id: str):
172
179
  async with HaikuRAG(db_path=self.db_path) as self.client:
@@ -224,8 +231,8 @@ class HaikuRAGApp:
224
231
  )
225
232
 
226
233
  start_node = DeepQAPlanNode(
227
- provider=Config.QA_PROVIDER,
228
- model=Config.QA_MODEL,
234
+ provider=Config.qa.provider,
235
+ model=Config.qa.model,
229
236
  )
230
237
 
231
238
  result = await graph.run(
@@ -271,8 +278,8 @@ class HaikuRAGApp:
271
278
  )
272
279
 
273
280
  start = PlanNode(
274
- provider=Config.RESEARCH_PROVIDER or Config.QA_PROVIDER,
275
- model=Config.RESEARCH_MODEL or Config.QA_MODEL,
281
+ provider=Config.research.provider or Config.qa.provider,
282
+ model=Config.research.model or Config.qa.model,
276
283
  )
277
284
  report = None
278
285
  async for event in stream_research_graph(graph, start, state, deps):
@@ -467,7 +474,9 @@ class HaikuRAGApp:
467
474
 
468
475
  # Start file monitor if enabled
469
476
  if enable_monitor:
470
- monitor = FileWatcher(paths=Config.MONITOR_DIRECTORIES, client=client)
477
+ monitor = FileWatcher(
478
+ paths=Config.storage.monitor_directories, client=client
479
+ )
471
480
  monitor_task = asyncio.create_task(monitor.observe())
472
481
  tasks.append(monitor_task)
473
482
 
@@ -22,7 +22,7 @@ class Chunker:
22
22
 
23
23
  def __init__(
24
24
  self,
25
- chunk_size: int = Config.CHUNK_SIZE,
25
+ chunk_size: int = Config.processing.chunk_size,
26
26
  ):
27
27
  self.chunk_size = chunk_size
28
28
  tokenizer = OpenAITokenizer(