opencode-skills-collection 3.1.9 → 3.1.11
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.
- package/bundled-skills/.antigravity-install-manifest.json +8 -1
- package/bundled-skills/browser-testing-with-devtools/SKILL.md +334 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/sources/sources.md +4 -0
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/drizzle-migration-conflict/SKILL.md +179 -0
- package/bundled-skills/drizzle-migration-conflict/references/ci-policy.md +87 -0
- package/bundled-skills/drizzle-migration-conflict/references/conflict-resolution.md +163 -0
- package/bundled-skills/drizzle-migration-conflict/references/report-template.md +69 -0
- package/bundled-skills/drizzle-migration-conflict/references/sources.md +51 -0
- package/bundled-skills/drizzle-migration-conflict/scripts/check_drizzle_migrations.py +721 -0
- package/bundled-skills/frontend-lighthouse/SKILL.md +348 -0
- package/bundled-skills/pre-release-review/SKILL.md +198 -0
- package/bundled-skills/pre-release-review/references/checklist.md +104 -0
- package/bundled-skills/pre-release-review/references/report-template.md +91 -0
- package/bundled-skills/re-create/SKILL.md +251 -0
- package/bundled-skills/weaviate/SKILL.md +132 -0
- package/bundled-skills/weaviate/references/ask.md +36 -0
- package/bundled-skills/weaviate/references/create_collection.md +152 -0
- package/bundled-skills/weaviate/references/environment_requirements.md +34 -0
- package/bundled-skills/weaviate/references/example_data.md +24 -0
- package/bundled-skills/weaviate/references/explore_collection.md +50 -0
- package/bundled-skills/weaviate/references/fetch_filter.md +88 -0
- package/bundled-skills/weaviate/references/get_collection.md +32 -0
- package/bundled-skills/weaviate/references/hybrid_search.md +47 -0
- package/bundled-skills/weaviate/references/import_data.md +160 -0
- package/bundled-skills/weaviate/references/keyword_search.md +38 -0
- package/bundled-skills/weaviate/references/list_collections.md +31 -0
- package/bundled-skills/weaviate/references/query_search.md +38 -0
- package/bundled-skills/weaviate/references/semantic_search.md +46 -0
- package/bundled-skills/weaviate/scripts/ask.py +106 -0
- package/bundled-skills/weaviate/scripts/create_collection.py +359 -0
- package/bundled-skills/weaviate/scripts/example_data.py +945 -0
- package/bundled-skills/weaviate/scripts/explore_collection.py +295 -0
- package/bundled-skills/weaviate/scripts/fetch_filter.py +261 -0
- package/bundled-skills/weaviate/scripts/get_collection.py +122 -0
- package/bundled-skills/weaviate/scripts/hybrid_search.py +157 -0
- package/bundled-skills/weaviate/scripts/import.py +701 -0
- package/bundled-skills/weaviate/scripts/keyword_search.py +142 -0
- package/bundled-skills/weaviate/scripts/list_collections.py +77 -0
- package/bundled-skills/weaviate/scripts/query_search.py +135 -0
- package/bundled-skills/weaviate/scripts/semantic_search.py +139 -0
- package/bundled-skills/weaviate/scripts/weaviate_conn.py +231 -0
- package/bundled-skills/weaviate-cookbooks/SKILL.md +67 -0
- package/bundled-skills/weaviate-cookbooks/references/advanced_rag.md +274 -0
- package/bundled-skills/weaviate-cookbooks/references/agentic_rag.md +360 -0
- package/bundled-skills/weaviate-cookbooks/references/async_client.md +428 -0
- package/bundled-skills/weaviate-cookbooks/references/basic_agent.md +270 -0
- package/bundled-skills/weaviate-cookbooks/references/basic_rag.md +219 -0
- package/bundled-skills/weaviate-cookbooks/references/data_explorer.md +336 -0
- package/bundled-skills/weaviate-cookbooks/references/environment_requirements.md +78 -0
- package/bundled-skills/weaviate-cookbooks/references/frontend_interface.md +104 -0
- package/bundled-skills/weaviate-cookbooks/references/pdf_multimodal_rag.md +635 -0
- package/bundled-skills/weaviate-cookbooks/references/project_setup.md +75 -0
- package/bundled-skills/weaviate-cookbooks/references/query_agent_chatbot.md +163 -0
- package/package.json +1 -1
- package/skills_index.json +156 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# Async Client Usage
|
|
2
|
+
|
|
3
|
+
Guide for using the Weaviate Python async client in production applications (FastAPI, async frameworks).
|
|
4
|
+
|
|
5
|
+
## 📚 Official Documentation Reference
|
|
6
|
+
|
|
7
|
+
**For agents:** If you encounter any issues not covered here, refer to the official Weaviate documentation:
|
|
8
|
+
|
|
9
|
+
- **Primary Reference**: [Weaviate Async API Documentation](https://docs.weaviate.io/weaviate/client-libraries/python/async)
|
|
10
|
+
- **Python Client Reference**: [Weaviate Python Client Docs](https://docs.weaviate.io/weaviate/client-libraries/python)
|
|
11
|
+
- **API Reference**: [ReadTheDocs - Python Client](https://weaviate-python-client.readthedocs.io/en/stable/)
|
|
12
|
+
- **Troubleshooting**: [Community Forum](https://forum.weaviate.io/) | [GitHub Issues](https://github.com/weaviate/weaviate-python-client/issues)
|
|
13
|
+
|
|
14
|
+
> **Note**: The async client (`WeaviateAsyncClient`) is available in `weaviate-client` v4.7.0+.
|
|
15
|
+
|
|
16
|
+
## Connection Methods
|
|
17
|
+
|
|
18
|
+
Three instantiation helpers are provided ([docs](https://docs.weaviate.io/weaviate/client-libraries/python/async#instantiation)):
|
|
19
|
+
|
|
20
|
+
### Weaviate Cloud (Recommended)
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import weaviate
|
|
24
|
+
from weaviate.classes.init import Auth
|
|
25
|
+
|
|
26
|
+
# Use the official helper function for Weaviate Cloud
|
|
27
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
28
|
+
cluster_url="your-cluster.weaviate.cloud", # Accepts hostname with or without https://
|
|
29
|
+
auth_credentials=Auth.api_key("your-api-key"),
|
|
30
|
+
headers={ # Note: parameter is "headers" not "additional_headers"
|
|
31
|
+
"X-OpenAI-Api-Key": "sk-...",
|
|
32
|
+
"X-Anthropic-Api-Key": "sk-ant-...",
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
await client.connect() # Required! Async helpers don't auto-connect
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Reference**: [Weaviate Cloud Setup](https://docs.weaviate.io/weaviate/quickstart)
|
|
40
|
+
|
|
41
|
+
### Self-Hosted
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# For local instances
|
|
45
|
+
client = weaviate.use_async_with_local()
|
|
46
|
+
|
|
47
|
+
# For custom endpoints
|
|
48
|
+
client = weaviate.use_async_with_custom(
|
|
49
|
+
http_host="localhost",
|
|
50
|
+
http_port=8080,
|
|
51
|
+
http_secure=False,
|
|
52
|
+
grpc_host="localhost",
|
|
53
|
+
grpc_port=50051,
|
|
54
|
+
grpc_secure=False,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
await client.connect()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Reference**: [Connection Configuration](https://weaviate-python-client.readthedocs.io/en/stable/weaviate.html)
|
|
61
|
+
|
|
62
|
+
### Authentication
|
|
63
|
+
|
|
64
|
+
Multiple authentication modes are supported ([docs](https://docs.weaviate.io/weaviate/client-libraries/python#authentication)):
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from weaviate.classes.init import Auth
|
|
68
|
+
|
|
69
|
+
# API Key (most common for Weaviate Cloud)
|
|
70
|
+
auth = Auth.api_key("your-api-key")
|
|
71
|
+
|
|
72
|
+
# Bearer Token (with optional refresh token)
|
|
73
|
+
auth = Auth.bearer_token("access-token", refresh_token="refresh-token")
|
|
74
|
+
|
|
75
|
+
# Client Credentials (OIDC)
|
|
76
|
+
auth = Auth.client_credentials(client_secret="secret")
|
|
77
|
+
|
|
78
|
+
# Client Password (OIDC Resource Owner Password flow)
|
|
79
|
+
auth = Auth.client_password(username="user", password="pass")
|
|
80
|
+
|
|
81
|
+
# Usage
|
|
82
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
83
|
+
cluster_url="your-cluster.weaviate.cloud",
|
|
84
|
+
auth_credentials=auth,
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Critical Patterns
|
|
89
|
+
|
|
90
|
+
### ⚠️ Connection Lifecycle
|
|
91
|
+
|
|
92
|
+
**Important**: Unlike synchronous helpers, async helpers **do not connect automatically** ([docs](https://docs.weaviate.io/weaviate/client-libraries/python/async#instantiation)). You must explicitly call `.connect()` and `.close()`:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# ❌ Wrong - client not connected
|
|
96
|
+
client = weaviate.use_async_with_weaviate_cloud(...)
|
|
97
|
+
collections = await client.collections.list_all() # Will fail!
|
|
98
|
+
|
|
99
|
+
# ✅ Correct - explicit connect/close
|
|
100
|
+
client = weaviate.use_async_with_weaviate_cloud(...)
|
|
101
|
+
await client.connect()
|
|
102
|
+
collections = await client.collections.list_all()
|
|
103
|
+
await client.close()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### ⚠️ Sync vs Async Methods
|
|
107
|
+
|
|
108
|
+
**Key distinction** ([docs](https://docs.weaviate.io/weaviate/client-libraries/python/async#which-methods-are-async)): Methods involving server requests are async; local operations are synchronous.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Collection retrieval is SYNC (no await)
|
|
112
|
+
collection = client.collections.get("MyCollection")
|
|
113
|
+
|
|
114
|
+
# Operations on collections are ASYNC (need await)
|
|
115
|
+
config = await collection.config.get()
|
|
116
|
+
results = await collection.query.fetch_objects()
|
|
117
|
+
count = await collection.aggregate.over_all()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Rule:** Getting the collection object is sync; calling methods on it is async.
|
|
121
|
+
|
|
122
|
+
### ⚠️ Bulk Operations
|
|
123
|
+
|
|
124
|
+
**Important Note** ([docs](https://docs.weaviate.io/weaviate/client-libraries/python/async#bulk-import-operations)): For large-scale data imports, use the **synchronous client** and its batch operations. The sync client's batch methods already handle concurrency internally and are optimized for bulk operations.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# ✅ For bulk imports, prefer sync client
|
|
128
|
+
import weaviate
|
|
129
|
+
|
|
130
|
+
with weaviate.connect_to_weaviate_cloud(...) as client:
|
|
131
|
+
collection = client.collections.get("MyCollection")
|
|
132
|
+
|
|
133
|
+
# Batch insert handles concurrency automatically
|
|
134
|
+
with collection.batch.dynamic() as batch:
|
|
135
|
+
for item in large_dataset:
|
|
136
|
+
batch.add_object(properties=item)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Use the async client for:
|
|
140
|
+
|
|
141
|
+
- Web applications (FastAPI, Starlette)
|
|
142
|
+
- Concurrent request handling
|
|
143
|
+
- Interactive queries
|
|
144
|
+
|
|
145
|
+
Don't use the async client for:
|
|
146
|
+
|
|
147
|
+
- Bulk data imports (use sync client instead)
|
|
148
|
+
|
|
149
|
+
## Context Manager Pattern (Recommended)
|
|
150
|
+
|
|
151
|
+
**Best Practice** ([docs](https://docs.weaviate.io/weaviate/client-libraries/python/async#using-the-async-context-manager)): Use `async with` to automatically connect/disconnect:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from contextlib import asynccontextmanager
|
|
155
|
+
from typing import AsyncGenerator
|
|
156
|
+
|
|
157
|
+
@asynccontextmanager
|
|
158
|
+
async def get_weaviate_client(
|
|
159
|
+
cluster_url: str,
|
|
160
|
+
api_key: str,
|
|
161
|
+
provider_headers: dict[str, str] | None = None,
|
|
162
|
+
) -> AsyncGenerator[weaviate.WeaviateAsyncClient, None]:
|
|
163
|
+
"""Connect to Weaviate Cloud with automatic cleanup."""
|
|
164
|
+
# Remove scheme if present
|
|
165
|
+
hostname = cluster_url.replace("https://", "").replace("http://", "")
|
|
166
|
+
|
|
167
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
168
|
+
cluster_url=hostname,
|
|
169
|
+
auth_credentials=Auth.api_key(api_key),
|
|
170
|
+
headers=provider_headers,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
await client.connect()
|
|
175
|
+
yield client
|
|
176
|
+
finally:
|
|
177
|
+
await client.close()
|
|
178
|
+
|
|
179
|
+
# Usage
|
|
180
|
+
async def example():
|
|
181
|
+
async with get_weaviate_client(
|
|
182
|
+
cluster_url="your-cluster.weaviate.cloud",
|
|
183
|
+
api_key="your-key",
|
|
184
|
+
) as client:
|
|
185
|
+
collections = await client.collections.list_all()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
> **Note**: When using the context manager, `.connect()` and `.close()` are called automatically.
|
|
189
|
+
|
|
190
|
+
## FastAPI Integration
|
|
191
|
+
|
|
192
|
+
**Use Case** ([docs](https://docs.weaviate.io/weaviate/client-libraries/python/async#use-cases)): The async client excels in web frameworks like FastAPI for handling concurrent requests.
|
|
193
|
+
|
|
194
|
+
Use lifespan management for shared client across requests:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from fastapi import FastAPI
|
|
198
|
+
from contextlib import asynccontextmanager
|
|
199
|
+
|
|
200
|
+
@asynccontextmanager
|
|
201
|
+
async def lifespan(app: FastAPI):
|
|
202
|
+
# Startup: connect to Weaviate
|
|
203
|
+
app.state.weaviate = weaviate.use_async_with_weaviate_cloud(
|
|
204
|
+
cluster_url="your-cluster.weaviate.cloud",
|
|
205
|
+
auth_credentials=Auth.api_key("your-key"),
|
|
206
|
+
)
|
|
207
|
+
await app.state.weaviate.connect()
|
|
208
|
+
|
|
209
|
+
yield
|
|
210
|
+
|
|
211
|
+
# Shutdown: close connection
|
|
212
|
+
await app.state.weaviate.close()
|
|
213
|
+
|
|
214
|
+
app = FastAPI(lifespan=lifespan)
|
|
215
|
+
|
|
216
|
+
@app.get("/collections")
|
|
217
|
+
async def list_collections():
|
|
218
|
+
collections = await app.state.weaviate.collections.list_all()
|
|
219
|
+
return {"collections": list(collections.keys())}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Community Discussion**: [FastAPI Best Practices](https://forum.weaviate.io/t/what-is-the-best-practice-to-use-v4-python-client-for-query-with-fastapi-or-other-async-python-framework/1245)
|
|
223
|
+
|
|
224
|
+
## Common Pitfalls
|
|
225
|
+
|
|
226
|
+
### 1. Parameter Name Confusion
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
# ❌ Wrong - WeaviateAsyncClient() constructor uses different param
|
|
230
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
231
|
+
additional_headers={...} # Wrong parameter name!
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# ✅ Correct - use "headers" not "additional_headers"
|
|
235
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
236
|
+
headers={...}
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 2. URL Format
|
|
241
|
+
|
|
242
|
+
Both formats work with helper functions:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
# ✅ Both accepted
|
|
246
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
247
|
+
cluster_url="https://cluster.weaviate.cloud" # With scheme
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
251
|
+
cluster_url="cluster.weaviate.cloud" # Without scheme
|
|
252
|
+
)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 3. Sync vs Async Function Names
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
# ❌ Wrong - sync client (cannot use await)
|
|
259
|
+
client = weaviate.connect_to_weaviate_cloud(...)
|
|
260
|
+
await client.connect() # TypeError!
|
|
261
|
+
|
|
262
|
+
# ✅ Correct - async client
|
|
263
|
+
client = weaviate.use_async_with_weaviate_cloud(...)
|
|
264
|
+
await client.connect()
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Naming pattern:**
|
|
268
|
+
|
|
269
|
+
- Sync: `connect_to_*` (e.g., `connect_to_weaviate_cloud`)
|
|
270
|
+
- Async: `use_async_with_*` (e.g., `use_async_with_weaviate_cloud`)
|
|
271
|
+
|
|
272
|
+
### 4. Port Configuration
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
# ❌ Wrong - manual port config causes conflicts with Weaviate Cloud
|
|
276
|
+
client = WeaviateAsyncClient(
|
|
277
|
+
connection_params=ConnectionParams.from_url(
|
|
278
|
+
url="https://cluster.weaviate.cloud",
|
|
279
|
+
grpc_port=443, # Conflict!
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# ✅ Correct - use helper function (handles ports automatically)
|
|
284
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
285
|
+
cluster_url="cluster.weaviate.cloud"
|
|
286
|
+
)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Rule:** For Weaviate Cloud, always use `use_async_with_weaviate_cloud()` — it handles HTTP (443) and gRPC (50051) ports correctly.
|
|
290
|
+
|
|
291
|
+
## Multi-Cluster Example
|
|
292
|
+
|
|
293
|
+
Managing connections to multiple Weaviate clusters:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
@asynccontextmanager
|
|
297
|
+
async def get_multi_cluster_clients(
|
|
298
|
+
clusters: dict[str, dict[str, str]]
|
|
299
|
+
) -> AsyncGenerator[dict[str, weaviate.WeaviateAsyncClient], None]:
|
|
300
|
+
"""Connect to multiple Weaviate clusters.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
clusters: Dict of {cluster_id: {"url": "...", "api_key": "..."}}
|
|
304
|
+
"""
|
|
305
|
+
clients = {}
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
# Connect to all clusters
|
|
309
|
+
for cluster_id, config in clusters.items():
|
|
310
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
311
|
+
cluster_url=config["url"],
|
|
312
|
+
auth_credentials=Auth.api_key(config["api_key"]),
|
|
313
|
+
)
|
|
314
|
+
await client.connect()
|
|
315
|
+
clients[cluster_id] = client
|
|
316
|
+
|
|
317
|
+
yield clients
|
|
318
|
+
|
|
319
|
+
finally:
|
|
320
|
+
# Close all connections
|
|
321
|
+
for client in clients.values():
|
|
322
|
+
await client.close()
|
|
323
|
+
|
|
324
|
+
# Usage
|
|
325
|
+
async def example():
|
|
326
|
+
clusters = {
|
|
327
|
+
"prod": {"url": "prod.weaviate.cloud", "api_key": "key1"},
|
|
328
|
+
"dev": {"url": "dev.weaviate.cloud", "api_key": "key2"},
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async with get_multi_cluster_clients(clusters) as clients:
|
|
332
|
+
prod_collections = await clients["prod"].collections.list_all()
|
|
333
|
+
dev_collections = await clients["dev"].collections.list_all()
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Environment Variables
|
|
337
|
+
|
|
338
|
+
See [Environment Requirements](environment_requirements.md) for provider API keys.
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
import os
|
|
342
|
+
|
|
343
|
+
# Read from environment
|
|
344
|
+
cluster_url = os.environ["WEAVIATE_URL"]
|
|
345
|
+
api_key = os.environ["WEAVIATE_API_KEY"]
|
|
346
|
+
|
|
347
|
+
# Build provider headers
|
|
348
|
+
provider_headers = {}
|
|
349
|
+
if openai_key := os.getenv("OPENAI_API_KEY"):
|
|
350
|
+
provider_headers["X-OpenAI-Api-Key"] = openai_key
|
|
351
|
+
if anthropic_key := os.getenv("ANTHROPIC_API_KEY"):
|
|
352
|
+
provider_headers["X-Anthropic-Api-Key"] = anthropic_key
|
|
353
|
+
|
|
354
|
+
client = weaviate.use_async_with_weaviate_cloud(
|
|
355
|
+
cluster_url=cluster_url,
|
|
356
|
+
auth_credentials=Auth.api_key(api_key),
|
|
357
|
+
headers=provider_headers or None,
|
|
358
|
+
)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Testing Async Code
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
import pytest
|
|
365
|
+
|
|
366
|
+
@pytest.mark.asyncio
|
|
367
|
+
async def test_weaviate_connection():
|
|
368
|
+
async with get_weaviate_client(
|
|
369
|
+
cluster_url="test-cluster.weaviate.cloud",
|
|
370
|
+
api_key="test-key",
|
|
371
|
+
) as client:
|
|
372
|
+
collections = await client.collections.list_all()
|
|
373
|
+
assert isinstance(collections, dict)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Quick Reference
|
|
377
|
+
|
|
378
|
+
| Task | Pattern | Await? |
|
|
379
|
+
| ---------------- | --------------------------------------------- | ------- |
|
|
380
|
+
| Create client | `weaviate.use_async_with_weaviate_cloud(...)` | No |
|
|
381
|
+
| Connect | `client.connect()` | **Yes** |
|
|
382
|
+
| Get collection | `client.collections.get("Name")` | No |
|
|
383
|
+
| List collections | `client.collections.list_all()` | **Yes** |
|
|
384
|
+
| Query data | `collection.query.fetch_objects()` | **Yes** |
|
|
385
|
+
| Get config | `collection.config.get()` | **Yes** |
|
|
386
|
+
| Aggregate | `collection.aggregate.over_all()` | **Yes** |
|
|
387
|
+
| Close | `client.close()` | **Yes** |
|
|
388
|
+
|
|
389
|
+
## Troubleshooting
|
|
390
|
+
|
|
391
|
+
### Common Issues
|
|
392
|
+
|
|
393
|
+
| Issue | Solution | Reference |
|
|
394
|
+
| ----------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
395
|
+
| Connection hangs indefinitely | Use context manager or ensure proper `.close()` | [GitHub #753](https://github.com/weaviate/weaviate-python-client/issues/753) |
|
|
396
|
+
| Multi-worker conflicts (Gunicorn) | Use lifespan management, not startup hooks | [GitHub #1292](https://github.com/weaviate/weaviate-python-client/issues/1292) |
|
|
397
|
+
| `TypeError: object NoneType can't be used in 'await'` | Use `use_async_with_*` not `connect_to_*` | [Async API Docs](https://docs.weaviate.io/weaviate/client-libraries/python/async) |
|
|
398
|
+
| Port conflicts with Weaviate Cloud | Use helper functions, not manual `ConnectionParams` | See "Common Pitfalls #4" above |
|
|
399
|
+
|
|
400
|
+
### Getting Help
|
|
401
|
+
|
|
402
|
+
**For agents:** When encountering errors:
|
|
403
|
+
|
|
404
|
+
1. Check the [Common Pitfalls](#common-pitfalls) section above
|
|
405
|
+
2. Search [Community Forum](https://forum.weaviate.io/) for similar issues
|
|
406
|
+
3. Check [GitHub Issues](https://github.com/weaviate/weaviate-python-client/issues) for known bugs
|
|
407
|
+
4. Refer to [official async documentation](https://docs.weaviate.io/weaviate/client-libraries/python/async)
|
|
408
|
+
5. Review [Python client best practices](https://docs.weaviate.io/weaviate/client-libraries/python/notes-best-practices)
|
|
409
|
+
|
|
410
|
+
## Additional Resources
|
|
411
|
+
|
|
412
|
+
### Official Documentation
|
|
413
|
+
|
|
414
|
+
- **Primary**: [Weaviate Async API](https://docs.weaviate.io/weaviate/client-libraries/python/async)
|
|
415
|
+
- **Python Client**: [Main Documentation](https://docs.weaviate.io/weaviate/client-libraries/python)
|
|
416
|
+
- **API Reference**: [ReadTheDocs](https://weaviate-python-client.readthedocs.io/en/stable/)
|
|
417
|
+
- **Best Practices**: [Notes and Best Practices](https://docs.weaviate.io/weaviate/client-libraries/python/notes-best-practices)
|
|
418
|
+
|
|
419
|
+
### Framework Integration
|
|
420
|
+
|
|
421
|
+
- [FastAPI Lifespan Events](https://fastapi.tiangolo.com/advanced/events/)
|
|
422
|
+
- [Python Async Context Managers](https://docs.python.org/3/reference/datamodel.html#asynchronous-context-managers)
|
|
423
|
+
|
|
424
|
+
### Community
|
|
425
|
+
|
|
426
|
+
- [Weaviate Community Forum](https://forum.weaviate.io/)
|
|
427
|
+
- [Python Client GitHub](https://github.com/weaviate/weaviate-python-client)
|
|
428
|
+
- [Weaviate Blog](https://weaviate.io/blog)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Basic Agent Cookbook
|
|
2
|
+
|
|
3
|
+
Build a tool-calling AI agent with structured outputs using DSPy.
|
|
4
|
+
For RAG tools, memory, and framework integrations, [see here](./agentic_rag.md).
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Docs to reference if needed:
|
|
9
|
+
- DSPy signatures: https://dspy.ai/learn/programming/signatures/
|
|
10
|
+
- DSPy language models: https://dspy.ai/learn/programming/language_models/
|
|
11
|
+
- LiteLLM providers: https://docs.litellm.ai/docs/
|
|
12
|
+
|
|
13
|
+
## Core Rules
|
|
14
|
+
|
|
15
|
+
- Use a virtual environment via `venv`
|
|
16
|
+
- Use `uv` for Python project/dependency management.
|
|
17
|
+
- Do not manually author `pyproject.toml` or `uv.lock`; let `uv` generate/update them.
|
|
18
|
+
- Use this install set: `uv add dspy python-dotenv`
|
|
19
|
+
- Customise this cookbook to the users specification, ask them for details if not given.
|
|
20
|
+
|
|
21
|
+
## Env Rules
|
|
22
|
+
|
|
23
|
+
Mandatory:
|
|
24
|
+
- An LLM provider API key (e.g. `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`)
|
|
25
|
+
- `WEAVIATE_URL`
|
|
26
|
+
- `WEAVIATE_API_KEY`
|
|
27
|
+
|
|
28
|
+
Optional:
|
|
29
|
+
- Matching provider keys listed in `environment_requirements.md`
|
|
30
|
+
|
|
31
|
+
If the user explicitly requests a non-Weaviate agent, `WEAVIATE_URL` and `WEAVIATE_API_KEY` can be omitted.
|
|
32
|
+
|
|
33
|
+
## Agent Response Signature
|
|
34
|
+
|
|
35
|
+
The structured output that defines what the LLM returns when selecting tools.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import dspy
|
|
39
|
+
from typing import Any, Dict
|
|
40
|
+
|
|
41
|
+
class AgentResponse(dspy.Signature):
|
|
42
|
+
|
|
43
|
+
# Input Fields
|
|
44
|
+
history: dspy.History = dspy.InputField()
|
|
45
|
+
user_prompt: str = dspy.InputField()
|
|
46
|
+
available_tools: str = dspy.InputField()
|
|
47
|
+
|
|
48
|
+
# Output Fields
|
|
49
|
+
response: str = dspy.OutputField(
|
|
50
|
+
description="The response to the user's prompt whilst the tool is running. Update the user on the progress of their request (if a tool is picked), or the final response to the user (if no tool is picked)."
|
|
51
|
+
)
|
|
52
|
+
tool: str | None = dspy.OutputField(
|
|
53
|
+
description="The tool that needs to be used. Return None if no tool is needed."
|
|
54
|
+
)
|
|
55
|
+
tool_inputs: Dict[str, Any] | None = dspy.OutputField(
|
|
56
|
+
description=(
|
|
57
|
+
"The inputs for the tool. Return an empty dictionary (still include the field) if no inputs are needed. "
|
|
58
|
+
"The key is the name of the input, the value is the value of the input."
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Extend `AgentResponse` as needed: add `confidence: float` for certainty scoring, `requires_clarification: bool` for follow-up questions, or modify `description` strings to shape agent behaviour for a specific domain.
|
|
64
|
+
|
|
65
|
+
## Router Agent (Single Step)
|
|
66
|
+
|
|
67
|
+
Wraps the agent response into a class that manages conversation history and tool execution.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from typing import Callable, List, Tuple
|
|
71
|
+
|
|
72
|
+
class RouterAgent:
|
|
73
|
+
def __init__(self, model: str, tools: List[Callable] = []):
|
|
74
|
+
self.tools: list[Callable] = tools
|
|
75
|
+
self.model = dspy.LM(model)
|
|
76
|
+
self.agent = dspy.ChainOfThought(AgentResponse)
|
|
77
|
+
self.conversation_history = dspy.History(messages=[])
|
|
78
|
+
|
|
79
|
+
def add_conversation_history(self, message: str, response: dspy.Prediction):
|
|
80
|
+
self.conversation_history.messages.append({"user_prompt": message, **response})
|
|
81
|
+
|
|
82
|
+
def get_tools_and_descriptions(self) -> str:
|
|
83
|
+
return "\n".join(
|
|
84
|
+
[
|
|
85
|
+
f"{tool.__name__}:\nDescription: {tool.__doc__ or ''}\nInputs: { {k: v for k, v in tool.__annotations__.items() if k != 'return'} }"
|
|
86
|
+
for tool in self.tools
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_response(self, user_prompt: str) -> Tuple[str, str | None]:
|
|
91
|
+
result = self.agent(
|
|
92
|
+
history=self.conversation_history,
|
|
93
|
+
user_prompt=user_prompt,
|
|
94
|
+
available_tools=self.get_tools_and_descriptions(),
|
|
95
|
+
lm=self.model,
|
|
96
|
+
)
|
|
97
|
+
self.add_conversation_history(message=user_prompt, response=result)
|
|
98
|
+
if result.tool and result.tool.lower() not in ["null", "none"]:
|
|
99
|
+
tool_function = next(
|
|
100
|
+
(tool for tool in self.tools if tool.__name__ == result.tool), None
|
|
101
|
+
)
|
|
102
|
+
if tool_function is None:
|
|
103
|
+
raise ValueError(f"Tool {result.tool} not found")
|
|
104
|
+
tool_inputs = {k: v for k, v in result.tool_inputs.items() if k != "return"}
|
|
105
|
+
tool_result = tool_function(**tool_inputs)
|
|
106
|
+
else:
|
|
107
|
+
tool_result = None
|
|
108
|
+
return result.response, tool_result
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
router = RouterAgent(
|
|
115
|
+
model="<model_name>", # e.g. claude-sonnet-4-5, gpt-5.2, gemini-2.5-pro
|
|
116
|
+
tools=[your_tool_function]
|
|
117
|
+
)
|
|
118
|
+
response, tool_result = router.get_response("user query here")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Tool Design
|
|
122
|
+
|
|
123
|
+
Tools are Python functions. The agent reads `__name__`, `__doc__`, and `__annotations__` to decide when to use them.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
def your_tool(param1: str, param2: int) -> str:
|
|
127
|
+
"""Clear description of what this tool does and when to use it."""
|
|
128
|
+
# tool logic here
|
|
129
|
+
return "result as string"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Key rules:
|
|
133
|
+
- Docstrings directly influence when the agent selects the tool. Be specific: "Get current weather conditions for a city" is better than "Get weather".
|
|
134
|
+
- Type hints guide what inputs the agent provides. Complex types like `filters: List[Dict]` may need additional description in the docstring.
|
|
135
|
+
- Return strings or string-serializable data.
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## Sequential Multi-Step Agent
|
|
139
|
+
|
|
140
|
+
For tasks requiring multiple tool calls in succession, add a followup signature and loop.
|
|
141
|
+
|
|
142
|
+
Followup signature (receives `tool_output` from the previous step):
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
class AgentFollowup(dspy.Signature):
|
|
146
|
+
|
|
147
|
+
# Input Fields
|
|
148
|
+
history: dspy.History = dspy.InputField()
|
|
149
|
+
user_prompt: str = dspy.InputField()
|
|
150
|
+
tool_output: str = dspy.InputField(description="The output of the previous tool.")
|
|
151
|
+
available_tools: str = dspy.InputField(
|
|
152
|
+
description="The available tools and their descriptions."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Output Fields
|
|
156
|
+
response: str = dspy.OutputField(
|
|
157
|
+
description="The response to the user's prompt whilst the tool is running. Update the user on the progress of their request (if a tool is picked), or the final response to the user (if no tool is picked)."
|
|
158
|
+
)
|
|
159
|
+
tool: str | None = dspy.OutputField(
|
|
160
|
+
description="The tool that needs to be used. Return None if no tool is needed."
|
|
161
|
+
)
|
|
162
|
+
tool_inputs: Dict[str, Any] | None = dspy.OutputField(
|
|
163
|
+
description="The inputs for the tool. Return an empty dictionary (still include the field) if no inputs are needed. The key is the name of the input, the value is the value of the input.",
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Modify `RouterAgent` to loop until the agent stops requesting tools:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
class RouterAgent:
|
|
171
|
+
def __init__(self, model: str, tools: List[Callable] = []):
|
|
172
|
+
self.tools: list[Callable] = tools
|
|
173
|
+
self.model = dspy.LM(model)
|
|
174
|
+
self.agent = dspy.ChainOfThought(AgentResponse)
|
|
175
|
+
self.followup_agent = dspy.ChainOfThought(AgentFollowup)
|
|
176
|
+
self.conversation_history = dspy.History(messages=[])
|
|
177
|
+
|
|
178
|
+
def add_conversation_history(self, message: str, response: dspy.Prediction):
|
|
179
|
+
self.conversation_history.messages.append({"user_prompt": message, **response})
|
|
180
|
+
|
|
181
|
+
def get_tools_and_descriptions(self) -> str:
|
|
182
|
+
return "\n".join(
|
|
183
|
+
[
|
|
184
|
+
f"{tool.__name__}:\nDescription: {tool.__doc__ or ''}\nInputs: { {k: v for k, v in tool.__annotations__.items() if k != 'return'} }"
|
|
185
|
+
for tool in self.tools
|
|
186
|
+
]
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def get_response(self, user_prompt: str) -> str:
|
|
190
|
+
result = self.agent(
|
|
191
|
+
history=self.conversation_history,
|
|
192
|
+
user_prompt=user_prompt,
|
|
193
|
+
available_tools=self.get_tools_and_descriptions(),
|
|
194
|
+
lm=self.model,
|
|
195
|
+
)
|
|
196
|
+
self.add_conversation_history(message=user_prompt, response=result)
|
|
197
|
+
|
|
198
|
+
max_iter = 10
|
|
199
|
+
iter = 0
|
|
200
|
+
|
|
201
|
+
while result.tool is not None and result.tool.lower() not in ["null", "none"]:
|
|
202
|
+
iter += 1
|
|
203
|
+
if iter > max_iter:
|
|
204
|
+
break
|
|
205
|
+
|
|
206
|
+
tool_function = next(
|
|
207
|
+
(tool for tool in self.tools if tool.__name__ == result.tool), None
|
|
208
|
+
)
|
|
209
|
+
if tool_function is None:
|
|
210
|
+
raise ValueError(f"Tool {result.tool} not found")
|
|
211
|
+
|
|
212
|
+
tool_inputs = {k: v for k, v in result.tool_inputs.items() if k != "return"}
|
|
213
|
+
tool_result = tool_function(**tool_inputs)
|
|
214
|
+
|
|
215
|
+
result = self.followup_agent(
|
|
216
|
+
history=self.conversation_history,
|
|
217
|
+
tool_output=tool_result,
|
|
218
|
+
available_tools=self.get_tools_and_descriptions(),
|
|
219
|
+
lm=self.model,
|
|
220
|
+
)
|
|
221
|
+
self.add_conversation_history(message=tool_result, response=result)
|
|
222
|
+
|
|
223
|
+
return result.response
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`max_iter` controls how many tool calls can occur before forced termination. Increase for complex multi-step tasks, decrease to limit costs and runaway loops.
|
|
227
|
+
|
|
228
|
+
## User-specific Customisations
|
|
229
|
+
|
|
230
|
+
If not specified ask the user about these points before implementing their respective strategies:
|
|
231
|
+
|
|
232
|
+
**LLM Framework**
|
|
233
|
+
|
|
234
|
+
You can use DSPy (works with all LiteLLM providers) or LiteLLM itself.
|
|
235
|
+
|
|
236
|
+
- DSPy: https://dspy.ai/learn/programming/language_models/
|
|
237
|
+
- LiteLLM: https://docs.litellm.ai/docs/
|
|
238
|
+
|
|
239
|
+
Alternatively, users can use a single model provider. What model provider will they use?
|
|
240
|
+
|
|
241
|
+
- OpenAI (https://platform.openai.com/docs/libraries)
|
|
242
|
+
- Anthropic (https://platform.claude.com/docs/)
|
|
243
|
+
- Google GenAI (https://ai.google.dev/gemini-api/docs/libraries)
|
|
244
|
+
- Other (such as locally hosted models), use best judgement
|
|
245
|
+
|
|
246
|
+
These may require additional installs.
|
|
247
|
+
|
|
248
|
+
**Model Selection**
|
|
249
|
+
|
|
250
|
+
What model(s) will the user use? Consider a mixed approach: capable model for main agent routing, cheaper model for auxiliary tasks like memory creation.
|
|
251
|
+
|
|
252
|
+
**Tools**
|
|
253
|
+
|
|
254
|
+
What tools does the user need? List their functions, inputs, and expected outputs. The agent is only as capable as its tools.
|
|
255
|
+
|
|
256
|
+
**Single-step vs Multi-step**
|
|
257
|
+
|
|
258
|
+
Does the user need a single tool call per query, or should the agent chain multiple tools in sequence? Only use multi-step if the use case requires it.
|
|
259
|
+
|
|
260
|
+
## Troubleshooting
|
|
261
|
+
|
|
262
|
+
- DSPy signature warnings about missing fields: ensure all input fields are passed or mark optional fields appropriately.
|
|
263
|
+
- Tool not found errors: ensure tool function names match exactly what the agent outputs.
|
|
264
|
+
- Agent loops indefinitely: lower `max_iter` or add more explicit termination conditions.
|
|
265
|
+
- For any other issues, refer to the official library/package documentation and use web search extensively for troubleshooting.
|
|
266
|
+
|
|
267
|
+
## Done Criteria
|
|
268
|
+
|
|
269
|
+
- Create test scripts to check each function works independently with test data. Tear down tests after completion, or create a proper test suite with pytest (requires install)
|
|
270
|
+
- User has completed specification of the app.
|