vectara-agentic 0.2.12__py3-none-any.whl → 0.2.14__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.
Potentially problematic release.
This version of vectara-agentic might be problematic. Click here for more details.
- tests/test_agent.py +18 -1
- tests/test_agent_planning.py +0 -9
- tests/test_agent_type.py +40 -0
- tests/test_groq.py +120 -0
- tests/test_tools.py +176 -42
- tests/test_vectara_llms.py +66 -0
- vectara_agentic/_prompts.py +6 -8
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +304 -79
- vectara_agentic/llm_utils.py +174 -0
- vectara_agentic/tool_utils.py +513 -0
- vectara_agentic/tools.py +73 -452
- vectara_agentic/tools_catalog.py +2 -1
- vectara_agentic/utils.py +25 -150
- {vectara_agentic-0.2.12.dist-info → vectara_agentic-0.2.14.dist-info}/METADATA +355 -236
- vectara_agentic-0.2.14.dist-info/RECORD +33 -0
- {vectara_agentic-0.2.12.dist-info → vectara_agentic-0.2.14.dist-info}/WHEEL +1 -1
- vectara_agentic-0.2.12.dist-info/RECORD +0 -29
- {vectara_agentic-0.2.12.dist-info → vectara_agentic-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.12.dist-info → vectara_agentic-0.2.14.dist-info}/top_level.txt +0 -0
vectara_agentic/tools.py
CHANGED
|
@@ -8,31 +8,35 @@ import importlib
|
|
|
8
8
|
import os
|
|
9
9
|
import asyncio
|
|
10
10
|
|
|
11
|
-
from typing import Callable, List, Dict, Any, Optional, Union
|
|
12
|
-
from pydantic import BaseModel, Field
|
|
13
|
-
from pydantic_core import PydanticUndefined
|
|
11
|
+
from typing import Callable, List, Dict, Any, Optional, Union
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
14
13
|
|
|
15
14
|
from llama_index.core.tools import FunctionTool
|
|
16
|
-
from llama_index.core.tools.function_tool import AsyncCallable
|
|
17
15
|
from llama_index.indices.managed.vectara import VectaraIndex
|
|
18
16
|
from llama_index.core.utilities.sql_wrapper import SQLDatabase
|
|
19
|
-
from llama_index.core.tools.types import
|
|
20
|
-
from llama_index.core.workflow.context import Context
|
|
17
|
+
from llama_index.core.tools.types import ToolOutput
|
|
21
18
|
|
|
22
19
|
from .types import ToolType
|
|
23
20
|
from .tools_catalog import ToolsCatalog, get_bad_topics
|
|
24
21
|
from .db_tools import DatabaseTools
|
|
25
|
-
from .utils import
|
|
22
|
+
from .utils import summarize_documents, is_float
|
|
26
23
|
from .agent_config import AgentConfig
|
|
24
|
+
from .tool_utils import (
|
|
25
|
+
_create_tool_from_dynamic_function,
|
|
26
|
+
_build_filter_string,
|
|
27
|
+
VectaraTool
|
|
28
|
+
)
|
|
27
29
|
|
|
28
30
|
LI_packages = {
|
|
29
31
|
"yahoo_finance": ToolType.QUERY,
|
|
30
32
|
"arxiv": ToolType.QUERY,
|
|
31
33
|
"tavily_research": ToolType.QUERY,
|
|
32
34
|
"exa": ToolType.QUERY,
|
|
33
|
-
"
|
|
35
|
+
"brave_search": ToolType.QUERY,
|
|
36
|
+
"bing_search": ToolType.QUERY,
|
|
34
37
|
"neo4j": ToolType.QUERY,
|
|
35
38
|
"kuzu": ToolType.QUERY,
|
|
39
|
+
"wikipedia": ToolType.QUERY,
|
|
36
40
|
"google": {
|
|
37
41
|
"GmailToolSpec": {
|
|
38
42
|
"load_data": ToolType.QUERY,
|
|
@@ -58,427 +62,6 @@ LI_packages = {
|
|
|
58
62
|
},
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
class VectaraToolMetadata(ToolMetadata):
|
|
63
|
-
"""
|
|
64
|
-
A subclass of ToolMetadata adding the tool_type attribute.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
tool_type: ToolType
|
|
68
|
-
|
|
69
|
-
def __init__(self, tool_type: ToolType, **kwargs):
|
|
70
|
-
super().__init__(**kwargs)
|
|
71
|
-
self.tool_type = tool_type
|
|
72
|
-
|
|
73
|
-
def __repr__(self) -> str:
|
|
74
|
-
"""
|
|
75
|
-
Returns a string representation of the VectaraToolMetadata object, including the tool_type attribute.
|
|
76
|
-
"""
|
|
77
|
-
base_repr = super().__repr__()
|
|
78
|
-
return f"{base_repr}, tool_type={self.tool_type}"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class VectaraTool(FunctionTool):
|
|
82
|
-
"""
|
|
83
|
-
A subclass of FunctionTool adding the tool_type attribute.
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
def __init__(
|
|
87
|
-
self,
|
|
88
|
-
tool_type: ToolType,
|
|
89
|
-
metadata: ToolMetadata,
|
|
90
|
-
fn: Optional[Callable[..., Any]] = None,
|
|
91
|
-
async_fn: Optional[AsyncCallable] = None,
|
|
92
|
-
) -> None:
|
|
93
|
-
metadata_dict = (
|
|
94
|
-
metadata.dict() if hasattr(metadata, "dict") else metadata.__dict__
|
|
95
|
-
)
|
|
96
|
-
vm = VectaraToolMetadata(tool_type=tool_type, **metadata_dict)
|
|
97
|
-
super().__init__(fn, vm, async_fn)
|
|
98
|
-
|
|
99
|
-
@classmethod
|
|
100
|
-
def from_defaults(
|
|
101
|
-
cls,
|
|
102
|
-
fn: Optional[Callable[..., Any]] = None,
|
|
103
|
-
name: Optional[str] = None,
|
|
104
|
-
description: Optional[str] = None,
|
|
105
|
-
return_direct: bool = False,
|
|
106
|
-
fn_schema: Optional[Type[BaseModel]] = None,
|
|
107
|
-
async_fn: Optional[AsyncCallable] = None,
|
|
108
|
-
tool_metadata: Optional[ToolMetadata] = None,
|
|
109
|
-
callback: Optional[Callable[[Any], Any]] = None,
|
|
110
|
-
async_callback: Optional[AsyncCallable] = None,
|
|
111
|
-
tool_type: ToolType = ToolType.QUERY,
|
|
112
|
-
) -> "VectaraTool":
|
|
113
|
-
tool = FunctionTool.from_defaults(
|
|
114
|
-
fn,
|
|
115
|
-
name,
|
|
116
|
-
description,
|
|
117
|
-
return_direct,
|
|
118
|
-
fn_schema,
|
|
119
|
-
async_fn,
|
|
120
|
-
tool_metadata,
|
|
121
|
-
callback,
|
|
122
|
-
async_callback,
|
|
123
|
-
)
|
|
124
|
-
vectara_tool = cls(
|
|
125
|
-
tool_type=tool_type,
|
|
126
|
-
fn=tool.fn,
|
|
127
|
-
metadata=tool.metadata,
|
|
128
|
-
async_fn=tool.async_fn,
|
|
129
|
-
)
|
|
130
|
-
return vectara_tool
|
|
131
|
-
|
|
132
|
-
def __str__(self) -> str:
|
|
133
|
-
return f"Tool(name={self.metadata.name}, " f"Tool metadata={self.metadata})"
|
|
134
|
-
|
|
135
|
-
def __repr__(self) -> str:
|
|
136
|
-
return str(self)
|
|
137
|
-
|
|
138
|
-
def __eq__(self, other):
|
|
139
|
-
if not isinstance(other, VectaraTool):
|
|
140
|
-
return False
|
|
141
|
-
|
|
142
|
-
if self.metadata.tool_type != other.metadata.tool_type:
|
|
143
|
-
return False
|
|
144
|
-
|
|
145
|
-
if self.metadata.name != other.metadata.name:
|
|
146
|
-
return False
|
|
147
|
-
|
|
148
|
-
# If schema is a dict-like object, compare the dict representation
|
|
149
|
-
try:
|
|
150
|
-
# Try to get schema as dict if possible
|
|
151
|
-
if hasattr(self.metadata.fn_schema, "schema"):
|
|
152
|
-
self_schema = self.metadata.fn_schema.schema
|
|
153
|
-
other_schema = other.metadata.fn_schema.schema
|
|
154
|
-
|
|
155
|
-
# Compare only properties and required fields
|
|
156
|
-
self_props = self_schema.get("properties", {})
|
|
157
|
-
other_props = other_schema.get("properties", {})
|
|
158
|
-
|
|
159
|
-
self_required = self_schema.get("required", [])
|
|
160
|
-
other_required = other_schema.get("required", [])
|
|
161
|
-
|
|
162
|
-
return self_props.keys() == other_props.keys() and set(
|
|
163
|
-
self_required
|
|
164
|
-
) == set(other_required)
|
|
165
|
-
except Exception:
|
|
166
|
-
# If any exception occurs during schema comparison, fall back to name comparison
|
|
167
|
-
pass
|
|
168
|
-
|
|
169
|
-
return True
|
|
170
|
-
|
|
171
|
-
def call(
|
|
172
|
-
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
173
|
-
) -> ToolOutput:
|
|
174
|
-
try:
|
|
175
|
-
return super().call(*args, ctx=ctx, **kwargs)
|
|
176
|
-
except TypeError as e:
|
|
177
|
-
sig = inspect.signature(self.metadata.fn_schema)
|
|
178
|
-
valid_parameters = list(sig.parameters.keys())
|
|
179
|
-
params_str = ", ".join(valid_parameters)
|
|
180
|
-
|
|
181
|
-
err_output = ToolOutput(
|
|
182
|
-
tool_name=self.metadata.name,
|
|
183
|
-
content=(
|
|
184
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
|
|
185
|
-
f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
|
|
186
|
-
),
|
|
187
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
188
|
-
raw_output={"response": str(e)},
|
|
189
|
-
)
|
|
190
|
-
return err_output
|
|
191
|
-
except Exception as e:
|
|
192
|
-
err_output = ToolOutput(
|
|
193
|
-
tool_name=self.metadata.name,
|
|
194
|
-
content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
|
|
195
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
196
|
-
raw_output={"response": str(e)},
|
|
197
|
-
)
|
|
198
|
-
return err_output
|
|
199
|
-
|
|
200
|
-
async def acall(
|
|
201
|
-
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
202
|
-
) -> ToolOutput:
|
|
203
|
-
try:
|
|
204
|
-
return await super().acall(*args, ctx=ctx, **kwargs)
|
|
205
|
-
except TypeError as e:
|
|
206
|
-
sig = inspect.signature(self.metadata.fn_schema)
|
|
207
|
-
valid_parameters = list(sig.parameters.keys())
|
|
208
|
-
params_str = ", ".join(valid_parameters)
|
|
209
|
-
|
|
210
|
-
err_output = ToolOutput(
|
|
211
|
-
tool_name=self.metadata.name,
|
|
212
|
-
content=(
|
|
213
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
|
|
214
|
-
f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
|
|
215
|
-
),
|
|
216
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
217
|
-
raw_output={"response": str(e)},
|
|
218
|
-
)
|
|
219
|
-
return err_output
|
|
220
|
-
except Exception as e:
|
|
221
|
-
err_output = ToolOutput(
|
|
222
|
-
tool_name=self.metadata.name,
|
|
223
|
-
content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
|
|
224
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
225
|
-
raw_output={"response": str(e)},
|
|
226
|
-
)
|
|
227
|
-
return err_output
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def _create_tool_from_dynamic_function(
|
|
231
|
-
function: Callable[..., ToolOutput],
|
|
232
|
-
tool_name: str,
|
|
233
|
-
tool_description: str,
|
|
234
|
-
base_params_model: Type[BaseModel], # Now a Pydantic BaseModel
|
|
235
|
-
tool_args_schema: Type[BaseModel],
|
|
236
|
-
) -> VectaraTool:
|
|
237
|
-
fields = {}
|
|
238
|
-
base_params = []
|
|
239
|
-
|
|
240
|
-
# Create inspect.Parameter objects for base_params_model fields.
|
|
241
|
-
for param_name, model_field in base_params_model.model_fields.items():
|
|
242
|
-
field_type = base_params_model.__annotations__.get(
|
|
243
|
-
param_name, str
|
|
244
|
-
) # default to str if not found
|
|
245
|
-
default_value = (
|
|
246
|
-
model_field.default
|
|
247
|
-
if model_field.default is not None
|
|
248
|
-
else inspect.Parameter.empty
|
|
249
|
-
)
|
|
250
|
-
base_params.append(
|
|
251
|
-
inspect.Parameter(
|
|
252
|
-
param_name,
|
|
253
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
254
|
-
default=default_value,
|
|
255
|
-
annotation=field_type,
|
|
256
|
-
)
|
|
257
|
-
)
|
|
258
|
-
fields[param_name] = (
|
|
259
|
-
field_type,
|
|
260
|
-
model_field.default if model_field.default is not None else ...,
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
# Add tool_args_schema fields to the fields dict if not already included.
|
|
264
|
-
# Also add them to the function signature by creating new inspect.Parameter objects.
|
|
265
|
-
for field_name, field_info in tool_args_schema.model_fields.items():
|
|
266
|
-
if field_name not in fields:
|
|
267
|
-
default_value = (
|
|
268
|
-
field_info.default if field_info.default is not None else ...
|
|
269
|
-
)
|
|
270
|
-
field_type = tool_args_schema.__annotations__.get(field_name, None)
|
|
271
|
-
fields[field_name] = (field_type, default_value)
|
|
272
|
-
# Append these fields to the signature.
|
|
273
|
-
base_params.append(
|
|
274
|
-
inspect.Parameter(
|
|
275
|
-
field_name,
|
|
276
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
277
|
-
default=(
|
|
278
|
-
default_value
|
|
279
|
-
if default_value is not ...
|
|
280
|
-
else inspect.Parameter.empty
|
|
281
|
-
),
|
|
282
|
-
annotation=field_type,
|
|
283
|
-
)
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
# Create the dynamic schema with both base_params_model and tool_args_schema fields.
|
|
287
|
-
fn_schema = create_model(f"{tool_name}_schema", **fields)
|
|
288
|
-
|
|
289
|
-
# Combine parameters into a function signature.
|
|
290
|
-
all_params = base_params[:] # Now all_params contains parameters from both models.
|
|
291
|
-
required_params = [p for p in all_params if p.default is inspect.Parameter.empty]
|
|
292
|
-
optional_params = [
|
|
293
|
-
p for p in all_params if p.default is not inspect.Parameter.empty
|
|
294
|
-
]
|
|
295
|
-
function.__signature__ = inspect.Signature(required_params + optional_params)
|
|
296
|
-
function.__annotations__["return"] = dict[str, Any]
|
|
297
|
-
function.__name__ = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
298
|
-
|
|
299
|
-
# Build a docstring using parameter descriptions from the BaseModels.
|
|
300
|
-
params_str = ",\n ".join(
|
|
301
|
-
f"{p.name}: {p.annotation.__name__ if hasattr(p.annotation, '__name__') else p.annotation}"
|
|
302
|
-
for p in all_params
|
|
303
|
-
)
|
|
304
|
-
signature_line = f"{tool_name}(\n {params_str}\n) -> dict[str, Any]"
|
|
305
|
-
doc_lines = [
|
|
306
|
-
signature_line,
|
|
307
|
-
"",
|
|
308
|
-
tool_description.strip(),
|
|
309
|
-
"",
|
|
310
|
-
"Args:",
|
|
311
|
-
]
|
|
312
|
-
for param in all_params:
|
|
313
|
-
description = ""
|
|
314
|
-
if param.name in base_params_model.model_fields:
|
|
315
|
-
description = base_params_model.model_fields[param.name].description
|
|
316
|
-
elif param.name in tool_args_schema.model_fields:
|
|
317
|
-
description = tool_args_schema.model_fields[param.name].description
|
|
318
|
-
if not description:
|
|
319
|
-
description = "No description provided."
|
|
320
|
-
type_name = (
|
|
321
|
-
param.annotation.__name__
|
|
322
|
-
if hasattr(param.annotation, "__name__")
|
|
323
|
-
else str(param.annotation)
|
|
324
|
-
)
|
|
325
|
-
default_text = (
|
|
326
|
-
f", default={param.default!r}"
|
|
327
|
-
if param.default is not inspect.Parameter.empty
|
|
328
|
-
else ""
|
|
329
|
-
)
|
|
330
|
-
doc_lines.append(f" {param.name} ({type_name}){default_text}: {description}")
|
|
331
|
-
doc_lines.append("")
|
|
332
|
-
doc_lines.append("Returns:")
|
|
333
|
-
return_desc = getattr(
|
|
334
|
-
function, "__return_description__", "A dictionary containing the result data."
|
|
335
|
-
)
|
|
336
|
-
doc_lines.append(f" dict[str, Any]: {return_desc}")
|
|
337
|
-
function.__doc__ = "\n".join(doc_lines)
|
|
338
|
-
|
|
339
|
-
tool = VectaraTool.from_defaults(
|
|
340
|
-
fn=function,
|
|
341
|
-
name=tool_name,
|
|
342
|
-
description=function.__doc__,
|
|
343
|
-
fn_schema=fn_schema,
|
|
344
|
-
tool_type=ToolType.QUERY,
|
|
345
|
-
)
|
|
346
|
-
return tool
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def _build_filter_string(
|
|
350
|
-
kwargs: Dict[str, Any], tool_args_type: Dict[str, dict], fixed_filter: str
|
|
351
|
-
) -> str:
|
|
352
|
-
"""
|
|
353
|
-
Build filter string for Vectara from kwargs
|
|
354
|
-
"""
|
|
355
|
-
filter_parts = []
|
|
356
|
-
comparison_operators = [">=", "<=", "!=", ">", "<", "="]
|
|
357
|
-
numeric_only_ops = {">", "<", ">=", "<="}
|
|
358
|
-
|
|
359
|
-
for key, value in kwargs.items():
|
|
360
|
-
if value is None or value == "":
|
|
361
|
-
continue
|
|
362
|
-
|
|
363
|
-
# Determine the prefix for the key. Valid values are "doc" or "part"
|
|
364
|
-
# default to 'doc' if not specified
|
|
365
|
-
tool_args_dict = tool_args_type.get(key, {"type": "doc", "is_list": False})
|
|
366
|
-
prefix = tool_args_dict.get(key, "doc")
|
|
367
|
-
is_list = tool_args_dict.get("is_list", False)
|
|
368
|
-
|
|
369
|
-
if prefix not in ["doc", "part"]:
|
|
370
|
-
raise ValueError(
|
|
371
|
-
f'Unrecognized prefix {prefix}. Please make sure to use either "doc" or "part" for the prefix.'
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
if value is PydanticUndefined:
|
|
375
|
-
raise ValueError(
|
|
376
|
-
f"Value of argument {key} is undefined, and this is invalid."
|
|
377
|
-
"Please form proper arguments and try again."
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
# value of the argument
|
|
381
|
-
val_str = str(value).strip()
|
|
382
|
-
|
|
383
|
-
# Special handling for range operator
|
|
384
|
-
if val_str.startswith(("[", "(")) and val_str.endswith(("]", ")")):
|
|
385
|
-
# Extract the boundary types
|
|
386
|
-
start_inclusive = val_str.startswith("[")
|
|
387
|
-
end_inclusive = val_str.endswith("]")
|
|
388
|
-
|
|
389
|
-
# Remove the boundaries and strip whitespace
|
|
390
|
-
val_str = val_str[1:-1].strip()
|
|
391
|
-
|
|
392
|
-
if "," in val_str:
|
|
393
|
-
val_str = val_str.split(",")
|
|
394
|
-
if len(val_str) != 2:
|
|
395
|
-
raise ValueError(
|
|
396
|
-
f"Range operator requires two values for {key}: {value}"
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
# Validate both bounds as numeric or empty (for unbounded ranges)
|
|
400
|
-
start_val, end_val = val_str[0].strip(), val_str[1].strip()
|
|
401
|
-
if start_val and not (start_val.isdigit() or is_float(start_val)):
|
|
402
|
-
raise ValueError(
|
|
403
|
-
f"Range operator requires numeric operands for {key}: {value}"
|
|
404
|
-
)
|
|
405
|
-
if end_val and not (end_val.isdigit() or is_float(end_val)):
|
|
406
|
-
raise ValueError(
|
|
407
|
-
f"Range operator requires numeric operands for {key}: {value}"
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
# Build the SQL condition
|
|
411
|
-
range_conditions = []
|
|
412
|
-
if start_val:
|
|
413
|
-
operator = ">=" if start_inclusive else ">"
|
|
414
|
-
range_conditions.append(f"{prefix}.{key} {operator} {start_val}")
|
|
415
|
-
if end_val:
|
|
416
|
-
operator = "<=" if end_inclusive else "<"
|
|
417
|
-
range_conditions.append(f"{prefix}.{key} {operator} {end_val}")
|
|
418
|
-
|
|
419
|
-
# Join the range conditions with AND
|
|
420
|
-
filter_parts.append("( " + " AND ".join(range_conditions) + " )")
|
|
421
|
-
continue
|
|
422
|
-
|
|
423
|
-
raise ValueError(f"Range operator requires two values for {key}: {value}")
|
|
424
|
-
|
|
425
|
-
# Check if value contains a known comparison operator at the start
|
|
426
|
-
matched_operator = None
|
|
427
|
-
for op in comparison_operators:
|
|
428
|
-
if val_str.startswith(op):
|
|
429
|
-
matched_operator = op
|
|
430
|
-
break
|
|
431
|
-
|
|
432
|
-
# Break down operator from value
|
|
433
|
-
# e.g. val_str = ">2022" --> operator = ">", rhs = "2022"
|
|
434
|
-
if matched_operator:
|
|
435
|
-
rhs = val_str[len(matched_operator) :].strip()
|
|
436
|
-
|
|
437
|
-
if matched_operator in numeric_only_ops:
|
|
438
|
-
# Must be numeric
|
|
439
|
-
if not (rhs.isdigit() or is_float(rhs)):
|
|
440
|
-
raise ValueError(
|
|
441
|
-
f"Operator {matched_operator} requires a numeric operand for {key}: {val_str}"
|
|
442
|
-
)
|
|
443
|
-
filter_parts.append(f"{prefix}.{key}{matched_operator}{rhs}")
|
|
444
|
-
else:
|
|
445
|
-
# = and != operators can be numeric or string
|
|
446
|
-
if rhs.isdigit() or is_float(rhs):
|
|
447
|
-
filter_parts.append(f"{prefix}.{key}{matched_operator}{rhs}")
|
|
448
|
-
elif rhs.lower() in ["true", "false"]:
|
|
449
|
-
filter_parts.append(
|
|
450
|
-
f"{prefix}.{key}{matched_operator}{rhs.lower()}"
|
|
451
|
-
)
|
|
452
|
-
else:
|
|
453
|
-
# For string operands, wrap them in quotes
|
|
454
|
-
filter_parts.append(f"{prefix}.{key}{matched_operator}'{rhs}'")
|
|
455
|
-
else:
|
|
456
|
-
if val_str.isdigit() or is_float(val_str):
|
|
457
|
-
if is_list:
|
|
458
|
-
filter_parts.append(f"({val_str} IN {prefix}.{key})")
|
|
459
|
-
else:
|
|
460
|
-
filter_parts.append(f"{prefix}.{key}={val_str}")
|
|
461
|
-
elif val_str.lower() in ["true", "false"]:
|
|
462
|
-
# This is to handle boolean values.
|
|
463
|
-
# This is not complete solution - the best solution would be to test if the field is boolean
|
|
464
|
-
# That can be done after we move to APIv2
|
|
465
|
-
if is_list:
|
|
466
|
-
filter_parts.append(f"({val_str.lower()} IN {prefix}.{key})")
|
|
467
|
-
else:
|
|
468
|
-
filter_parts.append(f"{prefix}.{key}={val_str.lower()}")
|
|
469
|
-
else:
|
|
470
|
-
if is_list:
|
|
471
|
-
filter_parts.append(f"('{val_str}' IN {prefix}.{key})")
|
|
472
|
-
else:
|
|
473
|
-
filter_parts.append(f"{prefix}.{key}='{val_str}'")
|
|
474
|
-
|
|
475
|
-
filter_str = " AND ".join(filter_parts)
|
|
476
|
-
if fixed_filter and filter_str:
|
|
477
|
-
return f"({fixed_filter}) AND ({filter_str})"
|
|
478
|
-
else:
|
|
479
|
-
return fixed_filter or filter_str
|
|
480
|
-
|
|
481
|
-
|
|
482
65
|
class VectaraToolFactory:
|
|
483
66
|
"""
|
|
484
67
|
A factory class for creating Vectara RAG tools.
|
|
@@ -488,25 +71,29 @@ class VectaraToolFactory:
|
|
|
488
71
|
self,
|
|
489
72
|
vectara_corpus_key: str = str(os.environ.get("VECTARA_CORPUS_KEY", "")),
|
|
490
73
|
vectara_api_key: str = str(os.environ.get("VECTARA_API_KEY", "")),
|
|
74
|
+
compact_docstring: bool = False,
|
|
491
75
|
) -> None:
|
|
492
76
|
"""
|
|
493
77
|
Initialize the VectaraToolFactory
|
|
494
78
|
Args:
|
|
495
79
|
vectara_corpus_key (str): The Vectara corpus key (or comma separated list of keys).
|
|
496
80
|
vectara_api_key (str): The Vectara API key.
|
|
81
|
+
compact_docstring (bool): Whether to use a compact docstring format for tools
|
|
82
|
+
This is useful if OpenAI complains on the 1024 token limit.
|
|
497
83
|
"""
|
|
498
84
|
self.vectara_corpus_key = vectara_corpus_key
|
|
499
85
|
self.vectara_api_key = vectara_api_key
|
|
500
86
|
self.num_corpora = len(vectara_corpus_key.split(","))
|
|
501
|
-
self.
|
|
502
|
-
self.max_cache_size = 128
|
|
87
|
+
self.compact_docstring = compact_docstring
|
|
503
88
|
|
|
504
89
|
def create_search_tool(
|
|
505
90
|
self,
|
|
506
91
|
tool_name: str,
|
|
507
92
|
tool_description: str,
|
|
508
|
-
tool_args_schema: type[BaseModel],
|
|
93
|
+
tool_args_schema: type[BaseModel] = None,
|
|
509
94
|
tool_args_type: Dict[str, str] = {},
|
|
95
|
+
summarize_docs: Optional[bool] = None,
|
|
96
|
+
summarize_llm_name: Optional[str] = None,
|
|
510
97
|
fixed_filter: str = "",
|
|
511
98
|
lambda_val: Union[List[float], float] = 0.005,
|
|
512
99
|
semantics: Union[List[str] | str] = "default",
|
|
@@ -532,7 +119,7 @@ class VectaraToolFactory:
|
|
|
532
119
|
Args:
|
|
533
120
|
tool_name (str): The name of the tool.
|
|
534
121
|
tool_description (str): The description of the tool.
|
|
535
|
-
tool_args_schema (BaseModel): The schema for the tool arguments.
|
|
122
|
+
tool_args_schema (BaseModel, optional): The schema for the tool arguments.
|
|
536
123
|
tool_args_type (Dict[str, str], optional): The type of each argument (doc or part).
|
|
537
124
|
fixed_filter (str, optional): A fixed Vectara filter condition to apply to all queries.
|
|
538
125
|
lambda_val (Union[List[float] | float], optional): Lambda value (or list of values for each corpora)
|
|
@@ -584,7 +171,11 @@ class VectaraToolFactory:
|
|
|
584
171
|
|
|
585
172
|
query = kwargs.pop("query")
|
|
586
173
|
top_k = kwargs.pop("top_k", 10)
|
|
587
|
-
summarize =
|
|
174
|
+
summarize = (
|
|
175
|
+
kwargs.pop("summarize", True)
|
|
176
|
+
if summarize_docs is None
|
|
177
|
+
else summarize_docs
|
|
178
|
+
)
|
|
588
179
|
try:
|
|
589
180
|
filter_string = _build_filter_string(
|
|
590
181
|
kwargs, tool_args_type, fixed_filter
|
|
@@ -643,7 +234,10 @@ class VectaraToolFactory:
|
|
|
643
234
|
if summarize:
|
|
644
235
|
summaries_dict = asyncio.run(
|
|
645
236
|
summarize_documents(
|
|
646
|
-
self.vectara_corpus_key,
|
|
237
|
+
corpus_key=self.vectara_corpus_key,
|
|
238
|
+
api_key=self.vectara_api_key,
|
|
239
|
+
llm_name=summarize_llm_name,
|
|
240
|
+
doc_ids=list(unique_ids),
|
|
647
241
|
)
|
|
648
242
|
)
|
|
649
243
|
for doc_id, metadata in docs:
|
|
@@ -665,30 +259,47 @@ class VectaraToolFactory:
|
|
|
665
259
|
|
|
666
260
|
class SearchToolBaseParams(BaseModel):
|
|
667
261
|
"""Model for the base parameters of the search tool."""
|
|
262
|
+
|
|
668
263
|
query: str = Field(
|
|
669
264
|
...,
|
|
670
|
-
description="The search query to perform,
|
|
265
|
+
description="The search query to perform, in the form of a question.",
|
|
671
266
|
)
|
|
672
267
|
top_k: int = Field(
|
|
673
|
-
10, description="The number of top documents to retrieve."
|
|
268
|
+
default=10, description="The number of top documents to retrieve."
|
|
674
269
|
)
|
|
675
270
|
summarize: bool = Field(
|
|
676
271
|
True,
|
|
677
|
-
description="
|
|
272
|
+
description="Whether to summarize the retrieved documents.",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
class SearchToolBaseParamsWithoutSummarize(BaseModel):
|
|
276
|
+
"""Model for the base parameters of the search tool."""
|
|
277
|
+
|
|
278
|
+
query: str = Field(
|
|
279
|
+
...,
|
|
280
|
+
description="The search query to perform, in the form of a question.",
|
|
281
|
+
)
|
|
282
|
+
top_k: int = Field(
|
|
283
|
+
10, description="The number of top documents to retrieve."
|
|
678
284
|
)
|
|
679
285
|
|
|
680
286
|
search_tool_extra_desc = (
|
|
681
287
|
tool_description
|
|
682
288
|
+ "\n"
|
|
683
|
-
+ "
|
|
289
|
+
+ "Use this tool to search for relevant documents, not to ask questions."
|
|
684
290
|
)
|
|
685
291
|
|
|
686
292
|
tool = _create_tool_from_dynamic_function(
|
|
687
293
|
search_function,
|
|
688
294
|
tool_name,
|
|
689
295
|
search_tool_extra_desc,
|
|
690
|
-
|
|
296
|
+
(
|
|
297
|
+
SearchToolBaseParams
|
|
298
|
+
if summarize_docs is None
|
|
299
|
+
else SearchToolBaseParamsWithoutSummarize
|
|
300
|
+
),
|
|
691
301
|
tool_args_schema,
|
|
302
|
+
compact_docstring=self.compact_docstring,
|
|
692
303
|
)
|
|
693
304
|
return tool
|
|
694
305
|
|
|
@@ -696,7 +307,7 @@ class VectaraToolFactory:
|
|
|
696
307
|
self,
|
|
697
308
|
tool_name: str,
|
|
698
309
|
tool_description: str,
|
|
699
|
-
tool_args_schema: type[BaseModel],
|
|
310
|
+
tool_args_schema: type[BaseModel] = None,
|
|
700
311
|
tool_args_type: Dict[str, dict] = {},
|
|
701
312
|
fixed_filter: str = "",
|
|
702
313
|
vectara_summarizer: str = "vectara-summary-ext-24-05-med-omni",
|
|
@@ -718,6 +329,7 @@ class VectaraToolFactory:
|
|
|
718
329
|
rerank_chain: List[Dict] = None,
|
|
719
330
|
max_response_chars: Optional[int] = None,
|
|
720
331
|
max_tokens: Optional[int] = None,
|
|
332
|
+
llm_name: Optional[str] = None,
|
|
721
333
|
temperature: Optional[float] = None,
|
|
722
334
|
frequency_penalty: Optional[float] = None,
|
|
723
335
|
presence_penalty: Optional[float] = None,
|
|
@@ -734,7 +346,7 @@ class VectaraToolFactory:
|
|
|
734
346
|
Args:
|
|
735
347
|
tool_name (str): The name of the tool.
|
|
736
348
|
tool_description (str): The description of the tool.
|
|
737
|
-
tool_args_schema (BaseModel): The schema for
|
|
349
|
+
tool_args_schema (BaseModel, optional): The schema for any tool arguments for filtering.
|
|
738
350
|
tool_args_type (Dict[str, dict], optional): attributes for each argument where they key is the field name
|
|
739
351
|
and the value is a dictionary with the following keys:
|
|
740
352
|
- 'type': the type of each filter attribute in Vectara (doc or part).
|
|
@@ -765,6 +377,7 @@ class VectaraToolFactory:
|
|
|
765
377
|
If using slingshot/multilingual_reranker_v1, it must be first in the list.
|
|
766
378
|
max_response_chars (int, optional): The desired maximum number of characters for the generated summary.
|
|
767
379
|
max_tokens (int, optional): The maximum number of tokens to be returned by the LLM.
|
|
380
|
+
llm_name (str, optional): The name of the LLM to use for generation.
|
|
768
381
|
temperature (float, optional): The sampling temperature; higher values lead to more randomness.
|
|
769
382
|
frequency_penalty (float, optional): How much to penalize repeating tokens in the response,
|
|
770
383
|
higher values reducing likelihood of repeating the same line.
|
|
@@ -842,6 +455,7 @@ class VectaraToolFactory:
|
|
|
842
455
|
filter=filter_string,
|
|
843
456
|
max_response_chars=max_response_chars,
|
|
844
457
|
max_tokens=max_tokens,
|
|
458
|
+
llm_name=llm_name,
|
|
845
459
|
temperature=temperature,
|
|
846
460
|
frequency_penalty=frequency_penalty,
|
|
847
461
|
presence_penalty=presence_penalty,
|
|
@@ -889,22 +503,27 @@ class VectaraToolFactory:
|
|
|
889
503
|
)
|
|
890
504
|
+ ".\n"
|
|
891
505
|
)
|
|
892
|
-
fcs =
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
506
|
+
fcs = 0.0
|
|
507
|
+
fcs_str = response.metadata["fcs"] if "fcs" in response.metadata else "0.0"
|
|
508
|
+
if fcs_str and is_float(fcs_str):
|
|
509
|
+
fcs = float(fcs_str)
|
|
510
|
+
if fcs < fcs_threshold:
|
|
511
|
+
msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
|
|
512
|
+
return ToolOutput(
|
|
513
|
+
tool_name=rag_function.__name__,
|
|
514
|
+
content=msg,
|
|
515
|
+
raw_input={"args": args, "kwargs": kwargs},
|
|
516
|
+
raw_output={"response": msg},
|
|
517
|
+
)
|
|
901
518
|
res = {
|
|
902
519
|
"response": response.response,
|
|
903
520
|
"references_metadata": citation_metadata,
|
|
521
|
+
"fcs_score": fcs,
|
|
904
522
|
}
|
|
905
523
|
if len(citation_metadata) > 0:
|
|
906
524
|
tool_output = f"""
|
|
907
525
|
Response: '''{res['response']}'''
|
|
526
|
+
fcs_score: {res['fcs_score']:.4f}
|
|
908
527
|
References:
|
|
909
528
|
{res['references_metadata']}
|
|
910
529
|
"""
|
|
@@ -920,9 +539,10 @@ class VectaraToolFactory:
|
|
|
920
539
|
|
|
921
540
|
class RagToolBaseParams(BaseModel):
|
|
922
541
|
"""Model for the base parameters of the RAG tool."""
|
|
542
|
+
|
|
923
543
|
query: str = Field(
|
|
924
544
|
...,
|
|
925
|
-
description="The search query to perform,
|
|
545
|
+
description="The search query to perform, in the form of a question",
|
|
926
546
|
)
|
|
927
547
|
|
|
928
548
|
tool = _create_tool_from_dynamic_function(
|
|
@@ -931,6 +551,7 @@ class VectaraToolFactory:
|
|
|
931
551
|
tool_description,
|
|
932
552
|
RagToolBaseParams,
|
|
933
553
|
tool_args_schema,
|
|
554
|
+
compact_docstring=self.compact_docstring,
|
|
934
555
|
)
|
|
935
556
|
return tool
|
|
936
557
|
|