haiku.rag 0.10.1__tar.gz → 0.11.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 (101) hide show
  1. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/PKG-INFO +34 -14
  2. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/README.md +33 -13
  3. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/agents.md +59 -10
  4. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/cli.md +41 -0
  5. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/index.md +2 -1
  6. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/installation.md +10 -0
  7. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/pyproject.toml +1 -1
  8. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/app.py +152 -28
  9. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/cli.py +72 -2
  10. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/migration.py +2 -2
  11. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/research/__init__.py +8 -0
  12. haiku_rag-0.11.0/src/haiku/rag/research/common.py +118 -0
  13. haiku_rag-0.11.0/src/haiku/rag/research/dependencies.py +215 -0
  14. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/research/graph.py +5 -3
  15. haiku_rag-0.11.0/src/haiku/rag/research/models.py +203 -0
  16. haiku_rag-0.11.0/src/haiku/rag/research/nodes/analysis.py +181 -0
  17. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/research/nodes/plan.py +16 -9
  18. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/research/nodes/search.py +14 -11
  19. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/research/nodes/synthesize.py +7 -3
  20. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/research/prompts.py +67 -28
  21. haiku_rag-0.11.0/src/haiku/rag/research/state.py +32 -0
  22. haiku_rag-0.11.0/src/haiku/rag/research/stream.py +177 -0
  23. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/__init__.py +1 -1
  24. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/models/__init__.py +1 -1
  25. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/utils.py +34 -0
  26. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_app.py +15 -8
  27. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_cli.py +93 -7
  28. haiku_rag-0.11.0/tests/test_info.py +79 -0
  29. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_research_graph.py +1 -2
  30. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_research_graph_integration.py +62 -13
  31. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/uv.lock +1 -1
  32. haiku_rag-0.10.1/src/haiku/rag/research/common.py +0 -53
  33. haiku_rag-0.10.1/src/haiku/rag/research/dependencies.py +0 -47
  34. haiku_rag-0.10.1/src/haiku/rag/research/models.py +0 -70
  35. haiku_rag-0.10.1/src/haiku/rag/research/nodes/evaluate.py +0 -80
  36. haiku_rag-0.10.1/src/haiku/rag/research/state.py +0 -25
  37. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/.github/FUNDING.yml +0 -0
  38. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/.github/workflows/build-docs.yml +0 -0
  39. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/.github/workflows/build-publish.yml +0 -0
  40. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/.gitignore +0 -0
  41. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/.pre-commit-config.yaml +0 -0
  42. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/.python-version +0 -0
  43. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/LICENSE +0 -0
  44. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/benchmarks.md +0 -0
  45. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/configuration.md +0 -0
  46. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/mcp.md +0 -0
  47. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/python.md +0 -0
  48. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/docs/server.md +0 -0
  49. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/mkdocs.yml +0 -0
  50. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/__init__.py +0 -0
  51. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/chunker.py +0 -0
  52. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/client.py +0 -0
  53. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/config.py +0 -0
  54. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/embeddings/__init__.py +0 -0
  55. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/embeddings/base.py +0 -0
  56. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/embeddings/ollama.py +0 -0
  57. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/embeddings/openai.py +0 -0
  58. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/embeddings/vllm.py +0 -0
  59. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/embeddings/voyageai.py +0 -0
  60. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/logging.py +0 -0
  61. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/mcp.py +0 -0
  62. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/monitor.py +0 -0
  63. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/qa/__init__.py +0 -0
  64. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/qa/agent.py +0 -0
  65. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/qa/prompts.py +0 -0
  66. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/reader.py +0 -0
  67. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/reranking/__init__.py +0 -0
  68. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/reranking/base.py +0 -0
  69. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/reranking/cohere.py +0 -0
  70. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/reranking/mxbai.py +0 -0
  71. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/reranking/vllm.py +0 -0
  72. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/engine.py +0 -0
  73. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/models/chunk.py +0 -0
  74. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/models/document.py +0 -0
  75. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/repositories/__init__.py +0 -0
  76. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/repositories/chunk.py +0 -0
  77. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/repositories/document.py +0 -0
  78. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/repositories/settings.py +0 -0
  79. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/upgrades/__init__.py +0 -0
  80. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/upgrades/v0_10_1.py +0 -0
  81. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/src/haiku/rag/store/upgrades/v0_9_3.py +0 -0
  82. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/__init__.py +0 -0
  83. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/conftest.py +0 -0
  84. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/generate_benchmark_db.py +0 -0
  85. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/llm_judge.py +0 -0
  86. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_chunk.py +0 -0
  87. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_chunker.py +0 -0
  88. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_client.py +0 -0
  89. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_document.py +0 -0
  90. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_embedder.py +0 -0
  91. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_lancedb_connection.py +0 -0
  92. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_monitor.py +0 -0
  93. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_preprocessor.py +0 -0
  94. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_qa.py +0 -0
  95. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_reader.py +0 -0
  96. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_rebuild.py +0 -0
  97. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_reranker.py +0 -0
  98. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_search.py +0 -0
  99. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_settings.py +0 -0
  100. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_utils.py +0 -0
  101. {haiku_rag-0.10.1 → haiku_rag-0.11.0}/tests/test_versioning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiku.rag
3
- Version: 0.10.1
3
+ Version: 0.11.0
4
4
  Summary: Agentic Retrieval Augmented Generation (RAG) with LanceDB
5
5
  Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
6
6
  License: MIT
@@ -66,7 +66,8 @@ uv pip install haiku.rag
66
66
 
67
67
  # Add documents
68
68
  haiku-rag add "Your content here"
69
- haiku-rag add-src document.pdf
69
+ haiku-rag add "Your content here" --meta author=alice --meta topic=notes
70
+ haiku-rag add-src document.pdf --meta source=manual
70
71
 
71
72
  # Search
72
73
  haiku-rag search "query"
@@ -101,11 +102,12 @@ haiku-rag serve
101
102
  ```python
102
103
  from haiku.rag.client import HaikuRAG
103
104
  from haiku.rag.research import (
105
+ PlanNode,
104
106
  ResearchContext,
105
107
  ResearchDeps,
106
108
  ResearchState,
107
109
  build_research_graph,
108
- PlanNode,
110
+ stream_research_graph,
109
111
  )
110
112
 
111
113
  async with HaikuRAG("database.lancedb") as client:
@@ -127,22 +129,40 @@ async with HaikuRAG("database.lancedb") as client:
127
129
 
128
130
  # Multi‑agent research pipeline (Plan → Search → Evaluate → Synthesize)
129
131
  graph = build_research_graph()
132
+ question = (
133
+ "What are the main drivers and trends of global temperature "
134
+ "anomalies since 1990?"
135
+ )
130
136
  state = ResearchState(
131
- question=(
132
- "What are the main drivers and trends of global temperature "
133
- "anomalies since 1990?"
134
- ),
135
- context=ResearchContext(original_question="…"),
137
+ context=ResearchContext(original_question=question),
136
138
  max_iterations=2,
137
139
  confidence_threshold=0.8,
138
- max_concurrency=3,
140
+ max_concurrency=2,
139
141
  )
140
142
  deps = ResearchDeps(client=client)
141
- start = PlanNode(provider=None, model=None)
142
- result = await graph.run(start, state=state, deps=deps)
143
- report = result.output
144
- print(report.title)
145
- print(report.executive_summary)
143
+
144
+ # Blocking run (final result only)
145
+ result = await graph.run(
146
+ PlanNode(provider="openai", model="gpt-4o-mini"),
147
+ state=state,
148
+ deps=deps,
149
+ )
150
+ print(result.output.title)
151
+
152
+ # Streaming progress (log/report/error events)
153
+ async for event in stream_research_graph(
154
+ graph,
155
+ PlanNode(provider="openai", model="gpt-4o-mini"),
156
+ state,
157
+ deps,
158
+ ):
159
+ if event.type == "log":
160
+ iteration = event.state.iterations if event.state else state.iterations
161
+ print(f"[{iteration}] {event.message}")
162
+ elif event.type == "report":
163
+ print("\nResearch complete!\n")
164
+ print(event.report.title)
165
+ print(event.report.executive_summary)
146
166
  ```
147
167
 
148
168
  ## MCP Server
@@ -28,7 +28,8 @@ uv pip install haiku.rag
28
28
 
29
29
  # Add documents
30
30
  haiku-rag add "Your content here"
31
- haiku-rag add-src document.pdf
31
+ haiku-rag add "Your content here" --meta author=alice --meta topic=notes
32
+ haiku-rag add-src document.pdf --meta source=manual
32
33
 
33
34
  # Search
34
35
  haiku-rag search "query"
@@ -63,11 +64,12 @@ haiku-rag serve
63
64
  ```python
64
65
  from haiku.rag.client import HaikuRAG
65
66
  from haiku.rag.research import (
67
+ PlanNode,
66
68
  ResearchContext,
67
69
  ResearchDeps,
68
70
  ResearchState,
69
71
  build_research_graph,
70
- PlanNode,
72
+ stream_research_graph,
71
73
  )
72
74
 
73
75
  async with HaikuRAG("database.lancedb") as client:
@@ -89,22 +91,40 @@ async with HaikuRAG("database.lancedb") as client:
89
91
 
90
92
  # Multi‑agent research pipeline (Plan → Search → Evaluate → Synthesize)
91
93
  graph = build_research_graph()
94
+ question = (
95
+ "What are the main drivers and trends of global temperature "
96
+ "anomalies since 1990?"
97
+ )
92
98
  state = ResearchState(
93
- question=(
94
- "What are the main drivers and trends of global temperature "
95
- "anomalies since 1990?"
96
- ),
97
- context=ResearchContext(original_question="…"),
99
+ context=ResearchContext(original_question=question),
98
100
  max_iterations=2,
99
101
  confidence_threshold=0.8,
100
- max_concurrency=3,
102
+ max_concurrency=2,
101
103
  )
102
104
  deps = ResearchDeps(client=client)
103
- start = PlanNode(provider=None, model=None)
104
- result = await graph.run(start, state=state, deps=deps)
105
- report = result.output
106
- print(report.title)
107
- print(report.executive_summary)
105
+
106
+ # Blocking run (final result only)
107
+ result = await graph.run(
108
+ PlanNode(provider="openai", model="gpt-4o-mini"),
109
+ state=state,
110
+ deps=deps,
111
+ )
112
+ print(result.output.title)
113
+
114
+ # Streaming progress (log/report/error events)
115
+ async for event in stream_research_graph(
116
+ graph,
117
+ PlanNode(provider="openai", model="gpt-4o-mini"),
118
+ state,
119
+ deps,
120
+ ):
121
+ if event.type == "log":
122
+ iteration = event.state.iterations if event.state else state.iterations
123
+ print(f"[{iteration}] {event.message}")
124
+ elif event.type == "report":
125
+ print("\nResearch complete!\n")
126
+ print(event.report.title)
127
+ print(event.report.executive_summary)
108
128
  ```
109
129
 
110
130
  ## MCP Server
@@ -47,9 +47,10 @@ title: Research graph
47
47
  ---
48
48
  stateDiagram-v2
49
49
  PlanNode --> SearchDispatchNode
50
- SearchDispatchNode --> EvaluateNode
51
- EvaluateNode --> SearchDispatchNode
52
- EvaluateNode --> SynthesizeNode
50
+ SearchDispatchNode --> AnalyzeInsightsNode
51
+ AnalyzeInsightsNode --> DecisionNode
52
+ DecisionNode --> SearchDispatchNode
53
+ DecisionNode --> SynthesizeNode
53
54
  SynthesizeNode --> [*]
54
55
  ```
55
56
 
@@ -57,12 +58,15 @@ Key nodes:
57
58
 
58
59
  - Plan: builds up to 3 standalone sub‑questions (uses an internal presearch tool)
59
60
  - Search (batched): answers sub‑questions using the KB with minimal, verbatim context
60
- - Evaluate: extracts insights, proposes new questions, and checks sufficiency/confidence
61
+ - Analyze: aggregates fresh insights, updates gaps, and suggests new sub-questions
62
+ - Decision: checks sufficiency/confidence thresholds and chooses whether to iterate
61
63
  - Synthesize: generates a final structured report
62
64
 
63
65
  Primary models:
64
66
 
65
67
  - `SearchAnswer` — one per sub‑question (query, answer, context, sources)
68
+ - `InsightRecord` / `GapRecord` — structured tracking of findings and open issues
69
+ - `InsightAnalysis` — output of the analysis stage (insights, gaps, commentary)
66
70
  - `EvaluationResult` — insights, new questions, sufficiency, confidence
67
71
  - `ResearchReport` — final report (title, executive summary, findings, conclusions, …)
68
72
 
@@ -76,30 +80,75 @@ haiku-rag research "How does haiku.rag organize and query documents?" \
76
80
  --verbose
77
81
  ```
78
82
 
79
- Python usage:
83
+ Python usage (blocking result):
80
84
 
81
85
  ```python
82
86
  from haiku.rag.client import HaikuRAG
83
87
  from haiku.rag.research import (
88
+ PlanNode,
84
89
  ResearchContext,
85
90
  ResearchDeps,
86
91
  ResearchState,
87
92
  build_research_graph,
88
- PlanNode,
89
93
  )
90
94
 
91
95
  async with HaikuRAG(path_to_db) as client:
92
96
  graph = build_research_graph()
97
+ question = "What are the main drivers and trends of global temperature anomalies since 1990?"
93
98
  state = ResearchState(
94
- question="What are the main drivers and trends of global temperature anomalies since 1990?",
95
- context=ResearchContext(original_question=... ),
99
+ context=ResearchContext(original_question=question),
96
100
  max_iterations=2,
97
101
  confidence_threshold=0.8,
98
- max_concurrency=3,
102
+ max_concurrency=2,
99
103
  )
100
104
  deps = ResearchDeps(client=client)
101
- result = await graph.run(PlanNode(provider=None, model=None), state=state, deps=deps)
105
+
106
+ result = await graph.run(
107
+ PlanNode(provider="openai", model="gpt-4o-mini"),
108
+ state=state,
109
+ deps=deps,
110
+ )
111
+
102
112
  report = result.output
103
113
  print(report.title)
104
114
  print(report.executive_summary)
105
115
  ```
116
+
117
+ Python usage (streamed events):
118
+
119
+ ```python
120
+ from haiku.rag.client import HaikuRAG
121
+ from haiku.rag.research import (
122
+ PlanNode,
123
+ ResearchContext,
124
+ ResearchDeps,
125
+ ResearchState,
126
+ build_research_graph,
127
+ stream_research_graph,
128
+ )
129
+
130
+ async with HaikuRAG(path_to_db) as client:
131
+ graph = build_research_graph()
132
+ question = "What are the main drivers and trends of global temperature anomalies since 1990?"
133
+ state = ResearchState(
134
+ context=ResearchContext(original_question=question),
135
+ max_iterations=2,
136
+ confidence_threshold=0.8,
137
+ max_concurrency=2,
138
+ )
139
+ deps = ResearchDeps(client=client)
140
+
141
+ async for event in stream_research_graph(
142
+ graph,
143
+ PlanNode(provider="openai", model="gpt-4o-mini"),
144
+ state,
145
+ deps,
146
+ ):
147
+ if event.type == "log":
148
+ iteration = event.state.iterations if event.state else state.iterations
149
+ print(f"[{iteration}] {event.message}")
150
+ elif event.type == "report":
151
+ print("\nResearch complete!\n")
152
+ print(event.report.title)
153
+ print(event.report.executive_summary)
154
+ ```
@@ -27,6 +27,9 @@ haiku-rag list
27
27
  From text:
28
28
  ```bash
29
29
  haiku-rag add "Your document content here"
30
+
31
+ # Attach metadata (repeat --meta for multiple entries)
32
+ haiku-rag add "Your document content here" --meta author=alice --meta topic=notes
30
33
  ```
31
34
 
32
35
  From file or URL:
@@ -36,6 +39,10 @@ haiku-rag add-src https://example.com/article.html
36
39
 
37
40
  # Optionally set a human‑readable title stored in the DB schema
38
41
  haiku-rag add-src /mnt/data/doc1.pdf --title "Q3 Financial Report"
42
+
43
+ # Optionally attach metadata (repeat --meta). Values use JSON parsing if possible:
44
+ # numbers, booleans, null, arrays/objects; otherwise kept as strings.
45
+ haiku-rag add-src /mnt/data/doc1.pdf --meta source=manual --meta page_count=12 --meta published=true
39
46
  ```
40
47
 
41
48
  !!! note
@@ -106,6 +113,8 @@ Flags:
106
113
  - `--max-concurrency`: number of sub-questions searched in parallel each iteration (default: 3)
107
114
  - `--verbose`: show planning, searching previews, evaluation summary, and stop reason
108
115
 
116
+ When `--verbose` is set the CLI also consumes the internal research stream, printing every `log` event as agents progress through planning, search, evaluation, and synthesis. If you build your own integration, call `stream_research_graph` to access the same `log`, `report`, and `error` events and render them however you like while the graph is running.
117
+
109
118
  ## Server
110
119
 
111
120
  Start the MCP server:
@@ -126,6 +135,26 @@ haiku-rag settings
126
135
 
127
136
  ## Maintenance
128
137
 
138
+ ### Info (Read-only)
139
+
140
+ Display database metadata without upgrading or modifying it:
141
+
142
+ ```bash
143
+ haiku-rag info [--db /path/to/your.lancedb]
144
+ ```
145
+
146
+ Shows:
147
+ - path to the database
148
+ - stored haiku.rag version (from settings)
149
+ - embeddings provider/model and vector dimension
150
+ - number of documents
151
+ - table versions per table (documents, chunks)
152
+
153
+ At the end, a separate “Versions” section lists runtime package versions:
154
+ - haiku.rag
155
+ - lancedb
156
+ - docling
157
+
129
158
  ### Vacuum (Optimize and Cleanup)
130
159
 
131
160
  Reduce disk usage by optimizing and pruning old table versions across all tables:
@@ -143,6 +172,18 @@ when want to switch embeddings provider or model:
143
172
  haiku-rag rebuild
144
173
  ```
145
174
 
175
+ ### Download Models
176
+
177
+ Download required runtime models:
178
+
179
+ ```bash
180
+ haiku-rag download-models
181
+ ```
182
+
183
+ This command:
184
+ - Downloads Docling OCR/conversion models (no-op if already present).
185
+ - Pulls Ollama models referenced in your configuration (embeddings, QA, research, rerank).
186
+
146
187
  ## Migration
147
188
 
148
189
  ### Migrate from SQLite to LanceDB
@@ -43,7 +43,8 @@ async with HaikuRAG("database.lancedb") as client:
43
43
  Or use the CLI:
44
44
  ```bash
45
45
  haiku-rag add "Your document content"
46
- haiku-rag add-src /path/to/document.pdf --title "Q3 Financial Report"
46
+ haiku-rag add "Your document content" --meta author=alice
47
+ haiku-rag add-src /path/to/document.pdf --title "Q3 Financial Report" --meta source=manual
47
48
  haiku-rag search "query"
48
49
  haiku-rag ask "Who is the author of haiku.rag?"
49
50
  haiku-rag migrate old_database.sqlite # Migrate from SQLite
@@ -72,3 +72,13 @@ VLLM_RERANK_BASE_URL="http://localhost:8001"
72
72
  - Python 3.10+
73
73
  - Ollama (for default embeddings)
74
74
  - vLLM server (for vLLM provider)
75
+
76
+ ## Pre-download Models (Optional)
77
+
78
+ You can prefetch all required runtime models before first use:
79
+
80
+ ```bash
81
+ haiku-rag download-models
82
+ ```
83
+
84
+ This will download Docling models and pull any Ollama models referenced by your current configuration.
@@ -2,7 +2,7 @@
2
2
 
3
3
  name = "haiku.rag"
4
4
  description = "Agentic Retrieval Augmented Generation (RAG) with LanceDB"
5
- version = "0.10.1"
5
+ version = "0.11.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" }
@@ -1,4 +1,6 @@
1
1
  import asyncio
2
+ import json
3
+ from importlib.metadata import version as pkg_version
2
4
  from pathlib import Path
3
5
 
4
6
  from rich.console import Console
@@ -16,6 +18,7 @@ from haiku.rag.research.graph import (
16
18
  ResearchState,
17
19
  build_research_graph,
18
20
  )
21
+ from haiku.rag.research.stream import stream_research_graph
19
22
  from haiku.rag.store.models.chunk import Chunk
20
23
  from haiku.rag.store.models.document import Document
21
24
 
@@ -25,26 +28,141 @@ class HaikuRAGApp:
25
28
  self.db_path = db_path
26
29
  self.console = Console()
27
30
 
31
+ async def info(self):
32
+ """Display read-only information about the database without modifying it."""
33
+
34
+ import lancedb
35
+
36
+ # Basic: show path
37
+ self.console.print("[bold]haiku.rag database info[/bold]")
38
+ self.console.print(
39
+ f" [repr.attrib_name]path[/repr.attrib_name]: {self.db_path}"
40
+ )
41
+
42
+ if not self.db_path.exists():
43
+ self.console.print("[red]Database path does not exist.[/red]")
44
+ return
45
+
46
+ # Connect without going through Store to avoid upgrades/validation writes
47
+ try:
48
+ db = lancedb.connect(self.db_path)
49
+ table_names = set(db.table_names())
50
+ except Exception as e:
51
+ self.console.print(f"[red]Failed to open database: {e}[/red]")
52
+ return
53
+
54
+ try:
55
+ ldb_version = pkg_version("lancedb")
56
+ except Exception:
57
+ ldb_version = "unknown"
58
+ try:
59
+ hr_version = pkg_version("haiku.rag")
60
+ except Exception:
61
+ hr_version = "unknown"
62
+ try:
63
+ docling_version = pkg_version("docling")
64
+ except Exception:
65
+ docling_version = "unknown"
66
+
67
+ # Read settings (if present) to find stored haiku.rag version and embedding config
68
+ stored_version = "unknown"
69
+ embed_provider: str | None = None
70
+ embed_model: str | None = None
71
+ vector_dim: int | None = None
72
+
73
+ if "settings" in table_names:
74
+ settings_tbl = db.open_table("settings")
75
+ arrow = settings_tbl.search().where("id = 'settings'").limit(1).to_arrow()
76
+ rows = arrow.to_pylist() if arrow is not None else []
77
+ if rows:
78
+ raw = rows[0].get("settings") or "{}"
79
+ data = json.loads(raw) if isinstance(raw, str) else (raw or {})
80
+ stored_version = str(data.get("version", stored_version))
81
+ embed_provider = data.get("EMBEDDINGS_PROVIDER")
82
+ embed_model = data.get("EMBEDDINGS_MODEL")
83
+ vector_dim = (
84
+ int(data.get("EMBEDDINGS_VECTOR_DIM")) # pyright: ignore[reportArgumentType]
85
+ if data.get("EMBEDDINGS_VECTOR_DIM") is not None
86
+ else None
87
+ )
88
+
89
+ num_docs = 0
90
+ if "documents" in table_names:
91
+ docs_tbl = db.open_table("documents")
92
+ num_docs = int(docs_tbl.count_rows()) # type: ignore[attr-defined]
93
+
94
+ # Table versions per table (direct API)
95
+ doc_versions = (
96
+ len(list(db.open_table("documents").list_versions()))
97
+ if "documents" in table_names
98
+ else 0
99
+ )
100
+ chunk_versions = (
101
+ len(list(db.open_table("chunks").list_versions()))
102
+ if "chunks" in table_names
103
+ else 0
104
+ )
105
+
106
+ self.console.print(
107
+ f" [repr.attrib_name]haiku.rag version (db)[/repr.attrib_name]: {stored_version}"
108
+ )
109
+ if embed_provider or embed_model or vector_dim:
110
+ provider_part = embed_provider or "unknown"
111
+ model_part = embed_model or "unknown"
112
+ dim_part = f"{vector_dim}" if vector_dim is not None else "unknown"
113
+ self.console.print(
114
+ " [repr.attrib_name]embeddings[/repr.attrib_name]: "
115
+ f"{provider_part}/{model_part} (dim: {dim_part})"
116
+ )
117
+ else:
118
+ self.console.print(
119
+ " [repr.attrib_name]embeddings[/repr.attrib_name]: unknown"
120
+ )
121
+ self.console.print(
122
+ f" [repr.attrib_name]documents[/repr.attrib_name]: {num_docs}"
123
+ )
124
+ self.console.print(
125
+ f" [repr.attrib_name]versions (documents)[/repr.attrib_name]: {doc_versions}"
126
+ )
127
+ self.console.print(
128
+ f" [repr.attrib_name]versions (chunks)[/repr.attrib_name]: {chunk_versions}"
129
+ )
130
+ self.console.rule()
131
+ self.console.print("[bold]Versions[/bold]")
132
+ self.console.print(
133
+ f" [repr.attrib_name]haiku.rag[/repr.attrib_name]: {hr_version}"
134
+ )
135
+ self.console.print(
136
+ f" [repr.attrib_name]lancedb[/repr.attrib_name]: {ldb_version}"
137
+ )
138
+ self.console.print(
139
+ f" [repr.attrib_name]docling[/repr.attrib_name]: {docling_version}"
140
+ )
141
+
28
142
  async def list_documents(self):
29
143
  async with HaikuRAG(db_path=self.db_path) as self.client:
30
144
  documents = await self.client.list_documents()
31
145
  for doc in documents:
32
146
  self._rich_print_document(doc, truncate=True)
33
147
 
34
- async def add_document_from_text(self, text: str):
148
+ async def add_document_from_text(self, text: str, metadata: dict | None = None):
35
149
  async with HaikuRAG(db_path=self.db_path) as self.client:
36
- doc = await self.client.create_document(text)
150
+ doc = await self.client.create_document(text, metadata=metadata)
37
151
  self._rich_print_document(doc, truncate=True)
38
152
  self.console.print(
39
- f"[b]Document with id [cyan]{doc.id}[/cyan] added successfully.[/b]"
153
+ f"[bold green]Document {doc.id} added successfully.[/bold green]"
40
154
  )
41
155
 
42
- async def add_document_from_source(self, source: str, title: str | None = None):
156
+ async def add_document_from_source(
157
+ self, source: str, title: str | None = None, metadata: dict | None = None
158
+ ):
43
159
  async with HaikuRAG(db_path=self.db_path) as self.client:
44
- doc = await self.client.create_document_from_source(source, title=title)
160
+ doc = await self.client.create_document_from_source(
161
+ source, title=title, metadata=metadata
162
+ )
45
163
  self._rich_print_document(doc, truncate=True)
46
164
  self.console.print(
47
- f"[b]Document with id [cyan]{doc.id}[/cyan] added successfully.[/b]"
165
+ f"[bold green]Document {doc.id} added successfully.[/bold green]"
48
166
  )
49
167
 
50
168
  async def get_document(self, doc_id: str):
@@ -59,7 +177,9 @@ class HaikuRAGApp:
59
177
  async with HaikuRAG(db_path=self.db_path) as self.client:
60
178
  deleted = await self.client.delete_document(doc_id)
61
179
  if deleted:
62
- self.console.print(f"[b]Document {doc_id} deleted successfully.[/b]")
180
+ self.console.print(
181
+ f"[bold green]Document {doc_id} deleted successfully.[/bold green]"
182
+ )
63
183
  else:
64
184
  self.console.print(
65
185
  f"[yellow]Document with id {doc_id} not found.[/yellow]"
@@ -69,7 +189,7 @@ class HaikuRAGApp:
69
189
  async with HaikuRAG(db_path=self.db_path) as self.client:
70
190
  results = await self.client.search(query, limit=limit)
71
191
  if not results:
72
- self.console.print("[red]No results found.[/red]")
192
+ self.console.print("[yellow]No results found.[/yellow]")
73
193
  return
74
194
  for chunk, score in results:
75
195
  self._rich_print_search_result(chunk, score)
@@ -102,9 +222,9 @@ class HaikuRAGApp:
102
222
  self.console.print()
103
223
 
104
224
  graph = build_research_graph()
225
+ context = ResearchContext(original_question=question)
105
226
  state = ResearchState(
106
- question=question,
107
- context=ResearchContext(original_question=question),
227
+ context=context,
108
228
  max_iterations=max_iterations,
109
229
  confidence_threshold=confidence_threshold,
110
230
  max_concurrency=max_concurrency,
@@ -117,22 +237,20 @@ class HaikuRAGApp:
117
237
  provider=Config.RESEARCH_PROVIDER or Config.QA_PROVIDER,
118
238
  model=Config.RESEARCH_MODEL or Config.QA_MODEL,
119
239
  )
120
- # Prefer graph.run; fall back to iter if unavailable
121
240
  report = None
122
- try:
123
- result = await graph.run(start, state=state, deps=deps)
124
- report = result.output
125
- except Exception:
126
- from pydantic_graph import End
127
-
128
- async with graph.iter(start, state=state, deps=deps) as run:
129
- node = run.next_node
130
- while not isinstance(node, End):
131
- node = await run.next(node)
132
- if run.result:
133
- report = run.result.output
241
+ async for event in stream_research_graph(graph, start, state, deps):
242
+ if event.type == "report":
243
+ report = event.report
244
+ break
245
+ if event.type == "error":
246
+ self.console.print(
247
+ f"[red]Error during research: {event.message}[/red]"
248
+ )
249
+ return
250
+
134
251
  if report is None:
135
- raise RuntimeError("Graph did not produce a report")
252
+ self.console.print("[red]Research did not produce a report.[/red]")
253
+ return
136
254
 
137
255
  # Display the report
138
256
  self.console.print("[bold green]Research Report[/bold green]")
@@ -202,14 +320,16 @@ class HaikuRAGApp:
202
320
  return
203
321
 
204
322
  self.console.print(
205
- f"[b]Rebuilding database with {total_docs} documents...[/b]"
323
+ f"[bold cyan]Rebuilding database with {total_docs} documents...[/bold cyan]"
206
324
  )
207
325
  with Progress() as progress:
208
326
  task = progress.add_task("Rebuilding...", total=total_docs)
209
327
  async for _ in client.rebuild_database():
210
328
  progress.update(task, advance=1)
211
329
 
212
- self.console.print("[b]Database rebuild completed successfully.[/b]")
330
+ self.console.print(
331
+ "[bold green]Database rebuild completed successfully.[/bold green]"
332
+ )
213
333
  except Exception as e:
214
334
  self.console.print(f"[red]Error rebuilding database: {e}[/red]")
215
335
 
@@ -218,7 +338,9 @@ class HaikuRAGApp:
218
338
  try:
219
339
  async with HaikuRAG(db_path=self.db_path, skip_validation=True) as client:
220
340
  await client.vacuum()
221
- self.console.print("[b]Vacuum completed successfully.[/b]")
341
+ self.console.print(
342
+ "[bold green]Vacuum completed successfully.[/bold green]"
343
+ )
222
344
  except Exception as e:
223
345
  self.console.print(f"[red]Error during vacuum: {e}[/red]")
224
346
 
@@ -240,7 +362,9 @@ class HaikuRAGApp:
240
362
  else:
241
363
  display_value = field_value
242
364
 
243
- self.console.print(f" [cyan]{field_name}[/cyan]: {display_value}")
365
+ self.console.print(
366
+ f" [repr.attrib_name]{field_name}[/repr.attrib_name]: {display_value}"
367
+ )
244
368
 
245
369
  def _rich_print_document(self, doc: Document, truncate: bool = False):
246
370
  """Format a document for display."""