clap-agents 0.1.1__py3-none-any.whl → 0.2.1__py3-none-any.whl

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,142 @@
1
+ import json
2
+ import functools
3
+ from typing import Any, Dict, List, Optional, cast
4
+ import asyncio
5
+ import anyio
6
+
7
+ _CHROMADB_LIB_AVAILABLE = False
8
+ _ChromaDB_Client_Placeholder_Type = Any
9
+ _ChromaDB_Collection_Placeholder_Type = Any
10
+ _ChromaDB_Settings_Placeholder_Type = Any
11
+ _ChromaDB_EmbeddingFunction_Placeholder_Type = Any
12
+ _ChromaDB_DefaultEmbeddingFunction_Placeholder_Type = Any
13
+
14
+ try:
15
+ import chromadb
16
+ from chromadb import Collection as ImportedChromaCollection
17
+ from chromadb.config import Settings as ImportedChromaSettings
18
+ from chromadb.utils.embedding_functions import (
19
+ EmbeddingFunction as ImportedChromaEF,
20
+ DefaultEmbeddingFunction as ImportedChromaDefaultEF
21
+ )
22
+ from chromadb.api.types import Documents, Embeddings
23
+ _CHROMADB_LIB_AVAILABLE = True
24
+ _ChromaDB_Client_Placeholder_Type = chromadb.Client
25
+ _ChromaDB_Collection_Placeholder_Type = ImportedChromaCollection
26
+ _ChromaDB_Settings_Placeholder_Type = ImportedChromaSettings
27
+ _ChromaDB_EmbeddingFunction_Placeholder_Type = ImportedChromaEF
28
+ _ChromaDB_DefaultEmbeddingFunction_Placeholder_Type = ImportedChromaDefaultEF
29
+ except ImportError:
30
+ class Documents: pass
31
+ class Embeddings: pass
32
+ pass
33
+
34
+
35
+ from .base import ( Document as ClapDocument, Embedding as ClapEmbedding, ID, Metadata, QueryResult, VectorStoreInterface,) # Aliased Document
36
+ from clap.embedding.base_embedding import EmbeddingFunctionInterface as ClapEFInterface
37
+
38
+
39
+ class _AsyncEFWrapperForChroma:
40
+ """Wraps an async CLAP EmbeddingFunctionInterface to be callable synchronously by Chroma."""
41
+ def __init__(self, async_ef_call: asyncio.coroutine, loop: asyncio.AbstractEventLoop):
42
+ self._async_ef_call = async_ef_call
43
+ self._loop = loop
44
+
45
+ def __call__(self, input: Documents) -> Embeddings:
46
+
47
+ if self._loop.is_running():
48
+
49
+ future = asyncio.run_coroutine_threadsafe(self._async_ef_call(input), self._loop)
50
+ return future.result()
51
+ else:
52
+ return self._loop.run_until_complete(self._async_ef_call(input))
53
+
54
+
55
+ class ChromaStore(VectorStoreInterface):
56
+ _client: _ChromaDB_Client_Placeholder_Type
57
+ _collection: _ChromaDB_Collection_Placeholder_Type
58
+ _clap_ef_is_async: bool = False
59
+
60
+ def __init__(
61
+ self,
62
+ collection_name: str,
63
+ embedding_function: Optional[ClapEFInterface] = None, # CLAP's interface
64
+ path: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None,
65
+ client_settings: Optional[_ChromaDB_Settings_Placeholder_Type] = None,
66
+ ):
67
+ if not _CHROMADB_LIB_AVAILABLE:
68
+ raise ImportError("The 'chromadb' library is required to use ChromaStore.")
69
+
70
+ self.collection_name = collection_name
71
+
72
+ if path: self._client = chromadb.PersistentClient(path=path, settings=client_settings)
73
+ elif host and port: self._client = chromadb.HttpClient(host=host, port=port, settings=client_settings)
74
+ else: self._client = chromadb.EphemeralClient(settings=client_settings)
75
+
76
+
77
+ chroma_ef_to_pass_to_chroma: Optional[_ChromaDB_EmbeddingFunction_Placeholder_Type]
78
+
79
+ if embedding_function is None:
80
+ chroma_ef_to_pass_to_chroma = _ChromaDB_DefaultEmbeddingFunction_Placeholder_Type()
81
+ self._clap_ef_is_async = False
82
+ print(f"ChromaStore: Using ChromaDB's DefaultEmbeddingFunction for '{self.collection_name}'.")
83
+
84
+ elif isinstance(embedding_function, _ChromaDB_DefaultEmbeddingFunction_Placeholder_Type):
85
+ chroma_ef_to_pass_to_chroma = embedding_function
86
+ self._clap_ef_is_async = False
87
+ print(f"ChromaStore: Using provided native Chroma EmbeddingFunction for '{self.collection_name}'.")
88
+ else:
89
+
90
+ ef_call_method = getattr(embedding_function, "__call__", None)
91
+ if ef_call_method and asyncio.iscoroutinefunction(ef_call_method):
92
+ self._clap_ef_is_async = True
93
+ print(f"ChromaStore: Wrapping async CLAP EmbeddingFunction for Chroma compatibility for '{self.collection_name}'.")
94
+
95
+ try:
96
+ loop = asyncio.get_running_loop()
97
+ except RuntimeError:
98
+
99
+ print("ChromaStore WARNING: No running asyncio loop to wrap async EF for Chroma. This might fail.")
100
+
101
+ loop = asyncio.new_event_loop()
102
+
103
+
104
+ chroma_ef_to_pass_to_chroma = _AsyncEFWrapperForChroma(ef_call_method, loop)
105
+ else:
106
+ self._clap_ef_is_async = False
107
+ print(f"ChromaStore: Using synchronous CLAP EmbeddingFunction for '{self.collection_name}'.")
108
+ chroma_ef_to_pass_to_chroma = embedding_function # type: ignore
109
+
110
+ self._collection = self._client.get_or_create_collection(
111
+ name=self.collection_name,
112
+ embedding_function=chroma_ef_to_pass_to_chroma
113
+ )
114
+ # print(f"ChromaStore: Collection '{self.collection_name}' ready.")
115
+
116
+
117
+ async def _run_sync(self, func, *args, **kwargs):
118
+ bound_func = functools.partial(func, *args, **kwargs)
119
+ return await anyio.to_thread.run_sync(bound_func)
120
+
121
+ async def add_documents(self, documents: List[ClapDocument], ids: List[ID], metadatas: Optional[List[Metadata]] = None, embeddings: Optional[List[ClapEmbedding]] = None) -> None:
122
+
123
+ # print(f"ChromaStore: Adding {len(ids)} documents to '{self.collection_name}'...")
124
+ await self._run_sync(self._collection.add, ids=ids, embeddings=embeddings, metadatas=metadatas, documents=documents)
125
+ # print(f"ChromaStore: Add/Update completed for {len(ids)} documents.")
126
+
127
+ async def aquery(self, query_texts: Optional[List[ClapDocument]] = None, query_embeddings: Optional[List[ClapEmbedding]] = None, n_results: int = 5, where: Optional[Dict[str, Any]] = None, where_document: Optional[Dict[str, Any]] = None, include: List[str] = ["metadatas", "documents", "distances"]) -> QueryResult:
128
+ if not query_texts and not query_embeddings: raise ValueError("Requires query_texts or query_embeddings.")
129
+ if query_texts and query_embeddings: query_texts = None
130
+
131
+
132
+ # print(f"ChromaStore: Querying collection '{self.collection_name}'...")
133
+ results = await self._run_sync(self._collection.query, query_embeddings=query_embeddings, query_texts=query_texts, n_results=n_results, where=where, where_document=where_document, include=include)
134
+
135
+ num_queries = len(query_texts or query_embeddings or [])
136
+
137
+ return QueryResult(
138
+ ids=results.get("ids") or ([[]] * num_queries), embeddings=results.get("embeddings"),
139
+ documents=results.get("documents"), metadatas=results.get("metadatas"), distances=results.get("distances") )
140
+
141
+ async def adelete(self, ids: Optional[List[ID]] = None, where: Optional[Dict[str, Any]] = None, where_document: Optional[Dict[str, Any]] = None) -> None:
142
+ await self._run_sync(self._collection.delete, ids=ids, where=where, where_document=where_document)
@@ -0,0 +1,155 @@
1
+ import asyncio
2
+ import json
3
+ import functools
4
+ import os
5
+ from typing import Any, Dict, List, Optional, cast, Type
6
+
7
+ import anyio
8
+
9
+ _QDRANT_LIB_AVAILABLE = False
10
+ _AsyncQdrantClient_Placeholder_Type = Any
11
+ _QdrantClient_Placeholder_Type = Any
12
+ _qdrant_models_Placeholder_Type = Any
13
+ _QdrantUnexpectedResponse_Placeholder_Type = type(Exception)
14
+
15
+ try:
16
+ from qdrant_client import AsyncQdrantClient as ImportedAsyncQC, QdrantClient as ImportedSyncQC, models as ImportedModels
17
+ from qdrant_client.http.exceptions import UnexpectedResponse as ImportedQdrantUnexpectedResponse
18
+ _QDRANT_LIB_AVAILABLE = True
19
+ _AsyncQdrantClient_Placeholder_Type = ImportedAsyncQC
20
+ _QdrantClient_Placeholder_Type = ImportedSyncQC
21
+ _qdrant_models_Placeholder_Type = ImportedModels
22
+ _QdrantUnexpectedResponse_Placeholder_Type = ImportedQdrantUnexpectedResponse
23
+ except ImportError:
24
+ pass
25
+
26
+
27
+ from .base import ( Document, Embedding, ID, Metadata, QueryResult, VectorStoreInterface,)
28
+ from clap.embedding.base_embedding import EmbeddingFunctionInterface
29
+
30
+
31
+ class QdrantStore(VectorStoreInterface):
32
+ _async_client: _AsyncQdrantClient_Placeholder_Type
33
+ models: Any # For qdrant_client.models
34
+
35
+ def __init__(self):
36
+ if not hasattr(self, "_initialized_via_factory"):
37
+ raise RuntimeError("Use QdrantStore.create(...) async factory method.")
38
+
39
+ @classmethod
40
+ async def create(
41
+ cls: Type['QdrantStore'],
42
+ collection_name: str, embedding_function: EmbeddingFunctionInterface,
43
+ path: Optional[str] = None, distance_metric: Any = None,
44
+ recreate_collection_if_exists: bool = False, **qdrant_client_kwargs: Any
45
+ ) -> 'QdrantStore':
46
+ if not _QDRANT_LIB_AVAILABLE:
47
+ raise ImportError("The 'qdrant-client' library is required. Install with 'pip install \"clap-agents[qdrant]\"'")
48
+ if not embedding_function: raise ValueError("embedding_function is required.")
49
+
50
+ instance = cls.__new__(cls)
51
+ instance._initialized_via_factory = True
52
+ instance.collection_name = collection_name
53
+ instance.models = _qdrant_models_Placeholder_Type
54
+ instance._embedding_function = embedding_function
55
+ instance.distance_metric = distance_metric if distance_metric else instance.models.Distance.COSINE
56
+ instance.vector_size = instance._embedding_function.get_embedding_dimension()
57
+
58
+ client_location_for_log = path if path else ":memory:"
59
+ # print(f"QdrantStore (Async): Initializing client for '{instance.collection_name}' at '{client_location_for_log}'.")
60
+ if path: instance._async_client = _AsyncQdrantClient_Placeholder_Type(path=path, **qdrant_client_kwargs)
61
+ else: instance._async_client = _AsyncQdrantClient_Placeholder_Type(location=":memory:", **qdrant_client_kwargs)
62
+
63
+ await instance._setup_collection_async(recreate_collection_if_exists)
64
+ return instance
65
+
66
+ async def _setup_collection_async(self, recreate_if_exists: bool):
67
+ try:
68
+ if recreate_if_exists:
69
+ await self._async_client.delete_collection(collection_name=self.collection_name)
70
+ await self._async_client.create_collection(
71
+ collection_name=self.collection_name,
72
+ vectors_config=self.models.VectorParams(size=self.vector_size, distance=self.distance_metric))
73
+ return
74
+ try:
75
+ info = await self._async_client.get_collection(collection_name=self.collection_name)
76
+ if info.config.params.size != self.vector_size or info.config.params.distance.lower() != self.distance_metric.lower(): # type: ignore
77
+ raise ValueError("Existing Qdrant collection has incompatible config.")
78
+ except (_QdrantUnexpectedResponse_Placeholder_Type, ValueError) as e: # type: ignore
79
+ if isinstance(e, _QdrantUnexpectedResponse_Placeholder_Type) and e.status_code == 404 or "not found" in str(e).lower(): # type: ignore
80
+ await self._async_client.create_collection(
81
+ collection_name=self.collection_name,
82
+ vectors_config=self.models.VectorParams(size=self.vector_size, distance=self.distance_metric))
83
+ else: raise
84
+ except Exception as e: print(f"QdrantStore: Error during collection setup: {e}"); raise
85
+
86
+
87
+ async def _embed_texts_via_interface(self, texts: List[Document]) -> List[Embedding]:
88
+ if not self._embedding_function: raise RuntimeError("EF missing.")
89
+ ef_call = self._embedding_function.__call__
90
+ if asyncio.iscoroutinefunction(ef_call): return await ef_call(texts)
91
+ return await anyio.to_thread.run_sync(functools.partial(ef_call, texts))
92
+
93
+ async def add_documents(self, documents: List[Document], ids: List[ID], metadatas: Optional[List[Metadata]] = None, embeddings: Optional[List[Embedding]] = None) -> None:
94
+ if not documents and not embeddings: raise ValueError("Requires docs or embeddings.")
95
+ num_items = len(documents) if documents else (len(embeddings) if embeddings else 0)
96
+ if num_items == 0: return
97
+ if len(ids) != num_items: raise ValueError("'ids' length mismatch.")
98
+ if metadatas and len(metadatas) != num_items: raise ValueError("'metadatas' length mismatch.")
99
+ if not embeddings and documents: embeddings = await self._embed_texts_via_interface(documents)
100
+ if not embeddings: return
101
+ points: List[Any] = [] # Use Any for models.PointStruct if models is Any
102
+ for i, item_id in enumerate(ids):
103
+ payload = metadatas[i].copy() if metadatas and i < len(metadatas) else {}
104
+ if documents and i < len(documents): payload["_clap_document_content_"] = documents[i]
105
+ points.append(self.models.PointStruct(id=str(item_id), vector=embeddings[i], payload=payload))
106
+ if points: await self._async_client.upsert(collection_name=self.collection_name, points=points, wait=True)
107
+
108
+ async def aquery(self, query_texts: Optional[List[Document]] = None, query_embeddings: Optional[List[Embedding]] = None, n_results: int = 5, where: Optional[Dict[str, Any]] = None, include: List[str] = ["metadatas", "documents", "distances"], **kwargs) -> QueryResult:
109
+ if not query_texts and not query_embeddings: raise ValueError("Requires query_texts or query_embeddings.")
110
+ if query_texts and query_embeddings: query_texts = None
111
+ q_filter = self._translate_clap_filter(where)
112
+ if query_texts: q_vectors = await self._embed_texts_via_interface(query_texts)
113
+ elif query_embeddings: q_vectors = query_embeddings
114
+ else: return QueryResult(ids=[[]], embeddings=None, documents=None, metadatas=None, distances=None)
115
+ raw_results: List[List[Any]] = []
116
+ for qv in q_vectors:
117
+ hits = await self._async_client.search(collection_name=self.collection_name, query_vector=qv, query_filter=q_filter, limit=n_results, with_payload=True, with_vectors="embeddings" in include, **kwargs)
118
+ raw_results.append(hits)
119
+ return self._format_qdrant_results(raw_results, include) # Keep using self.models
120
+
121
+ def _translate_clap_filter(self, clap_where_filter: Optional[Dict[str, Any]]) -> Optional[Any]: # Return Any
122
+ if not clap_where_filter or not _QDRANT_LIB_AVAILABLE: return None
123
+ must = []
124
+ for k, v in clap_where_filter.items():
125
+ if isinstance(v, dict) and "$eq" in v: must.append(self.models.FieldCondition(key=k, match=self.models.MatchValue(value=v["$eq"])))
126
+ elif isinstance(v, (str,int,float,bool)): must.append(self.models.FieldCondition(key=k, match=self.models.MatchValue(value=v)))
127
+ return self.models.Filter(must=must) if must else None
128
+
129
+ def _format_qdrant_results(self, raw_results: List[List[Any]], include: List[str]) -> QueryResult:
130
+ ids, embs, docs, metas, dists_final = [], [], [], [], []
131
+ for hits_list in raw_results:
132
+ current_ids, current_embs, current_docs, current_metas, current_dists = [],[],[],[],[]
133
+ for hit in hits_list:
134
+ current_ids.append(str(hit.id))
135
+ payload = hit.payload if hit.payload else {}
136
+ if "distances" in include and hit.score is not None: current_dists.append(hit.score)
137
+ if "embeddings" in include and hit.vector : current_embs.append(cast(List[float], hit.vector))
138
+ if "documents" in include : current_docs.append(payload.get("_clap_document_content_", ""))
139
+ if "metadatas" in include : current_metas.append({k:v for k,v in payload.items() if k != "_clap_document_content_"})
140
+ ids.append(current_ids); embs.append(current_embs); docs.append(current_docs); metas.append(current_metas); dists_final.append(current_dists)
141
+ return QueryResult(ids=ids, embeddings=embs if "embeddings" in include else None, documents=docs if "documents" in include else None, metadatas=metas if "metadatas" in include else None, distances=dists_final if "distances" in include else None)
142
+
143
+
144
+ async def adelete(self, ids: Optional[List[ID]] = None, where: Optional[Dict[str, Any]] = None, **kwargs ) -> None:
145
+ if not ids and not where: return
146
+ q_filter = self._translate_clap_filter(where)
147
+ selector: Any = None
148
+ if ids and q_filter: selector = self.models.FilterSelector(filter=self.models.Filter(must=[q_filter, self.models.HasIdCondition(has_id=[str(i) for i in ids])]))
149
+ elif ids: selector = self.models.PointIdsList(points=[str(i) for i in ids])
150
+ elif q_filter: selector = self.models.FilterSelector(filter=q_filter)
151
+ if selector: await self._async_client.delete(collection_name=self.collection_name, points_selector=selector, wait=True)
152
+
153
+ async def close(self):
154
+ if hasattr(self, '_async_client') and self._async_client:
155
+ await self._async_client.close(timeout=5)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clap-agents
3
- Version: 0.1.1
3
+ Version: 0.2.1
4
4
  Summary: A Python framework for building cognitive agentic patterns including ReAct agents, Multi-Agent Teams, native tool calling, and MCP client integration.
5
5
  Project-URL: Homepage, https://github.com/MaitreyaM/CLAP-AGENTS.git
6
6
  Project-URL: Repository, https://github.com/MaitreyaM/CLAP-AGENTS.git
@@ -233,44 +233,93 @@ Requires-Dist: openai>=1.0.0
233
233
  Requires-Dist: pydantic<3.0.0,>=2.7.2
234
234
  Requires-Dist: requests
235
235
  Provides-Extra: all
236
- Requires-Dist: clap[standard-tools,viz]; extra == 'all'
236
+ Requires-Dist: chromadb>=0.5.0; extra == 'all'
237
+ Requires-Dist: crawl4ai; extra == 'all'
238
+ Requires-Dist: fastembed>=0.2.0; extra == 'all'
239
+ Requires-Dist: graphviz; extra == 'all'
240
+ Requires-Dist: ollama>=0.2.0; extra == 'all'
241
+ Requires-Dist: pandas; extra == 'all'
242
+ Requires-Dist: pypdf; extra == 'all'
243
+ Requires-Dist: qdrant-client[fastembed]>=1.7.0; extra == 'all'
244
+ Requires-Dist: sentence-transformers; extra == 'all'
245
+ Provides-Extra: chromadb
246
+ Requires-Dist: chromadb>=0.5.0; extra == 'chromadb'
247
+ Provides-Extra: fastembed
248
+ Requires-Dist: fastembed>=0.2.0; extra == 'fastembed'
249
+ Provides-Extra: ollama
250
+ Requires-Dist: ollama>=0.2.0; extra == 'ollama'
251
+ Provides-Extra: pandas
252
+ Requires-Dist: pandas; extra == 'pandas'
253
+ Provides-Extra: pdf
254
+ Requires-Dist: pypdf; extra == 'pdf'
255
+ Provides-Extra: qdrant
256
+ Requires-Dist: qdrant-client[fastembed]>=1.7.0; extra == 'qdrant'
257
+ Provides-Extra: rag
258
+ Requires-Dist: chromadb>=0.5.0; extra == 'rag'
259
+ Requires-Dist: fastembed>=0.2.0; extra == 'rag'
260
+ Requires-Dist: ollama>=0.2.0; extra == 'rag'
261
+ Requires-Dist: pypdf; extra == 'rag'
262
+ Requires-Dist: qdrant-client[fastembed]>=1.7.0; extra == 'rag'
263
+ Requires-Dist: sentence-transformers; extra == 'rag'
264
+ Provides-Extra: sentence-transformers
265
+ Requires-Dist: sentence-transformers; extra == 'sentence-transformers'
237
266
  Provides-Extra: standard-tools
238
267
  Requires-Dist: crawl4ai; extra == 'standard-tools'
239
268
  Provides-Extra: viz
240
269
  Requires-Dist: graphviz; extra == 'viz'
241
270
  Description-Content-Type: text/markdown
242
271
 
243
- # CLAP - Cognitive Layer Agents Package
272
+ <p align="center">
273
+ <img src="GITCLAP.png" alt="CLAP Logo" width="700" height="200"/>
274
+ </p>
244
275
 
245
- [![PyPI version](https://img.shields.io/pypi/v/CLAP.svg)](https://pypi.org/project/CLAP/)
276
+ # CLAP - Cognitive Layer Agent Package
277
+
278
+ [![PyPI version](https://img.shields.io/pypi/v/clap-agents.svg)](https://pypi.org/project/clap-agents/)
246
279
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
247
- [![Python Version](https://img.shields.io/pypi/pyversions/CLAP.svg)](https://pypi.org/project/CLAP/)
248
- <!-- Add other badges as desired, e.g., build status, coverage -->
280
+ [![Python Version](https://img.shields.io/pypi/pyversions/clap-agents.svg)](https://pypi.org/project/clap-agents/)
249
281
 
250
- **CLAP (Cognitive Layer Agent Package)** is a Python framework providing building blocks for creating sophisticated AI agents based on modern agentic patterns. It enables developers to easily construct agents capable of reasoning, planning, and interacting with external tools and systems.
282
+ **CLAP (Cognitive Layer Agent Package)** is a Python framework providing building blocks for creating sophisticated AI agents based on modern agentic patterns. It enables developers to easily construct agents capable of reasoning, planning, and interacting with external tools, systems, and knowledge bases.
251
283
 
252
284
  Built with an asynchronous core (`asyncio`), CLAP offers flexibility and performance for complex agentic workflows.
253
285
 
286
+ <p align="center">
287
+ <img src="PIP CLAP.png" alt="CLAP Pip Install" width="700" height="200"/> <!-- Updated alt text -->
288
+ </p>
289
+
254
290
  ## Key Features
255
291
 
256
292
  * **Modular Agent Patterns:**
257
- * **ReAct Agent:** Implements the Reason-Act loop with robust thought-prompting and native tool calling.
293
+ * **ReAct Agent:** Implements the Reason-Act loop with robust thought-prompting and native tool calling. Ideal for complex reasoning and RAG.
294
+ * **Tool Agent:** A straightforward agent for single-step tool usage, including simple RAG.
258
295
  * **Multi-Agent Teams:** Define teams of specialized agents with dependencies, enabling collaborative task execution (sequential or parallel).
259
- * **Simple Tool Agent:** A straightforward agent for single-step tool usage.
260
296
  * **Advanced Tool Integration:**
261
297
  * **Native LLM Tool Calling:** Leverages modern LLM APIs for reliable tool execution.
262
298
  * **Local Tools:** Easily define and use local Python functions (both synchronous and asynchronous) as tools using the `@tool` decorator.
263
299
  * **Remote Tools (MCP):** Integrates with [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers via the included `MCPClientManager`, allowing agents to discover and use tools exposed by external systems (currently supports SSE transport).
264
- * **Robust Validation:** Uses `jsonschema` for strict validation of tool arguments provided by the LLM.
300
+ * **Robust Validation & Coercion:** Uses `jsonschema` for strict validation of tool arguments and attempts type coercion for common LLM outputs (e.g., string numbers to integers).
301
+ * **Retrieval Augmented Generation (RAG) Capabilities:**
302
+ * **`VectorStoreInterface`:** An abstraction for interacting with various vector databases.
303
+ * **Supported Vector Stores:**
304
+ * **ChromaDB:** (`ChromaStore`) For local or self-hosted vector storage.
305
+ * **Qdrant:** (`QdrantStore`) For local (in-memory or file-based) vector storage.
306
+ * **`EmbeddingFunctionInterface`:** A protocol for consistent interaction with different embedding models.
307
+ * **Supported Embedding Function Wrappers:**
308
+ * `SentenceTransformerEmbeddings`: Uses models from the `sentence-transformers` library.
309
+ * `OllamaEmbeddings`: Generates embeddings using models running locally via Ollama.
310
+ * `FastEmbedEmbeddings`: Utilizes the `fastembed` library for CPU-optimized embeddings. (Note: Performance for very large batch ingestions via the async wrapper might vary based on CPU and may be slower than SentenceTransformers for initial bulk loads.)
311
+ * **RAG-Aware Agents:** Both `Agent` (via `ReactAgent`) and `ToolAgent` can be equipped with a `vector_store` to perform `vector_query` tool calls, enabling them to retrieve context before responding.
312
+ * **Utilities:** Includes basic PDF and CSV text loaders and chunking strategies in `clap.utils.rag_utils`.
265
313
  * **Pluggable LLM Backends:**
266
314
  * Uses a **Strategy Pattern** (`LLMServiceInterface`) to abstract LLM interactions.
267
315
  * Includes ready-to-use service implementations for:
268
316
  * **Groq:** (`GroqService`)
269
317
  * **Google Generative AI (Gemini):** (`GoogleOpenAICompatService` via OpenAI compatibility layer)
318
+ * **Ollama (Local LLMs):** (`OllamaOpenAICompatService` also known as `OllamaService` via OpenAI compatibility layer, allowing use of locally run models like Llama 3, Mistral, etc.)
270
319
  * Easily extensible to support other LLM providers.
271
320
  * **Asynchronous Core:** Built entirely on `asyncio` for efficient I/O operations and potential concurrency.
272
- * **Structured Context Passing:** Enables clear and organized information flow between agents in a team using Python dictionaries.
273
- * **Built-in Tools:** Includes helpers for web search, web scraping, and email interaction (optional dependencies may apply).
321
+ * **Structured Context Passing:** Enables clear and organized information flow between agents in a team.
322
+ * **Built-in Tools:** Includes helpers for web search (`duckduckgo_search`). More available via optional dependencies.
274
323
 
275
324
  ## Installation
276
325
 
@@ -278,13 +327,40 @@ Ensure you have Python 3.10 or later installed.
278
327
 
279
328
  ```bash
280
329
  pip install clap-agents
330
+ ```
331
+
332
+ Ensure you have Python 3.10 or later installed.
333
+
334
+ ```bash
335
+ pip install clap-agents
336
+
281
337
 
338
+ To use specific features, you might need to install optional dependencies:
339
+ # For Qdrant support (includes fastembed)
340
+ pip install "clap-agents[qdrant]"
341
+
342
+ # For ChromaDB support
343
+ pip install "clap-agents[chromadb]"
344
+
345
+ # For Ollama (LLM and/or Embeddings)
346
+ pip install "clap-agents[ollama]"
347
+
348
+ # For other tools like web crawling or visualization
349
+ pip install "clap-agents[standard_tools,viz]"
350
+
351
+ # To install all major optional dependencies
352
+ pip install "clap-agents[all]"
353
+ ```
354
+
355
+
356
+ Check the pyproject.toml for the full list of [project.optional-dependencies]. You will also need to have external services like Ollama or Qdrant (if used locally) running.
282
357
  Depending on the tools or LLM backends you intend to use, you might need additional dependencies listed in the pyproject.toml (e.g., groq, openai, mcp, jsonschema, requests, duckduckgo-search, graphviz). Check the [project.dependencies] and [project.optional-dependencies] sections.
283
358
 
284
359
 
285
- Quick Start: Simple Tool calling Agent with a Local Tool
360
+ ## Quick Start: Simple Tool calling Agent with a Local Tool
286
361
  This example demonstrates creating a Tool calling agent using the Groq backend and a local tool
287
362
 
363
+ ```
288
364
  from dotenv import load_dotenv
289
365
  from clap import ToolAgent
290
366
  from clap import duckduckgo_search
@@ -292,25 +368,24 @@ from clap import duckduckgo_search
292
368
  load_dotenv()
293
369
 
294
370
  async def main():
295
- agent = ToolAgent(tools=duckduckgo_search, model="llama-3.3-70b-versatile")
371
+ agent = ToolAgent(tools=duckduckgo_search, model="meta-llama/llama-4-scout-17b-16e-instruct")
296
372
  user_query = "Search the web for recent news about AI advancements."
297
373
  response = await agent.run(user_msg=user_query)
298
374
  print(f"Response:\n{response}")
299
375
 
300
376
  asyncio.run(main())
377
+ ```
301
378
 
302
379
 
303
-
304
- Quick Start: Simple ReAct Agent with a Local Tool
380
+ ## Quick Start: Simple ReAct Agent with a Local Tool
305
381
  This example demonstrates creating a ReAct agent using the Groq backend and a local tool.
306
382
 
307
-
383
+ ```
308
384
  import asyncio
309
385
  import os
310
386
  from dotenv import load_dotenv
311
387
  from clap import ReactAgent, tool, GroqService
312
388
 
313
- # --- Setup ---
314
389
  load_dotenv()
315
390
  @tool
316
391
  def get_word_length(word: str) -> int:
@@ -333,14 +408,117 @@ async def main():
333
408
  print(response)
334
409
 
335
410
  asyncio.run(main())
411
+ ```
412
+
413
+ ## Quick Start: Simple Tool-Calling Agent with Ollama
414
+ This example demonstrates a ToolAgent using a local Ollama model and a local tool.
415
+ Ensure Ollama is running and you have pulled the model (e.g., ollama pull llama3).
416
+
417
+ ```
418
+ import asyncio
419
+ from dotenv import load_dotenv
420
+ from clap import ToolAgent, tool, OllamaService # Assuming OllamaService is your OllamaOpenAICompatService
421
+
422
+ load_dotenv()
423
+
424
+ @tool
425
+ def get_capital(country: str) -> str:
426
+ """Returns the capital of a country."""
427
+ if country.lower() == "france": return "Paris"
428
+ return f"I don't know the capital of {country}."
429
+
430
+ async def main():
431
+ # Initialize the Ollama service
432
+ ollama_llm_service = OllamaService(default_model="llama3") # Specify your Ollama model
433
+
434
+ agent = ToolAgent(
435
+ llm_service=ollama_llm_service,
436
+ model="llama3", # Model name for this agent
437
+ tools=[get_capital]
438
+ )
439
+ user_query = "What is the capital of France?"
440
+ response = await agent.run(user_msg=user_query)
441
+ print(f"Query: {user_query}\nResponse:\n{response}")
336
442
 
443
+ await ollama_llm_service.close() # Important for OllamaService
337
444
 
338
- Exploring Further
339
- Multi-Agent Teams: See examples/team_agent.py for setting up sequential or parallel agent workflows.
340
- MCP Integration: Check examples/minimal_react_mcp_test.py and examples/test_tool_agent_mcp.py (ensure the corresponding MCP server like examples/minimal_mcp_server.py is running).
341
- Google GenAI: Modify the Quick Start to use GoogleOpenAICompatService instead of GroqService (ensure GOOGLE_API_KEY is set and openai library is installed).
342
- Built-in Tools: Explore the tools provided in clap.tools (like duckduckgo_search, scrape_url, etc.).
445
+ if __name__ == "__main__":
446
+ asyncio.run(main())
343
447
 
448
+ ```
449
+
450
+ ## Quick Start: RAG Agent with Qdrant and Ollama Embeddings
451
+ This example shows an Agent performing RAG using Ollama for embeddings and Qdrant as the vector store.
452
+ Ensure Ollama is running (with nomic-embed-text and llama3 pulled) and Qdrant is running (e.g., via Docker).
453
+ ```
454
+ import asyncio
455
+ import os
456
+ import shutil
457
+ from dotenv import load_dotenv
458
+ from clap import Agent, QdrantStore, OllamaEmbeddings, OllamaService
459
+ from clap.utils.rag_utils import chunk_text_by_fixed_size
460
+ from qdrant_client import models as qdrant_models # If needed for distance
461
+
462
+ load_dotenv()
463
+
464
+ OLLAMA_HOST = "http://localhost:11434"
465
+ EMBED_MODEL = "nomic-embed-text"
466
+ LLM_MODEL = "llama3"
467
+ DB_PATH = "./temp_rag_db_ollama_qdrant"
468
+ COLLECTION = "my_rag_docs"
469
+
470
+ async def main():
471
+ if os.path.exists(DB_PATH): shutil.rmtree(DB_PATH)
472
+
473
+ ollama_ef = OllamaEmbeddings(model_name=EMBED_MODEL, ollama_host=OLLAMA_HOST)
474
+ vector_store = await QdrantStore.create(
475
+ collection_name=COLLECTION,
476
+ embedding_function=ollama_ef,
477
+ path=DB_PATH, # For local file-based Qdrant
478
+ recreate_collection_if_exists=True
479
+ )
480
+
481
+ sample_texts = ["The sky is blue due to Rayleigh scattering.", "Large language models are powerful."]
482
+ chunks = [chunk for text in sample_texts for chunk in chunk_text_by_fixed_size(text, 100, 10)]
483
+ ids = [str(i) for i in range(len(chunks))] # Qdrant needs UUIDs; QdrantStore handles this
484
+
485
+ if chunks:
486
+ await vector_store.add_documents(documents=chunks, ids=ids)
487
+ print(f"Ingested {len(chunks)} chunks.")
488
+
489
+ ollama_llm_service = OllamaService(default_model=LLM_MODEL, base_url=f"{OLLAMA_HOST}/v1")
490
+ rag_agent = Agent(
491
+ name="RAGMaster",
492
+ backstory="I answer questions using provided documents.",
493
+ task_description="Why is the sky blue according to the documents?", # This becomes the User Query
494
+ llm_service=ollama_llm_service,
495
+ model=LLM_MODEL,
496
+ vector_store=vector_store
497
+ )
498
+
499
+ response = await rag_agent.run()
500
+ print(f"Query: {rag_agent.task_description}\nResponse:\n{response.get('output')}")
501
+
502
+ await vector_store.close()
503
+ await ollama_llm_service.close()
504
+ if os.path.exists(DB_PATH): shutil.rmtree(DB_PATH)
505
+
506
+ asyncio.run(main())
507
+ ```
508
+
509
+ ## Exploring Further
510
+
511
+
512
+ # Multi-Agent Teams: See examples/test_clap_comprehensive_suite.py and other team examples for setting up sequential or parallel agent workflows.
513
+
514
+ # MCP Integration: Check examples/test_clap_comprehensive_suite.py (ensure corresponding MCP servers from examples/simple_mcp.py etc. are running).
515
+
516
+ # Other LLM Services (Groq, Google Gemini , Ollama): Modify the Quick Starts to use GroqService or GoogleOpenAICompatService (ensure API keys are set).
517
+
518
+ # Different Vector Stores & Embedding Functions: Experiment with ChromaStore, QdrantStore, SentenceTransformerEmbeddings, FastEmbedEmbeddings, and OllamaEmbeddings as shown in the comprehensive test suite.
344
519
 
345
520
  License
346
521
  This project is licensed under the terms of the Apache License 2.0. See the LICENSE file for details.
522
+
523
+
524
+
@@ -0,0 +1,38 @@
1
+ clap/__init__.py,sha256=rxxESl-xpSZpM4ZIh-GvHYF74CkQdbe-dSLvhMC_2dQ,1069
2
+ clap/embedding/__init__.py,sha256=PqnqcSiA_JwEvn69g2DCQHdsffL2l4GNEKo0fAtCqbs,520
3
+ clap/embedding/base_embedding.py,sha256=0SYicQ-A-rSDqHoFK0IOrRQe0cisOl8OBnis6V43Chs,696
4
+ clap/embedding/fastembed_embedding.py,sha256=Xo2H7r45wNqN89WzI8InXtjahOXAcKDyHS0fYt_jtsc,2959
5
+ clap/embedding/ollama_embedding.py,sha256=s7IYFs4BuM114Md1cqxim5WzCwCjbEJ48wAZZOgR7KQ,3702
6
+ clap/embedding/sentence_transformer_embedding.py,sha256=0RAqGxDpjZVwerOLmVirqqnCwC07kHdfAPiy2fgOSCk,1798
7
+ clap/llm_services/__init__.py,sha256=js__xIlkbXBLd2q1trYKjATYWa4xLNgPwwJz2Flwi0s,532
8
+ clap/llm_services/base.py,sha256=BjSBVOtPMvPZJjI3HwSSI3OvVFyxd5HeOKLI9bN6P7M,2204
9
+ clap/llm_services/google_openai_compat_service.py,sha256=X5ehwQEv4DobXCBjhtuo2v365DUl-thDa9Gdc16L67k,4522
10
+ clap/llm_services/groq_service.py,sha256=HQoY3IpNHiOe0h6Gy0EMa1YsCUex0CvZ_MxApNMo8Ig,3206
11
+ clap/llm_services/ollama_service.py,sha256=LsoJhCw8TZVbfuJeHATo2uPpkXsOWOuuE78HAxTQ9RY,5326
12
+ clap/mcp_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ clap/mcp_client/client.py,sha256=wtbdAblkB8TNltvbC0pbthIFlpLoz_LsWj7sFnfaduw,8623
14
+ clap/multiagent_pattern/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ clap/multiagent_pattern/agent.py,sha256=6Og1ZujhAIzQ_JLzuhc4oh9TwJ_mTXl2q2ca302PE8g,8345
16
+ clap/multiagent_pattern/team.py,sha256=t8Xru3fVPblw75pyuPT1wmI3jlsrZHD_GKAW5APbpFg,7966
17
+ clap/react_pattern/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ clap/react_pattern/react_agent.py,sha256=Ron5zz-yn5EHmnXiL5G1sDYF0-3DCiTVujZ2RJI27JQ,23478
19
+ clap/tool_pattern/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ clap/tool_pattern/tool.py,sha256=7fHkLTPafsAWr_ECXZKpQz0zzqEAxSrQ56ns5oWcBSU,5964
21
+ clap/tool_pattern/tool_agent.py,sha256=VTQv9DNU16zgINZKVcX5oDw1lPfw5Y_8bUnW6wad2vE,14439
22
+ clap/tools/__init__.py,sha256=veMtEmjaY6aj_aUXNnPT_ijI2XM1DJU-y8dLqwx_t2s,293
23
+ clap/tools/email_tools.py,sha256=18aAlbjcSaOzpf9R3H-EGeRsqL5gdzmcJJcW619xOHU,9729
24
+ clap/tools/web_crawler.py,sha256=WdFbAKhUsUVveJimmLbzQ6k1BhOdMsg87FjL628HEKM,3542
25
+ clap/tools/web_search.py,sha256=YT0I1kPrdxMUst-dpsGqqF6aqxMgj3ACwiW_jN9Pu9s,985
26
+ clap/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ clap/utils/completions.py,sha256=xgHhXSJfPEOjQmeB2j6TpXEswMaDC87uCpIWXztSFBw,5613
28
+ clap/utils/extraction.py,sha256=x0wK66E37aB7UIq6ms4Juf8Ga2Zdm12kYXCzL3AiwTI,1295
29
+ clap/utils/logging.py,sha256=FfrOHXnVJYCgc3mepIMKcIcGSdSWkMpV7ccKPGFxFiM,727
30
+ clap/utils/rag_utils.py,sha256=Zy-C9l39tHrxYSluaDWcEl1g__uozu4jx5hEGC7h370,6455
31
+ clap/vector_stores/__init__.py,sha256=H3w5jLdQFbXArVgiidy4RlAalM8a6LAiMlAX0Z-2v7U,404
32
+ clap/vector_stores/base.py,sha256=nvk8J1oNG3OKFhJfxBGFyVeh9YxoDs9RkB_iOzPBm1w,2853
33
+ clap/vector_stores/chroma_store.py,sha256=knaZ91dC1M6T3yISLcn7PCQ5gZlh4xfeNLTVYD0k92E,7193
34
+ clap/vector_stores/qdrant_store.py,sha256=-SwMTb0yaGngpQ9AddDzDIt3x8GZevlFT-0FMkWD28I,9923
35
+ clap_agents-0.2.1.dist-info/METADATA,sha256=YE4izY_nqWF9OwbnOfS-bp-nk2a4y9jP1kbiiUk2QZk,27199
36
+ clap_agents-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ clap_agents-0.2.1.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
38
+ clap_agents-0.2.1.dist-info/RECORD,,