vectara-agentic 0.2.13__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_groq.py +120 -0
- tests/test_tools.py +41 -5
- tests/test_vectara_llms.py +0 -11
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +65 -1
- vectara_agentic/llm_utils.py +174 -0
- vectara_agentic/tool_utils.py +513 -0
- vectara_agentic/tools.py +23 -471
- vectara_agentic/tools_catalog.py +2 -1
- vectara_agentic/utils.py +0 -153
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.14.dist-info}/METADATA +25 -11
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.14.dist-info}/RECORD +15 -12
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.14.dist-info}/WHEEL +1 -1
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.14.dist-info}/top_level.txt +0 -0
vectara_agentic/tools.py
CHANGED
|
@@ -8,22 +8,24 @@ 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
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,
|
|
@@ -60,461 +62,6 @@ LI_packages = {
|
|
|
60
62
|
},
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
class VectaraToolMetadata(ToolMetadata):
|
|
65
|
-
"""
|
|
66
|
-
A subclass of ToolMetadata adding the tool_type attribute.
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
tool_type: ToolType
|
|
70
|
-
|
|
71
|
-
def __init__(self, tool_type: ToolType, **kwargs):
|
|
72
|
-
super().__init__(**kwargs)
|
|
73
|
-
self.tool_type = tool_type
|
|
74
|
-
|
|
75
|
-
def __repr__(self) -> str:
|
|
76
|
-
"""
|
|
77
|
-
Returns a string representation of the VectaraToolMetadata object, including the tool_type attribute.
|
|
78
|
-
"""
|
|
79
|
-
base_repr = super().__repr__()
|
|
80
|
-
return f"{base_repr}, tool_type={self.tool_type}"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class VectaraTool(FunctionTool):
|
|
84
|
-
"""
|
|
85
|
-
A subclass of FunctionTool adding the tool_type attribute.
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
def __init__(
|
|
89
|
-
self,
|
|
90
|
-
tool_type: ToolType,
|
|
91
|
-
metadata: ToolMetadata,
|
|
92
|
-
fn: Optional[Callable[..., Any]] = None,
|
|
93
|
-
async_fn: Optional[AsyncCallable] = None,
|
|
94
|
-
) -> None:
|
|
95
|
-
metadata_dict = (
|
|
96
|
-
metadata.dict() if hasattr(metadata, "dict") else metadata.__dict__
|
|
97
|
-
)
|
|
98
|
-
vm = VectaraToolMetadata(tool_type=tool_type, **metadata_dict)
|
|
99
|
-
super().__init__(fn, vm, async_fn)
|
|
100
|
-
|
|
101
|
-
@classmethod
|
|
102
|
-
def from_defaults(
|
|
103
|
-
cls,
|
|
104
|
-
fn: Optional[Callable[..., Any]] = None,
|
|
105
|
-
name: Optional[str] = None,
|
|
106
|
-
description: Optional[str] = None,
|
|
107
|
-
return_direct: bool = False,
|
|
108
|
-
fn_schema: Optional[Type[BaseModel]] = None,
|
|
109
|
-
async_fn: Optional[AsyncCallable] = None,
|
|
110
|
-
tool_metadata: Optional[ToolMetadata] = None,
|
|
111
|
-
callback: Optional[Callable[[Any], Any]] = None,
|
|
112
|
-
async_callback: Optional[AsyncCallable] = None,
|
|
113
|
-
tool_type: ToolType = ToolType.QUERY,
|
|
114
|
-
) -> "VectaraTool":
|
|
115
|
-
tool = FunctionTool.from_defaults(
|
|
116
|
-
fn,
|
|
117
|
-
name,
|
|
118
|
-
description,
|
|
119
|
-
return_direct,
|
|
120
|
-
fn_schema,
|
|
121
|
-
async_fn,
|
|
122
|
-
tool_metadata,
|
|
123
|
-
callback,
|
|
124
|
-
async_callback,
|
|
125
|
-
)
|
|
126
|
-
vectara_tool = cls(
|
|
127
|
-
tool_type=tool_type,
|
|
128
|
-
fn=tool.fn,
|
|
129
|
-
metadata=tool.metadata,
|
|
130
|
-
async_fn=tool.async_fn,
|
|
131
|
-
)
|
|
132
|
-
return vectara_tool
|
|
133
|
-
|
|
134
|
-
def __str__(self) -> str:
|
|
135
|
-
return f"Tool(name={self.metadata.name}, " f"Tool metadata={self.metadata})"
|
|
136
|
-
|
|
137
|
-
def __repr__(self) -> str:
|
|
138
|
-
return str(self)
|
|
139
|
-
|
|
140
|
-
def __eq__(self, other):
|
|
141
|
-
if not isinstance(other, VectaraTool):
|
|
142
|
-
return False
|
|
143
|
-
|
|
144
|
-
if self.metadata.tool_type != other.metadata.tool_type:
|
|
145
|
-
return False
|
|
146
|
-
|
|
147
|
-
if self.metadata.name != other.metadata.name:
|
|
148
|
-
return False
|
|
149
|
-
|
|
150
|
-
# If schema is a dict-like object, compare the dict representation
|
|
151
|
-
try:
|
|
152
|
-
# Try to get schema as dict if possible
|
|
153
|
-
if hasattr(self.metadata.fn_schema, "schema"):
|
|
154
|
-
self_schema = self.metadata.fn_schema.schema
|
|
155
|
-
other_schema = other.metadata.fn_schema.schema
|
|
156
|
-
|
|
157
|
-
# Compare only properties and required fields
|
|
158
|
-
self_props = self_schema.get("properties", {})
|
|
159
|
-
other_props = other_schema.get("properties", {})
|
|
160
|
-
|
|
161
|
-
self_required = self_schema.get("required", [])
|
|
162
|
-
other_required = other_schema.get("required", [])
|
|
163
|
-
|
|
164
|
-
return self_props.keys() == other_props.keys() and set(
|
|
165
|
-
self_required
|
|
166
|
-
) == set(other_required)
|
|
167
|
-
except Exception:
|
|
168
|
-
# If any exception occurs during schema comparison, fall back to name comparison
|
|
169
|
-
pass
|
|
170
|
-
|
|
171
|
-
return True
|
|
172
|
-
|
|
173
|
-
def call(
|
|
174
|
-
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
175
|
-
) -> ToolOutput:
|
|
176
|
-
try:
|
|
177
|
-
return super().call(*args, ctx=ctx, **kwargs)
|
|
178
|
-
except TypeError as e:
|
|
179
|
-
sig = inspect.signature(self.metadata.fn_schema)
|
|
180
|
-
valid_parameters = list(sig.parameters.keys())
|
|
181
|
-
params_str = ", ".join(valid_parameters)
|
|
182
|
-
|
|
183
|
-
err_output = ToolOutput(
|
|
184
|
-
tool_name=self.metadata.name,
|
|
185
|
-
content=(
|
|
186
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
|
|
187
|
-
f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
|
|
188
|
-
),
|
|
189
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
190
|
-
raw_output={"response": str(e)},
|
|
191
|
-
)
|
|
192
|
-
return err_output
|
|
193
|
-
except Exception as e:
|
|
194
|
-
err_output = ToolOutput(
|
|
195
|
-
tool_name=self.metadata.name,
|
|
196
|
-
content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
|
|
197
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
198
|
-
raw_output={"response": str(e)},
|
|
199
|
-
)
|
|
200
|
-
return err_output
|
|
201
|
-
|
|
202
|
-
async def acall(
|
|
203
|
-
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
204
|
-
) -> ToolOutput:
|
|
205
|
-
try:
|
|
206
|
-
return await super().acall(*args, ctx=ctx, **kwargs)
|
|
207
|
-
except TypeError as e:
|
|
208
|
-
sig = inspect.signature(self.metadata.fn_schema)
|
|
209
|
-
valid_parameters = list(sig.parameters.keys())
|
|
210
|
-
params_str = ", ".join(valid_parameters)
|
|
211
|
-
|
|
212
|
-
err_output = ToolOutput(
|
|
213
|
-
tool_name=self.metadata.name,
|
|
214
|
-
content=(
|
|
215
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
|
|
216
|
-
f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
|
|
217
|
-
),
|
|
218
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
219
|
-
raw_output={"response": str(e)},
|
|
220
|
-
)
|
|
221
|
-
return err_output
|
|
222
|
-
except Exception as e:
|
|
223
|
-
err_output = ToolOutput(
|
|
224
|
-
tool_name=self.metadata.name,
|
|
225
|
-
content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
|
|
226
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
227
|
-
raw_output={"response": str(e)},
|
|
228
|
-
)
|
|
229
|
-
return err_output
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def _create_tool_from_dynamic_function(
|
|
233
|
-
function: Callable[..., ToolOutput],
|
|
234
|
-
tool_name: str,
|
|
235
|
-
tool_description: str,
|
|
236
|
-
base_params_model: Type[BaseModel], # Now a Pydantic BaseModel
|
|
237
|
-
tool_args_schema: Type[BaseModel],
|
|
238
|
-
compact_docstring: bool = False,
|
|
239
|
-
) -> VectaraTool:
|
|
240
|
-
fields = {}
|
|
241
|
-
base_params = []
|
|
242
|
-
|
|
243
|
-
if tool_args_schema is None:
|
|
244
|
-
|
|
245
|
-
class EmptyBaseModel(BaseModel):
|
|
246
|
-
"""empty base model"""
|
|
247
|
-
|
|
248
|
-
tool_args_schema = EmptyBaseModel
|
|
249
|
-
|
|
250
|
-
# Create inspect.Parameter objects for base_params_model fields.
|
|
251
|
-
for param_name, model_field in base_params_model.model_fields.items():
|
|
252
|
-
field_type = base_params_model.__annotations__.get(
|
|
253
|
-
param_name, str
|
|
254
|
-
) # default to str if not found
|
|
255
|
-
default_value = (
|
|
256
|
-
model_field.default
|
|
257
|
-
if model_field.default is not None
|
|
258
|
-
else inspect.Parameter.empty
|
|
259
|
-
)
|
|
260
|
-
base_params.append(
|
|
261
|
-
inspect.Parameter(
|
|
262
|
-
param_name,
|
|
263
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
264
|
-
default=default_value,
|
|
265
|
-
annotation=field_type,
|
|
266
|
-
)
|
|
267
|
-
)
|
|
268
|
-
fields[param_name] = (
|
|
269
|
-
field_type,
|
|
270
|
-
model_field.default if model_field.default is not None else ...,
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
# Add tool_args_schema fields to the fields dict if not already included.
|
|
274
|
-
# Also add them to the function signature by creating new inspect.Parameter objects.
|
|
275
|
-
for field_name, field_info in tool_args_schema.model_fields.items():
|
|
276
|
-
if field_name not in fields:
|
|
277
|
-
default_value = (
|
|
278
|
-
field_info.default if field_info.default is not None else ...
|
|
279
|
-
)
|
|
280
|
-
field_type = tool_args_schema.__annotations__.get(field_name, None)
|
|
281
|
-
fields[field_name] = (field_type, default_value)
|
|
282
|
-
# Append these fields to the signature.
|
|
283
|
-
base_params.append(
|
|
284
|
-
inspect.Parameter(
|
|
285
|
-
field_name,
|
|
286
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
287
|
-
default=(
|
|
288
|
-
default_value
|
|
289
|
-
if default_value is not ...
|
|
290
|
-
else inspect.Parameter.empty
|
|
291
|
-
),
|
|
292
|
-
annotation=field_type,
|
|
293
|
-
)
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
# Create the dynamic schema with both base_params_model and tool_args_schema fields.
|
|
297
|
-
fn_schema = create_model(f"{tool_name}_schema", **fields)
|
|
298
|
-
|
|
299
|
-
# Combine parameters into a function signature.
|
|
300
|
-
all_params = base_params[:] # Now all_params contains parameters from both models.
|
|
301
|
-
required_params = [p for p in all_params if p.default is inspect.Parameter.empty]
|
|
302
|
-
optional_params = [
|
|
303
|
-
p for p in all_params if p.default is not inspect.Parameter.empty
|
|
304
|
-
]
|
|
305
|
-
function.__signature__ = inspect.Signature(required_params + optional_params)
|
|
306
|
-
function.__annotations__["return"] = dict[str, Any]
|
|
307
|
-
function.__name__ = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
308
|
-
|
|
309
|
-
# Build a docstring using parameter descriptions from the BaseModels.
|
|
310
|
-
params_str = ", ".join(
|
|
311
|
-
f"{p.name}: {p.annotation.__name__ if hasattr(p.annotation, '__name__') else p.annotation}"
|
|
312
|
-
for p in all_params
|
|
313
|
-
)
|
|
314
|
-
signature_line = f"{tool_name}({params_str}) -> dict[str, Any]"
|
|
315
|
-
if compact_docstring:
|
|
316
|
-
doc_lines = [
|
|
317
|
-
tool_description.strip(),
|
|
318
|
-
]
|
|
319
|
-
else:
|
|
320
|
-
doc_lines = [
|
|
321
|
-
signature_line,
|
|
322
|
-
"",
|
|
323
|
-
tool_description.strip(),
|
|
324
|
-
]
|
|
325
|
-
doc_lines += [
|
|
326
|
-
"",
|
|
327
|
-
"Args:",
|
|
328
|
-
]
|
|
329
|
-
for param in all_params:
|
|
330
|
-
description = ""
|
|
331
|
-
if param.name in base_params_model.model_fields:
|
|
332
|
-
description = base_params_model.model_fields[param.name].description
|
|
333
|
-
elif param.name in tool_args_schema.model_fields:
|
|
334
|
-
description = tool_args_schema.model_fields[param.name].description
|
|
335
|
-
if not description:
|
|
336
|
-
description = ""
|
|
337
|
-
type_name = (
|
|
338
|
-
param.annotation.__name__
|
|
339
|
-
if hasattr(param.annotation, "__name__")
|
|
340
|
-
else str(param.annotation)
|
|
341
|
-
)
|
|
342
|
-
if (
|
|
343
|
-
param.default is not inspect.Parameter.empty
|
|
344
|
-
and param.default is not PydanticUndefined
|
|
345
|
-
):
|
|
346
|
-
default_text = f", default={param.default!r}"
|
|
347
|
-
else:
|
|
348
|
-
default_text = ""
|
|
349
|
-
doc_lines.append(f" - {param.name} ({type_name}){default_text}: {description}")
|
|
350
|
-
doc_lines.append("")
|
|
351
|
-
doc_lines.append("Returns:")
|
|
352
|
-
return_desc = getattr(
|
|
353
|
-
function, "__return_description__", "A dictionary containing the result data."
|
|
354
|
-
)
|
|
355
|
-
doc_lines.append(f" dict[str, Any]: {return_desc}")
|
|
356
|
-
|
|
357
|
-
initial_docstring = "\n".join(doc_lines)
|
|
358
|
-
collapsed_spaces = re.sub(r' {2,}', ' ', initial_docstring)
|
|
359
|
-
final_docstring = re.sub(r'\n{2,}', '\n', collapsed_spaces).strip()
|
|
360
|
-
function.__doc__ = final_docstring
|
|
361
|
-
|
|
362
|
-
tool = VectaraTool.from_defaults(
|
|
363
|
-
fn=function,
|
|
364
|
-
name=tool_name,
|
|
365
|
-
description=function.__doc__,
|
|
366
|
-
fn_schema=fn_schema,
|
|
367
|
-
tool_type=ToolType.QUERY,
|
|
368
|
-
)
|
|
369
|
-
return tool
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
Range = Tuple[float, float, bool, bool] # (min, max, min_inclusive, max_inclusive)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def _parse_range(val_str: str) -> Range:
|
|
376
|
-
"""
|
|
377
|
-
Parses '[1,10)' or '(0.5, 5]' etc.
|
|
378
|
-
Returns (start, end, start_incl, end_incl) or raises ValueError.
|
|
379
|
-
"""
|
|
380
|
-
m = re.match(
|
|
381
|
-
r"""
|
|
382
|
-
^([\[\(])\s* # opening bracket
|
|
383
|
-
([+-]?\d+(\.\d*)?)\s*, # first number
|
|
384
|
-
\s*([+-]?\d+(\.\d*)?) # second number
|
|
385
|
-
\s*([\]\)])$ # closing bracket
|
|
386
|
-
""",
|
|
387
|
-
val_str,
|
|
388
|
-
re.VERBOSE,
|
|
389
|
-
)
|
|
390
|
-
if not m:
|
|
391
|
-
raise ValueError(f"Invalid range syntax: {val_str!r}")
|
|
392
|
-
start_inc = m.group(1) == "["
|
|
393
|
-
end_inc = m.group(7) == "]"
|
|
394
|
-
start = float(m.group(2))
|
|
395
|
-
end = float(m.group(4))
|
|
396
|
-
if start > end:
|
|
397
|
-
raise ValueError(f"Range lower bound greater than upper bound: {val_str!r}")
|
|
398
|
-
return start, end, start_inc, end_inc
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
def _parse_comparison(val_str: str) -> Tuple[str, Union[float, str, bool]]:
|
|
402
|
-
"""
|
|
403
|
-
Parses '>10', '<=3.14', '!=foo', \"='bar'\" etc.
|
|
404
|
-
Returns (operator, rhs) or raises ValueError.
|
|
405
|
-
"""
|
|
406
|
-
# pick off the operator
|
|
407
|
-
comparison_operators = [">=", "<=", "!=", ">", "<", "="]
|
|
408
|
-
numeric_only_operators = {">", "<", ">=", "<="}
|
|
409
|
-
for op in comparison_operators:
|
|
410
|
-
if val_str.startswith(op):
|
|
411
|
-
rhs = val_str[len(op) :].strip()
|
|
412
|
-
if op in numeric_only_operators:
|
|
413
|
-
try:
|
|
414
|
-
rhs_val = float(rhs)
|
|
415
|
-
except ValueError as e:
|
|
416
|
-
raise ValueError(
|
|
417
|
-
f"Numeric comparison {op!r} must have a number, got {rhs!r}"
|
|
418
|
-
) from e
|
|
419
|
-
return op, rhs_val
|
|
420
|
-
# = and != can be bool, numeric, or string
|
|
421
|
-
low = rhs.lower()
|
|
422
|
-
if low in ("true", "false"):
|
|
423
|
-
return op, (low == "true")
|
|
424
|
-
try:
|
|
425
|
-
return op, float(rhs)
|
|
426
|
-
except ValueError:
|
|
427
|
-
return op, rhs
|
|
428
|
-
raise ValueError(f"No valid comparison operator at start of {val_str!r}")
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def _build_filter_string(
|
|
432
|
-
kwargs: Dict[str, Any], tool_args_type: Dict[str, dict], fixed_filter: str
|
|
433
|
-
) -> str:
|
|
434
|
-
"""
|
|
435
|
-
Build filter string for Vectara from kwargs
|
|
436
|
-
"""
|
|
437
|
-
filter_parts = []
|
|
438
|
-
for key, raw in kwargs.items():
|
|
439
|
-
if raw is None or raw == "":
|
|
440
|
-
continue
|
|
441
|
-
|
|
442
|
-
if raw is PydanticUndefined:
|
|
443
|
-
raise ValueError(
|
|
444
|
-
f"Value of argument {key!r} is undefined, and this is invalid. "
|
|
445
|
-
"Please form proper arguments and try again."
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
tool_args_dict = tool_args_type.get(key, {"type": "doc", "is_list": False})
|
|
449
|
-
prefix = tool_args_dict.get("type", "doc")
|
|
450
|
-
is_list = tool_args_dict.get("is_list", False)
|
|
451
|
-
|
|
452
|
-
if prefix not in ("doc", "part"):
|
|
453
|
-
raise ValueError(
|
|
454
|
-
f'Unrecognized prefix {prefix!r}. Please make sure to use either "doc" or "part" for the prefix.'
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
# 1) native numeric
|
|
458
|
-
if isinstance(raw, (int, float)) or is_float(str(raw)):
|
|
459
|
-
val = str(raw)
|
|
460
|
-
if is_list:
|
|
461
|
-
filter_parts.append(f"({val} IN {prefix}.{key})")
|
|
462
|
-
else:
|
|
463
|
-
filter_parts.append(f"{prefix}.{key}={val}")
|
|
464
|
-
continue
|
|
465
|
-
|
|
466
|
-
# 2) native boolean
|
|
467
|
-
if isinstance(raw, bool):
|
|
468
|
-
val = "true" if raw else "false"
|
|
469
|
-
if is_list:
|
|
470
|
-
filter_parts.append(f"({val} IN {prefix}.{key})")
|
|
471
|
-
else:
|
|
472
|
-
filter_parts.append(f"{prefix}.{key}={val}")
|
|
473
|
-
continue
|
|
474
|
-
|
|
475
|
-
if not isinstance(raw, str):
|
|
476
|
-
raise ValueError(f"Unsupported type for {key!r}: {type(raw).__name__}")
|
|
477
|
-
|
|
478
|
-
val_str = raw.strip()
|
|
479
|
-
|
|
480
|
-
# 3) Range operator
|
|
481
|
-
if (val_str.startswith("[") or val_str.startswith("(")) and (
|
|
482
|
-
val_str.endswith("]") or val_str.endswith(")")
|
|
483
|
-
):
|
|
484
|
-
start, end, start_incl, end_incl = _parse_range(val_str)
|
|
485
|
-
conds = []
|
|
486
|
-
op1 = ">=" if start_incl else ">"
|
|
487
|
-
op2 = "<=" if end_incl else "<"
|
|
488
|
-
conds.append(f"{prefix}.{key} {op1} {start}")
|
|
489
|
-
conds.append(f"{prefix}.{key} {op2} {end}")
|
|
490
|
-
filter_parts.append("(" + " AND ".join(conds) + ")")
|
|
491
|
-
continue
|
|
492
|
-
|
|
493
|
-
# 4) comparison operator
|
|
494
|
-
try:
|
|
495
|
-
op, rhs = _parse_comparison(val_str)
|
|
496
|
-
except ValueError:
|
|
497
|
-
# no operator → treat as membership or equality-on-string
|
|
498
|
-
if is_list:
|
|
499
|
-
filter_parts.append(f"('{val_str}' IN {prefix}.{key})")
|
|
500
|
-
else:
|
|
501
|
-
filter_parts.append(f"{prefix}.{key}='{val_str}'")
|
|
502
|
-
else:
|
|
503
|
-
# normal comparison always binds to the field
|
|
504
|
-
if isinstance(rhs, bool):
|
|
505
|
-
rhs_sql = "true" if rhs else "false"
|
|
506
|
-
elif isinstance(rhs, (int, float)):
|
|
507
|
-
rhs_sql = str(rhs)
|
|
508
|
-
else:
|
|
509
|
-
rhs_sql = f"'{rhs}'"
|
|
510
|
-
filter_parts.append(f"{prefix}.{key}{op}{rhs_sql}")
|
|
511
|
-
|
|
512
|
-
joined = " AND ".join(filter_parts)
|
|
513
|
-
if fixed_filter and joined:
|
|
514
|
-
return f"({fixed_filter}) AND ({joined})"
|
|
515
|
-
return fixed_filter or joined
|
|
516
|
-
|
|
517
|
-
|
|
518
65
|
class VectaraToolFactory:
|
|
519
66
|
"""
|
|
520
67
|
A factory class for creating Vectara RAG tools.
|
|
@@ -718,7 +265,7 @@ class VectaraToolFactory:
|
|
|
718
265
|
description="The search query to perform, in the form of a question.",
|
|
719
266
|
)
|
|
720
267
|
top_k: int = Field(
|
|
721
|
-
10, description="The number of top documents to retrieve."
|
|
268
|
+
default=10, description="The number of top documents to retrieve."
|
|
722
269
|
)
|
|
723
270
|
summarize: bool = Field(
|
|
724
271
|
True,
|
|
@@ -956,22 +503,27 @@ class VectaraToolFactory:
|
|
|
956
503
|
)
|
|
957
504
|
+ ".\n"
|
|
958
505
|
)
|
|
959
|
-
fcs =
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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
|
+
)
|
|
968
518
|
res = {
|
|
969
519
|
"response": response.response,
|
|
970
520
|
"references_metadata": citation_metadata,
|
|
521
|
+
"fcs_score": fcs,
|
|
971
522
|
}
|
|
972
523
|
if len(citation_metadata) > 0:
|
|
973
524
|
tool_output = f"""
|
|
974
525
|
Response: '''{res['response']}'''
|
|
526
|
+
fcs_score: {res['fcs_score']:.4f}
|
|
975
527
|
References:
|
|
976
528
|
{res['references_metadata']}
|
|
977
529
|
"""
|
vectara_agentic/tools_catalog.py
CHANGED
|
@@ -10,7 +10,8 @@ from pydantic import Field
|
|
|
10
10
|
|
|
11
11
|
from .types import LLMRole
|
|
12
12
|
from .agent_config import AgentConfig
|
|
13
|
-
from .
|
|
13
|
+
from .llm_utils import get_llm
|
|
14
|
+
from .utils import remove_self_from_signature
|
|
14
15
|
|
|
15
16
|
req_session = requests.Session()
|
|
16
17
|
|