vectara-agentic 0.2.13__py3-none-any.whl → 0.2.15__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.

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, Type, Tuple
12
- from pydantic import BaseModel, Field, create_model
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 ToolMetadata, ToolOutput
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,
@@ -61,460 +63,6 @@ LI_packages = {
61
63
  }
62
64
 
63
65
 
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
66
  class VectaraToolFactory:
519
67
  """
520
68
  A factory class for creating Vectara RAG tools.
@@ -561,6 +109,7 @@ class VectaraToolFactory:
561
109
  mmr_diversity_bias: float = 0.2,
562
110
  udf_expression: str = None,
563
111
  rerank_chain: List[Dict] = None,
112
+ return_direct: bool = False,
564
113
  save_history: bool = True,
565
114
  verbose: bool = False,
566
115
  vectara_base_url: str = "https://api.vectara.io",
@@ -595,6 +144,7 @@ class VectaraToolFactory:
595
144
  "diversity_bias" for mmr, and "user_function" for udf).
596
145
  If using slingshot/multilingual_reranker_v1, it must be first in the list.
597
146
  save_history (bool, optional): Whether to save the query in history.
147
+ return_direct (bool, optional): Whether the agent should return the tool's response directly.
598
148
  verbose (bool, optional): Whether to print verbose output.
599
149
  vectara_base_url (str, optional): The base URL for the Vectara API.
600
150
  vectara_verify_ssl (bool, optional): Whether to verify SSL certificates for the Vectara API.
@@ -630,7 +180,7 @@ class VectaraToolFactory:
630
180
  else summarize_docs
631
181
  )
632
182
  try:
633
- filter_string = _build_filter_string(
183
+ filter_string = build_filter_string(
634
184
  kwargs, tool_args_type, fixed_filter
635
185
  )
636
186
  except ValueError as e:
@@ -718,7 +268,7 @@ class VectaraToolFactory:
718
268
  description="The search query to perform, in the form of a question.",
719
269
  )
720
270
  top_k: int = Field(
721
- 10, description="The number of top documents to retrieve."
271
+ default=10, description="The number of top documents to retrieve."
722
272
  )
723
273
  summarize: bool = Field(
724
274
  True,
@@ -742,7 +292,7 @@ class VectaraToolFactory:
742
292
  + "Use this tool to search for relevant documents, not to ask questions."
743
293
  )
744
294
 
745
- tool = _create_tool_from_dynamic_function(
295
+ tool = create_tool_from_dynamic_function(
746
296
  search_function,
747
297
  tool_name,
748
298
  search_tool_extra_desc,
@@ -753,6 +303,7 @@ class VectaraToolFactory:
753
303
  ),
754
304
  tool_args_schema,
755
305
  compact_docstring=self.compact_docstring,
306
+ return_direct=return_direct,
756
307
  )
757
308
  return tool
758
309
 
@@ -789,6 +340,7 @@ class VectaraToolFactory:
789
340
  include_citations: bool = True,
790
341
  save_history: bool = False,
791
342
  fcs_threshold: float = 0.0,
343
+ return_direct: bool = False,
792
344
  verbose: bool = False,
793
345
  vectara_base_url: str = "https://api.vectara.io",
794
346
  vectara_verify_ssl: bool = True,
@@ -841,6 +393,7 @@ class VectaraToolFactory:
841
393
  save_history (bool, optional): Whether to save the query in history.
842
394
  fcs_threshold (float, optional): A threshold for factual consistency.
843
395
  If set above 0, the tool notifies the calling agent that it "cannot respond" if FCS is too low.
396
+ return_direct (bool, optional): Whether the agent should return the tool's response directly.
844
397
  verbose (bool, optional): Whether to print verbose output.
845
398
  vectara_base_url (str, optional): The base URL for the Vectara API.
846
399
  vectara_verify_ssl (bool, optional): Whether to verify SSL certificates for the Vectara API.
@@ -870,7 +423,7 @@ class VectaraToolFactory:
870
423
 
871
424
  query = kwargs.pop("query")
872
425
  try:
873
- filter_string = _build_filter_string(
426
+ filter_string = build_filter_string(
874
427
  kwargs, tool_args_type, fixed_filter
875
428
  )
876
429
  except ValueError as e:
@@ -921,7 +474,10 @@ class VectaraToolFactory:
921
474
  response = vectara_query_engine.query(query)
922
475
 
923
476
  if len(response.source_nodes) == 0:
924
- msg = "Tool failed to generate a response since no matches were found."
477
+ msg = (
478
+ "Tool failed to generate a response since no matches were found. "
479
+ "Please check the arguments and try again."
480
+ )
925
481
  return ToolOutput(
926
482
  tool_name=rag_function.__name__,
927
483
  content=msg,
@@ -956,22 +512,27 @@ class VectaraToolFactory:
956
512
  )
957
513
  + ".\n"
958
514
  )
959
- fcs = response.metadata["fcs"] if "fcs" in response.metadata else 0.0
960
- if fcs and fcs < fcs_threshold:
961
- msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
962
- return ToolOutput(
963
- tool_name=rag_function.__name__,
964
- content=msg,
965
- raw_input={"args": args, "kwargs": kwargs},
966
- raw_output={"response": msg},
967
- )
515
+ fcs = 0.0
516
+ fcs_str = response.metadata["fcs"] if "fcs" in response.metadata else "0.0"
517
+ if fcs_str and is_float(fcs_str):
518
+ fcs = float(fcs_str)
519
+ if fcs < fcs_threshold:
520
+ msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
521
+ return ToolOutput(
522
+ tool_name=rag_function.__name__,
523
+ content=msg,
524
+ raw_input={"args": args, "kwargs": kwargs},
525
+ raw_output={"response": msg},
526
+ )
968
527
  res = {
969
528
  "response": response.response,
970
529
  "references_metadata": citation_metadata,
530
+ "fcs_score": fcs,
971
531
  }
972
532
  if len(citation_metadata) > 0:
973
533
  tool_output = f"""
974
534
  Response: '''{res['response']}'''
535
+ fcs_score: {res['fcs_score']:.4f}
975
536
  References:
976
537
  {res['references_metadata']}
977
538
  """
@@ -993,13 +554,14 @@ class VectaraToolFactory:
993
554
  description="The search query to perform, in the form of a question",
994
555
  )
995
556
 
996
- tool = _create_tool_from_dynamic_function(
557
+ tool = create_tool_from_dynamic_function(
997
558
  rag_function,
998
559
  tool_name,
999
560
  tool_description,
1000
561
  RagToolBaseParams,
1001
562
  tool_args_schema,
1002
563
  compact_docstring=self.compact_docstring,
564
+ return_direct=return_direct,
1003
565
  )
1004
566
  return tool
1005
567
 
@@ -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 .utils import get_llm, remove_self_from_signature
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
 
@@ -24,7 +25,7 @@ get_headers = {
24
25
 
25
26
  def get_current_date() -> str:
26
27
  """
27
- Returns: the current date (when called) as a string.
28
+ Returns the current date as a string.
28
29
  """
29
30
  return date.today().strftime("%A, %B %d, %Y")
30
31