freeaiagent 0.1.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.
@@ -0,0 +1,374 @@
1
+ Metadata-Version: 2.4
2
+ Name: freeaiagent
3
+ Version: 0.1.0
4
+ Summary: Local AI agent service — HTTP endpoints, persistent context, multi-model LLM backend
5
+ Author-email: Subham <shubham.divakar@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: llm,agent,ollama,groq,local,ai,fastapi
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: fastapi>=0.110.0
19
+ Requires-Dist: uvicorn[standard]>=0.29.0
20
+ Requires-Dist: typer>=0.12.0
21
+ Requires-Dist: httpx2
22
+ Provides-Extra: groq
23
+ Requires-Dist: groq>=0.9.0; extra == "groq"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
27
+ Requires-Dist: httpx2; extra == "dev"
28
+ Provides-Extra: all
29
+ Requires-Dist: groq>=0.9.0; extra == "all"
30
+
31
+ # freeaiagent
32
+
33
+ A local AI agent service you `pip install` once and call from anywhere.
34
+
35
+ Runs as a persistent HTTP server on `localhost:7731`. Stores conversation history in SQLite. Any app — script, CLI tool, personal project — can delegate tasks to it with a single HTTP call, no LLM code required on the caller's side.
36
+
37
+ Built on free LLM backends: **Ollama** (local, no API key) and **Groq** (free-tier cloud).
38
+
39
+ ---
40
+
41
+ ## Why
42
+
43
+ Embedding LLM logic directly into every app that needs it means duplicating prompt management, context handling, model configuration, and install detection across projects. When something changes — a new model, a different provider, a context bug — you fix it in every app separately.
44
+
45
+ `freeaiagent` is the single place that owns all of that. Your apps just call an endpoint.
46
+
47
+ ---
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install freeaiagent # Ollama only
53
+ pip install "freeaiagent[groq]" # + Groq support
54
+ ```
55
+
56
+ Requires Python 3.10+.
57
+
58
+ ---
59
+
60
+ ## Quick start
61
+
62
+ **1. Start the server**
63
+ ```bash
64
+ freeaiagent start
65
+ # Running at http://localhost:7731
66
+ # API docs at http://localhost:7731/docs
67
+ ```
68
+
69
+ **2. Chat with it**
70
+ ```bash
71
+ freeaiagent chat
72
+ # You: what is the capital of France?
73
+ # Agent [llama3.2:3b]: Paris.
74
+ ```
75
+
76
+ **3. Call it from any app**
77
+ ```python
78
+ import urllib.request, json
79
+
80
+ req = urllib.request.Request(
81
+ "http://localhost:7731/chat",
82
+ data=json.dumps({"message": "summarize this for me: ..."}).encode(),
83
+ headers={"Content-Type": "application/json"},
84
+ )
85
+ response = json.loads(urllib.request.urlopen(req).read())["response"]
86
+ ```
87
+
88
+ No pip dependencies needed in the calling app. Pure stdlib.
89
+
90
+ ---
91
+
92
+ ## CLI
93
+
94
+ ```bash
95
+ freeaiagent start # start server (default port 7731)
96
+ freeaiagent start --port 8080 # custom port
97
+ freeaiagent start --reload # dev mode: auto-reload on code change
98
+
99
+ freeaiagent chat # interactive chat (context preserved)
100
+ freeaiagent chat "quick one-liner" # single message, then exit
101
+
102
+ freeaiagent task "explain this" \
103
+ --input "$(cat file.py)" # one-shot task, no context read/written
104
+ freeaiagent task "translate to French" \
105
+ --input "Hello world" \
106
+ --model mistral:7b # override model for this task
107
+
108
+ freeaiagent status # health check + active backend/model
109
+ freeaiagent models # list models on active backend
110
+
111
+ freeaiagent context show # print conversation history
112
+ freeaiagent context clear # wipe conversation history
113
+
114
+ freeaiagent config show # print current config
115
+ freeaiagent config set default_model mistral:7b
116
+ freeaiagent config set default_backend groq
117
+ freeaiagent config set backends.groq.api_key gsk_...
118
+ ```
119
+
120
+ ---
121
+
122
+ ## HTTP API
123
+
124
+ All endpoints accept and return JSON.
125
+
126
+ ### `POST /chat`
127
+ Send a message. Conversation history is read and updated automatically.
128
+
129
+ ```bash
130
+ curl -X POST http://localhost:7731/chat \
131
+ -H "Content-Type: application/json" \
132
+ -d '{"message": "what did I just ask you?"}'
133
+ ```
134
+
135
+ ```json
136
+ {
137
+ "response": "You asked me what you just asked me.",
138
+ "model": "llama3.2:3b",
139
+ "context_length": 4
140
+ }
141
+ ```
142
+
143
+ | Field | Type | Description |
144
+ |---|---|---|
145
+ | `message` | string | required |
146
+ | `system` | string | optional system prompt override |
147
+
148
+ ---
149
+
150
+ ### `POST /task`
151
+ One-shot task. No context is read or written — clean slate every time.
152
+
153
+ ```bash
154
+ curl -X POST http://localhost:7731/task \
155
+ -H "Content-Type: application/json" \
156
+ -d '{"task": "list all TODO comments", "input": "..."}'
157
+ ```
158
+
159
+ ```json
160
+ {
161
+ "result": "Line 42: TODO fix this\nLine 87: TODO add tests",
162
+ "model": "llama3.2:3b"
163
+ }
164
+ ```
165
+
166
+ | Field | Type | Description |
167
+ |---|---|---|
168
+ | `task` | string | required — the instruction |
169
+ | `input` | string | optional — content to work on |
170
+ | `model` | string | optional — override model for this call |
171
+ | `system` | string | optional — override system prompt |
172
+
173
+ ---
174
+
175
+ ### `GET /context`
176
+ Returns the full conversation history.
177
+
178
+ ```json
179
+ {
180
+ "messages": [
181
+ {"role": "user", "content": "hello", "timestamp": "2026-06-21T10:00:00+00:00"},
182
+ {"role": "assistant", "content": "hi there", "timestamp": "2026-06-21T10:00:01+00:00"}
183
+ ],
184
+ "total": 2
185
+ }
186
+ ```
187
+
188
+ ### `DELETE /context`
189
+ Clears all conversation history.
190
+
191
+ ```json
192
+ {"cleared": 4, "message": "Cleared 4 messages."}
193
+ ```
194
+
195
+ ### `GET /health`
196
+ ```json
197
+ {"status": "ok", "active_backend": "ollama", "default_model": "llama3.2:3b"}
198
+ ```
199
+ Returns `"status": "degraded"` with an `"error"` field if no backend is reachable.
200
+
201
+ ### `GET /models`
202
+ ```json
203
+ {"models": ["llama3.2:3b", "mistral:7b", "phi3:mini"]}
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Configuration
209
+
210
+ Config lives at `~/.freeaiagent/config.json` and is created on first run.
211
+
212
+ ```json
213
+ {
214
+ "default_backend": "ollama",
215
+ "default_model": "llama3.2:3b",
216
+ "port": 7731,
217
+ "backends": {
218
+ "ollama": {
219
+ "base_url": "http://localhost:11434"
220
+ },
221
+ "groq": {
222
+ "api_key": ""
223
+ }
224
+ },
225
+ "fallback_order": ["ollama", "groq"]
226
+ }
227
+ ```
228
+
229
+ Edit directly or use `freeaiagent config set <key> <value>` with dotted keys:
230
+
231
+ ```bash
232
+ freeaiagent config set port 8080
233
+ freeaiagent config set backends.ollama.base_url http://192.168.1.10:11434
234
+ freeaiagent config set backends.groq.api_key gsk_abc123
235
+ freeaiagent config set default_backend groq
236
+ freeaiagent config set default_model llama-3.1-8b-instant
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Backends
242
+
243
+ ### Ollama (default)
244
+ Runs locally. No API key. No data leaves your machine.
245
+
246
+ ```bash
247
+ # Install Ollama from https://ollama.com
248
+ ollama pull llama3.2:3b
249
+ freeaiagent start
250
+ ```
251
+
252
+ Any model available on your Ollama instance works. Switch with:
253
+ ```bash
254
+ freeaiagent config set default_model mistral:7b
255
+ ```
256
+
257
+ ### Groq (free-tier cloud)
258
+ Fast inference. Free API key at [console.groq.com](https://console.groq.com).
259
+
260
+ ```bash
261
+ freeaiagent config set backends.groq.api_key gsk_...
262
+ freeaiagent config set default_backend groq
263
+ freeaiagent config set default_model llama-3.1-8b-instant
264
+ ```
265
+
266
+ Free-tier models available on Groq:
267
+
268
+ | Model | Context |
269
+ |---|---|
270
+ | `llama-3.1-8b-instant` | 128k |
271
+ | `llama-3.3-70b-versatile` | 128k |
272
+ | `mixtral-8x7b-32768` | 32k |
273
+ | `gemma2-9b-it` | 8k |
274
+
275
+ ### Automatic fallback
276
+ If the default backend is unreachable, `freeaiagent` tries the next one in `fallback_order` automatically. No configuration needed for this to work — just have both set up.
277
+
278
+ ---
279
+
280
+ ## Using from another app
281
+
282
+ The agent runs as a separate process. Your app calls it over HTTP — no LLM dependencies, no model management, no context handling in your code.
283
+
284
+ **Python (stdlib only):**
285
+ ```python
286
+ import urllib.request, json
287
+
288
+ def ask(message):
289
+ body = json.dumps({"message": message}).encode()
290
+ req = urllib.request.Request(
291
+ "http://localhost:7731/chat",
292
+ data=body,
293
+ headers={"Content-Type": "application/json"},
294
+ )
295
+ return json.loads(urllib.request.urlopen(req).read())["response"]
296
+
297
+ def run_task(task, input_text=None):
298
+ body = json.dumps({"task": task, "input": input_text}).encode()
299
+ req = urllib.request.Request(
300
+ "http://localhost:7731/task",
301
+ data=body,
302
+ headers={"Content-Type": "application/json"},
303
+ )
304
+ return json.loads(urllib.request.urlopen(req).read())["result"]
305
+ ```
306
+
307
+ **Shell:**
308
+ ```bash
309
+ curl -s -X POST http://localhost:7731/task \
310
+ -H "Content-Type: application/json" \
311
+ -d "{\"task\": \"summarize\", \"input\": \"$(cat notes.txt)\"}" \
312
+ | python -c "import sys,json; print(json.load(sys.stdin)['result'])"
313
+ ```
314
+
315
+ **JavaScript / Node:**
316
+ ```js
317
+ const res = await fetch("http://localhost:7731/chat", {
318
+ method: "POST",
319
+ headers: { "Content-Type": "application/json" },
320
+ body: JSON.stringify({ message: "hello" }),
321
+ });
322
+ const { response } = await res.json();
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Context storage
328
+
329
+ Conversation history is stored in `~/.freeaiagent/context.db` (SQLite). It persists across server restarts. Clear it any time:
330
+
331
+ ```bash
332
+ freeaiagent context clear
333
+ # or
334
+ curl -X DELETE http://localhost:7731/context
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Roadmap
340
+
341
+ **Phase 2 — Named sessions**
342
+ Multiple apps keep separate conversation histories via a `session_id` field.
343
+ ```json
344
+ {"message": "hello", "session_id": "my-project"}
345
+ ```
346
+
347
+ **Phase 3 — Auto caller detection**
348
+ Sessions created automatically per caller using `X-Caller-ID` header or caller port. Zero config for multi-app setups.
349
+
350
+ **Phase 4 — More backends and features**
351
+ - llamafile (single-file model, no Ollama install needed)
352
+ - Together AI, OpenRouter
353
+ - Streaming responses (`/chat/stream` SSE)
354
+ - Tool use / function calling
355
+ - Minimal web UI at `localhost:7731`
356
+
357
+ ---
358
+
359
+ ## Development
360
+
361
+ ```bash
362
+ git clone <repo>
363
+ cd freeaiagent
364
+ pip install -e ".[dev,groq]"
365
+
366
+ pytest # unit + integration (no LLM needed)
367
+ pytest tests/smoke/ -m smoke -v # smoke tests (requires Ollama running)
368
+ ```
369
+
370
+ ---
371
+
372
+ ## License
373
+
374
+ MIT
@@ -0,0 +1,344 @@
1
+ # freeaiagent
2
+
3
+ A local AI agent service you `pip install` once and call from anywhere.
4
+
5
+ Runs as a persistent HTTP server on `localhost:7731`. Stores conversation history in SQLite. Any app — script, CLI tool, personal project — can delegate tasks to it with a single HTTP call, no LLM code required on the caller's side.
6
+
7
+ Built on free LLM backends: **Ollama** (local, no API key) and **Groq** (free-tier cloud).
8
+
9
+ ---
10
+
11
+ ## Why
12
+
13
+ Embedding LLM logic directly into every app that needs it means duplicating prompt management, context handling, model configuration, and install detection across projects. When something changes — a new model, a different provider, a context bug — you fix it in every app separately.
14
+
15
+ `freeaiagent` is the single place that owns all of that. Your apps just call an endpoint.
16
+
17
+ ---
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install freeaiagent # Ollama only
23
+ pip install "freeaiagent[groq]" # + Groq support
24
+ ```
25
+
26
+ Requires Python 3.10+.
27
+
28
+ ---
29
+
30
+ ## Quick start
31
+
32
+ **1. Start the server**
33
+ ```bash
34
+ freeaiagent start
35
+ # Running at http://localhost:7731
36
+ # API docs at http://localhost:7731/docs
37
+ ```
38
+
39
+ **2. Chat with it**
40
+ ```bash
41
+ freeaiagent chat
42
+ # You: what is the capital of France?
43
+ # Agent [llama3.2:3b]: Paris.
44
+ ```
45
+
46
+ **3. Call it from any app**
47
+ ```python
48
+ import urllib.request, json
49
+
50
+ req = urllib.request.Request(
51
+ "http://localhost:7731/chat",
52
+ data=json.dumps({"message": "summarize this for me: ..."}).encode(),
53
+ headers={"Content-Type": "application/json"},
54
+ )
55
+ response = json.loads(urllib.request.urlopen(req).read())["response"]
56
+ ```
57
+
58
+ No pip dependencies needed in the calling app. Pure stdlib.
59
+
60
+ ---
61
+
62
+ ## CLI
63
+
64
+ ```bash
65
+ freeaiagent start # start server (default port 7731)
66
+ freeaiagent start --port 8080 # custom port
67
+ freeaiagent start --reload # dev mode: auto-reload on code change
68
+
69
+ freeaiagent chat # interactive chat (context preserved)
70
+ freeaiagent chat "quick one-liner" # single message, then exit
71
+
72
+ freeaiagent task "explain this" \
73
+ --input "$(cat file.py)" # one-shot task, no context read/written
74
+ freeaiagent task "translate to French" \
75
+ --input "Hello world" \
76
+ --model mistral:7b # override model for this task
77
+
78
+ freeaiagent status # health check + active backend/model
79
+ freeaiagent models # list models on active backend
80
+
81
+ freeaiagent context show # print conversation history
82
+ freeaiagent context clear # wipe conversation history
83
+
84
+ freeaiagent config show # print current config
85
+ freeaiagent config set default_model mistral:7b
86
+ freeaiagent config set default_backend groq
87
+ freeaiagent config set backends.groq.api_key gsk_...
88
+ ```
89
+
90
+ ---
91
+
92
+ ## HTTP API
93
+
94
+ All endpoints accept and return JSON.
95
+
96
+ ### `POST /chat`
97
+ Send a message. Conversation history is read and updated automatically.
98
+
99
+ ```bash
100
+ curl -X POST http://localhost:7731/chat \
101
+ -H "Content-Type: application/json" \
102
+ -d '{"message": "what did I just ask you?"}'
103
+ ```
104
+
105
+ ```json
106
+ {
107
+ "response": "You asked me what you just asked me.",
108
+ "model": "llama3.2:3b",
109
+ "context_length": 4
110
+ }
111
+ ```
112
+
113
+ | Field | Type | Description |
114
+ |---|---|---|
115
+ | `message` | string | required |
116
+ | `system` | string | optional system prompt override |
117
+
118
+ ---
119
+
120
+ ### `POST /task`
121
+ One-shot task. No context is read or written — clean slate every time.
122
+
123
+ ```bash
124
+ curl -X POST http://localhost:7731/task \
125
+ -H "Content-Type: application/json" \
126
+ -d '{"task": "list all TODO comments", "input": "..."}'
127
+ ```
128
+
129
+ ```json
130
+ {
131
+ "result": "Line 42: TODO fix this\nLine 87: TODO add tests",
132
+ "model": "llama3.2:3b"
133
+ }
134
+ ```
135
+
136
+ | Field | Type | Description |
137
+ |---|---|---|
138
+ | `task` | string | required — the instruction |
139
+ | `input` | string | optional — content to work on |
140
+ | `model` | string | optional — override model for this call |
141
+ | `system` | string | optional — override system prompt |
142
+
143
+ ---
144
+
145
+ ### `GET /context`
146
+ Returns the full conversation history.
147
+
148
+ ```json
149
+ {
150
+ "messages": [
151
+ {"role": "user", "content": "hello", "timestamp": "2026-06-21T10:00:00+00:00"},
152
+ {"role": "assistant", "content": "hi there", "timestamp": "2026-06-21T10:00:01+00:00"}
153
+ ],
154
+ "total": 2
155
+ }
156
+ ```
157
+
158
+ ### `DELETE /context`
159
+ Clears all conversation history.
160
+
161
+ ```json
162
+ {"cleared": 4, "message": "Cleared 4 messages."}
163
+ ```
164
+
165
+ ### `GET /health`
166
+ ```json
167
+ {"status": "ok", "active_backend": "ollama", "default_model": "llama3.2:3b"}
168
+ ```
169
+ Returns `"status": "degraded"` with an `"error"` field if no backend is reachable.
170
+
171
+ ### `GET /models`
172
+ ```json
173
+ {"models": ["llama3.2:3b", "mistral:7b", "phi3:mini"]}
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Configuration
179
+
180
+ Config lives at `~/.freeaiagent/config.json` and is created on first run.
181
+
182
+ ```json
183
+ {
184
+ "default_backend": "ollama",
185
+ "default_model": "llama3.2:3b",
186
+ "port": 7731,
187
+ "backends": {
188
+ "ollama": {
189
+ "base_url": "http://localhost:11434"
190
+ },
191
+ "groq": {
192
+ "api_key": ""
193
+ }
194
+ },
195
+ "fallback_order": ["ollama", "groq"]
196
+ }
197
+ ```
198
+
199
+ Edit directly or use `freeaiagent config set <key> <value>` with dotted keys:
200
+
201
+ ```bash
202
+ freeaiagent config set port 8080
203
+ freeaiagent config set backends.ollama.base_url http://192.168.1.10:11434
204
+ freeaiagent config set backends.groq.api_key gsk_abc123
205
+ freeaiagent config set default_backend groq
206
+ freeaiagent config set default_model llama-3.1-8b-instant
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Backends
212
+
213
+ ### Ollama (default)
214
+ Runs locally. No API key. No data leaves your machine.
215
+
216
+ ```bash
217
+ # Install Ollama from https://ollama.com
218
+ ollama pull llama3.2:3b
219
+ freeaiagent start
220
+ ```
221
+
222
+ Any model available on your Ollama instance works. Switch with:
223
+ ```bash
224
+ freeaiagent config set default_model mistral:7b
225
+ ```
226
+
227
+ ### Groq (free-tier cloud)
228
+ Fast inference. Free API key at [console.groq.com](https://console.groq.com).
229
+
230
+ ```bash
231
+ freeaiagent config set backends.groq.api_key gsk_...
232
+ freeaiagent config set default_backend groq
233
+ freeaiagent config set default_model llama-3.1-8b-instant
234
+ ```
235
+
236
+ Free-tier models available on Groq:
237
+
238
+ | Model | Context |
239
+ |---|---|
240
+ | `llama-3.1-8b-instant` | 128k |
241
+ | `llama-3.3-70b-versatile` | 128k |
242
+ | `mixtral-8x7b-32768` | 32k |
243
+ | `gemma2-9b-it` | 8k |
244
+
245
+ ### Automatic fallback
246
+ If the default backend is unreachable, `freeaiagent` tries the next one in `fallback_order` automatically. No configuration needed for this to work — just have both set up.
247
+
248
+ ---
249
+
250
+ ## Using from another app
251
+
252
+ The agent runs as a separate process. Your app calls it over HTTP — no LLM dependencies, no model management, no context handling in your code.
253
+
254
+ **Python (stdlib only):**
255
+ ```python
256
+ import urllib.request, json
257
+
258
+ def ask(message):
259
+ body = json.dumps({"message": message}).encode()
260
+ req = urllib.request.Request(
261
+ "http://localhost:7731/chat",
262
+ data=body,
263
+ headers={"Content-Type": "application/json"},
264
+ )
265
+ return json.loads(urllib.request.urlopen(req).read())["response"]
266
+
267
+ def run_task(task, input_text=None):
268
+ body = json.dumps({"task": task, "input": input_text}).encode()
269
+ req = urllib.request.Request(
270
+ "http://localhost:7731/task",
271
+ data=body,
272
+ headers={"Content-Type": "application/json"},
273
+ )
274
+ return json.loads(urllib.request.urlopen(req).read())["result"]
275
+ ```
276
+
277
+ **Shell:**
278
+ ```bash
279
+ curl -s -X POST http://localhost:7731/task \
280
+ -H "Content-Type: application/json" \
281
+ -d "{\"task\": \"summarize\", \"input\": \"$(cat notes.txt)\"}" \
282
+ | python -c "import sys,json; print(json.load(sys.stdin)['result'])"
283
+ ```
284
+
285
+ **JavaScript / Node:**
286
+ ```js
287
+ const res = await fetch("http://localhost:7731/chat", {
288
+ method: "POST",
289
+ headers: { "Content-Type": "application/json" },
290
+ body: JSON.stringify({ message: "hello" }),
291
+ });
292
+ const { response } = await res.json();
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Context storage
298
+
299
+ Conversation history is stored in `~/.freeaiagent/context.db` (SQLite). It persists across server restarts. Clear it any time:
300
+
301
+ ```bash
302
+ freeaiagent context clear
303
+ # or
304
+ curl -X DELETE http://localhost:7731/context
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Roadmap
310
+
311
+ **Phase 2 — Named sessions**
312
+ Multiple apps keep separate conversation histories via a `session_id` field.
313
+ ```json
314
+ {"message": "hello", "session_id": "my-project"}
315
+ ```
316
+
317
+ **Phase 3 — Auto caller detection**
318
+ Sessions created automatically per caller using `X-Caller-ID` header or caller port. Zero config for multi-app setups.
319
+
320
+ **Phase 4 — More backends and features**
321
+ - llamafile (single-file model, no Ollama install needed)
322
+ - Together AI, OpenRouter
323
+ - Streaming responses (`/chat/stream` SSE)
324
+ - Tool use / function calling
325
+ - Minimal web UI at `localhost:7731`
326
+
327
+ ---
328
+
329
+ ## Development
330
+
331
+ ```bash
332
+ git clone <repo>
333
+ cd freeaiagent
334
+ pip install -e ".[dev,groq]"
335
+
336
+ pytest # unit + integration (no LLM needed)
337
+ pytest tests/smoke/ -m smoke -v # smoke tests (requires Ollama running)
338
+ ```
339
+
340
+ ---
341
+
342
+ ## License
343
+
344
+ MIT
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"