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.
- clap/__init__.py +13 -42
- clap/embedding/__init__.py +21 -0
- clap/embedding/base_embedding.py +28 -0
- clap/embedding/fastembed_embedding.py +75 -0
- clap/embedding/ollama_embedding.py +76 -0
- clap/embedding/sentence_transformer_embedding.py +44 -0
- clap/llm_services/__init__.py +15 -0
- clap/llm_services/base.py +3 -6
- clap/llm_services/google_openai_compat_service.py +1 -5
- clap/llm_services/groq_service.py +5 -13
- clap/llm_services/ollama_service.py +101 -0
- clap/mcp_client/client.py +7 -20
- clap/multiagent_pattern/agent.py +107 -34
- clap/multiagent_pattern/team.py +54 -29
- clap/react_pattern/react_agent.py +339 -126
- clap/tool_pattern/tool.py +94 -165
- clap/tool_pattern/tool_agent.py +171 -171
- clap/tools/__init__.py +1 -1
- clap/tools/email_tools.py +16 -19
- clap/tools/web_crawler.py +26 -18
- clap/utils/completions.py +35 -37
- clap/utils/extraction.py +3 -3
- clap/utils/rag_utils.py +183 -0
- clap/vector_stores/__init__.py +16 -0
- clap/vector_stores/base.py +85 -0
- clap/vector_stores/chroma_store.py +142 -0
- clap/vector_stores/qdrant_store.py +155 -0
- {clap_agents-0.1.1.dist-info → clap_agents-0.2.1.dist-info}/METADATA +201 -23
- clap_agents-0.2.1.dist-info/RECORD +38 -0
- clap_agents-0.1.1.dist-info/RECORD +0 -27
- {clap_agents-0.1.1.dist-info → clap_agents-0.2.1.dist-info}/WHEEL +0 -0
- {clap_agents-0.1.1.dist-info → clap_agents-0.2.1.dist-info}/licenses/LICENSE +0 -0
clap/tool_pattern/tool.py
CHANGED
@@ -1,229 +1,158 @@
|
|
1
|
-
|
2
1
|
import json
|
3
2
|
import inspect
|
4
|
-
import functools
|
5
|
-
from typing import Callable, Any
|
3
|
+
import functools
|
4
|
+
from typing import Callable, Any, Dict, List
|
6
5
|
import anyio
|
7
|
-
import jsonschema
|
6
|
+
import jsonschema
|
8
7
|
|
9
8
|
|
10
9
|
def get_fn_signature(fn: Callable) -> dict:
|
11
|
-
|
12
|
-
Generates the signature (schema) for a given function in JSON Schema format.
|
13
|
-
|
14
|
-
Args:
|
15
|
-
fn (Callable): The function whose signature needs to be extracted.
|
16
|
-
|
17
|
-
Returns:
|
18
|
-
dict: A dictionary representing the function's schema.
|
19
|
-
"""
|
20
|
-
|
10
|
+
|
21
11
|
type_mapping = {
|
22
|
-
"int": "integer",
|
23
|
-
"
|
24
|
-
"bool": "boolean",
|
25
|
-
"float": "number",
|
26
|
-
"list": "array", # Basic support for lists
|
27
|
-
"dict": "object", # Basic support for dicts
|
12
|
+
"int": "integer", "str": "string", "bool": "boolean",
|
13
|
+
"float": "number", "list": "array", "dict": "object",
|
28
14
|
}
|
29
|
-
|
30
15
|
parameters = {"type": "object", "properties": {}, "required": []}
|
31
16
|
sig = inspect.signature(fn)
|
32
|
-
|
33
17
|
for name, type_hint in fn.__annotations__.items():
|
34
|
-
if name == "return":
|
35
|
-
continue
|
18
|
+
if name == "return": continue
|
36
19
|
param_type_name = getattr(type_hint, "__name__", str(type_hint))
|
37
|
-
schema_type = type_mapping.get(param_type_name, "string")
|
38
|
-
|
20
|
+
schema_type = type_mapping.get(param_type_name.lower(), "string")
|
39
21
|
parameters["properties"][name] = {"type": schema_type}
|
40
|
-
|
41
22
|
if sig.parameters[name].default is inspect.Parameter.empty:
|
42
23
|
parameters["required"].append(name)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
fn_schema: dict = {
|
48
|
-
"type": "function",
|
49
|
-
"function": {
|
50
|
-
"name": fn.__name__,
|
51
|
-
"description": fn.__doc__,
|
52
|
-
"parameters": parameters,
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
return fn_schema
|
24
|
+
if not parameters.get("required"):
|
25
|
+
if "required" in parameters: del parameters["required"]
|
26
|
+
return {"type": "function", "function": {"name": fn.__name__, "description": fn.__doc__, "parameters": parameters}}
|
57
27
|
|
58
28
|
|
59
|
-
def
|
29
|
+
def validate_and_coerce_arguments(tool_call_args: Dict[str, Any], tool_schema: Dict[str, Any]) -> Dict[str, Any]: # Renamed for clarity
|
60
30
|
"""
|
61
|
-
Validates and
|
62
|
-
|
31
|
+
Validates and coerces arguments in the input dictionary based on the tool's JSON schema.
|
32
|
+
Then, performs final validation using jsonschema.
|
63
33
|
|
64
34
|
Args:
|
65
|
-
tool_call_args (
|
66
|
-
tool_schema (
|
35
|
+
tool_call_args (Dict[str, Any]): Arguments from LLM (often strings).
|
36
|
+
tool_schema (Dict[str, Any]): Tool's JSON schema.
|
67
37
|
|
68
38
|
Returns:
|
69
|
-
|
39
|
+
Dict[str, Any]: Arguments with values coerced to correct types.
|
70
40
|
|
71
41
|
Raises:
|
72
|
-
|
42
|
+
jsonschema.ValidationError: If final validation fails after coercion.
|
43
|
+
ValueError: If coercion fails for a required argument or types are incompatible.
|
73
44
|
"""
|
74
|
-
|
75
|
-
|
45
|
+
parameter_schema = tool_schema.get("function", {}).get("parameters", {})
|
46
|
+
properties = parameter_schema.get("properties", {})
|
47
|
+
required_args = parameter_schema.get("required", [])
|
48
|
+
|
49
|
+
coerced_args: Dict[str, Any] = {}
|
76
50
|
|
77
|
-
|
51
|
+
|
52
|
+
string_coercion_map = {
|
78
53
|
"integer": int,
|
54
|
+
"number": float,
|
55
|
+
"boolean": lambda x: x.lower() in ['true', '1', 'yes'] if isinstance(x, str) else bool(x),
|
79
56
|
"string": str,
|
80
|
-
|
81
|
-
"
|
82
|
-
"
|
83
|
-
|
57
|
+
|
58
|
+
"array": lambda x: json.loads(x) if isinstance(x, str) else x,
|
59
|
+
"object": lambda x: json.loads(x) if isinstance(x, str) else x,
|
60
|
+
}
|
61
|
+
|
62
|
+
expected_python_type_map = {
|
63
|
+
"integer": int, "number": (int, float), "boolean": bool,
|
64
|
+
"string": str, "array": list, "object": dict,
|
84
65
|
}
|
85
66
|
|
86
67
|
for arg_name, arg_value in tool_call_args.items():
|
87
68
|
prop_schema = properties.get(arg_name)
|
88
69
|
if not prop_schema:
|
89
|
-
|
90
|
-
|
91
|
-
validated_args[arg_name] = arg_value # Pass through unknown args for now
|
70
|
+
|
71
|
+
coerced_args[arg_name] = arg_value
|
92
72
|
continue
|
93
73
|
|
94
|
-
|
95
|
-
|
74
|
+
expected_schema_type = prop_schema.get("type")
|
75
|
+
python_type_tuple = expected_python_type_map.get(expected_schema_type)
|
76
|
+
|
77
|
+
|
78
|
+
if python_type_tuple and isinstance(arg_value, python_type_tuple):
|
79
|
+
coerced_args[arg_name] = arg_value
|
80
|
+
continue
|
96
81
|
|
97
|
-
|
82
|
+
|
83
|
+
coercer = string_coercion_map.get(expected_schema_type)
|
84
|
+
if coercer:
|
98
85
|
try:
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
except json.JSONDecodeError:
|
114
|
-
raise ValueError(f"Argument '{arg_name}' with value '{arg_value}' is not valid JSON for type '{expected_type_name}'.")
|
115
|
-
else:
|
116
|
-
validated_args[arg_name] = expected_type(arg_value)
|
86
|
+
coerced_value = coercer(arg_value)
|
87
|
+
|
88
|
+
if expected_schema_type in ["array", "object"] and python_type_tuple:
|
89
|
+
if not isinstance(coerced_value, python_type_tuple):
|
90
|
+
raise ValueError(f"Decoded JSON for '{arg_name}' is not expected type '{expected_schema_type}'.")
|
91
|
+
coerced_args[arg_name] = coerced_value
|
92
|
+
except (ValueError, TypeError, json.JSONDecodeError) as e:
|
93
|
+
|
94
|
+
|
95
|
+
if arg_name in required_args:
|
96
|
+
raise ValueError(
|
97
|
+
f"Error coercing required argument '{arg_name}' with value '{arg_value}' "
|
98
|
+
f"to type '{expected_schema_type}': {e}"
|
99
|
+
)
|
117
100
|
else:
|
118
|
-
|
119
|
-
validated_args[arg_name] = arg_value
|
120
|
-
except (ValueError, TypeError) as e:
|
121
|
-
raise ValueError(f"Error converting argument '{arg_name}' with value '{arg_value}' to type '{expected_type_name}': {e}")
|
101
|
+
coerced_args[arg_name] = arg_value
|
122
102
|
else:
|
123
|
-
|
124
|
-
|
103
|
+
|
104
|
+
coerced_args[arg_name] = arg_value
|
125
105
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
106
|
+
|
107
|
+
try:
|
108
|
+
jsonschema.validate(instance=coerced_args, schema=parameter_schema)
|
109
|
+
except jsonschema.ValidationError as e:
|
110
|
+
|
111
|
+
raise
|
131
112
|
|
113
|
+
return coerced_args
|
132
114
|
|
133
|
-
return validated_args
|
134
115
|
|
135
116
|
class Tool:
|
136
|
-
"""
|
137
|
-
A class representing a tool that wraps a callable and its schema.
|
138
|
-
Handles both synchronous and asynchronous functions.
|
139
|
-
|
140
|
-
Attributes:
|
141
|
-
name (str): The name of the tool (function).
|
142
|
-
fn (Callable): The function that the tool represents (can be sync or async).
|
143
|
-
fn_schema (dict): Dictionary representing the function's schema in JSON Schema format.
|
144
|
-
fn_signature (str): JSON string representation of the function's signature (legacy, kept for potential compatibility).
|
145
|
-
"""
|
146
|
-
|
147
117
|
def __init__(self, name: str, fn: Callable, fn_schema: dict):
|
148
118
|
self.name = name
|
149
119
|
self.fn = fn
|
150
120
|
self.fn_schema = fn_schema
|
151
|
-
self.fn_signature = json.dumps(fn_schema)
|
121
|
+
# self.fn_signature = json.dumps(fn_schema)
|
152
122
|
|
153
123
|
def __str__(self):
|
154
124
|
return json.dumps(self.fn_schema, indent=2)
|
155
125
|
|
156
|
-
async def run(self, **kwargs) -> Any:
|
126
|
+
async def run(self, **kwargs: Any) -> Any:
|
157
127
|
"""
|
158
|
-
Executes the tool
|
159
|
-
Validates arguments against the tool's JSON schema before execution.
|
160
|
-
Handles both sync and async tool functions appropriately.
|
161
|
-
|
162
|
-
Args:
|
163
|
-
**kwargs: Keyword arguments provided for the tool call.
|
164
|
-
|
165
|
-
Returns:
|
166
|
-
The result of the function call, or an error string.
|
128
|
+
Executes the tool, validating and coercing arguments first.
|
167
129
|
"""
|
168
|
-
|
169
|
-
|
170
|
-
# --- Use jsonschema for validation ---
|
130
|
+
function_name = self.fn_schema.get("function", {}).get("name", "unknown_tool")
|
171
131
|
try:
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
validated_kwargs = kwargs # Use original kwargs after validation passes
|
182
|
-
|
183
|
-
except jsonschema.ValidationError as e:
|
184
|
-
print(f"Argument validation failed for tool {self.name}: {e.message}")
|
185
|
-
return f"Error: Invalid arguments provided - {e.message}"
|
186
|
-
except Exception as e: # Catch other potential validation setup errors
|
187
|
-
print(f"An unexpected error occurred during argument validation for tool {self.name}: {e}")
|
188
|
-
return f"Error: Argument validation failed."
|
189
|
-
# --- End jsonschema validation ---
|
190
|
-
|
191
|
-
# --- Execute the function (sync or async) ---
|
132
|
+
|
133
|
+
validated_and_coerced_kwargs = validate_and_coerce_arguments(kwargs, self.fn_schema)
|
134
|
+
except (jsonschema.ValidationError, ValueError) as e:
|
135
|
+
|
136
|
+
return f"Error: Invalid arguments for tool {function_name} - {str(e)}"
|
137
|
+
except Exception as e:
|
138
|
+
return f"Error: Unexpected issue with arguments for tool {function_name}."
|
139
|
+
|
140
|
+
|
192
141
|
try:
|
193
142
|
if inspect.iscoroutinefunction(self.fn):
|
194
|
-
return await self.fn(**
|
143
|
+
return await self.fn(**validated_and_coerced_kwargs)
|
195
144
|
else:
|
196
|
-
|
145
|
+
|
146
|
+
func_with_args = functools.partial(self.fn, **validated_and_coerced_kwargs)
|
197
147
|
return await anyio.to_thread.run_sync(func_with_args)
|
198
148
|
except Exception as e:
|
199
|
-
|
200
|
-
|
201
|
-
# Consider logging traceback here
|
202
|
-
return f"Error executing tool: {e}"
|
203
|
-
|
149
|
+
|
150
|
+
return f"Error executing tool {function_name}: {e}"
|
204
151
|
|
205
152
|
|
206
153
|
def tool(fn: Callable):
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
Args:
|
212
|
-
fn (Callable): The function to be wrapped.
|
213
|
-
|
214
|
-
Returns:
|
215
|
-
Tool: A Tool object containing the function, its name, and its schema.
|
216
|
-
"""
|
217
|
-
|
218
|
-
def wrapper():
|
219
|
-
fn_schema = get_fn_signature(fn)
|
220
|
-
if not fn_schema or 'function' not in fn_schema or 'name' not in fn_schema['function']:
|
221
|
-
raise ValueError(f"Could not generate valid schema for function {fn.__name__}")
|
222
|
-
return Tool(
|
223
|
-
name=fn_schema["function"]["name"],
|
224
|
-
fn=fn,
|
225
|
-
fn_schema=fn_schema
|
226
|
-
)
|
227
|
-
|
228
|
-
return wrapper()
|
154
|
+
fn_schema = get_fn_signature(fn)
|
155
|
+
if not fn_schema or 'function' not in fn_schema or 'name' not in fn_schema['function']:
|
156
|
+
raise ValueError(f"Could not generate valid schema for function {fn.__name__}")
|
157
|
+
return Tool(name=fn_schema["function"]["name"], fn=fn, fn_schema=fn_schema)
|
229
158
|
|