vectara-agentic 0.3.3__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of vectara-agentic might be problematic. Click here for more details.
- tests/__init__.py +7 -0
- tests/conftest.py +316 -0
- tests/endpoint.py +54 -17
- tests/run_tests.py +112 -0
- tests/test_agent.py +35 -33
- tests/test_agent_fallback_memory.py +270 -0
- tests/test_agent_memory_consistency.py +229 -0
- tests/test_agent_type.py +86 -143
- tests/test_api_endpoint.py +4 -0
- tests/test_bedrock.py +50 -31
- tests/test_fallback.py +4 -0
- tests/test_gemini.py +27 -59
- tests/test_groq.py +50 -31
- tests/test_private_llm.py +11 -2
- tests/test_return_direct.py +6 -2
- tests/test_serialization.py +7 -6
- tests/test_session_memory.py +252 -0
- tests/test_streaming.py +109 -0
- tests/test_together.py +62 -0
- tests/test_tools.py +10 -82
- tests/test_vectara_llms.py +4 -0
- tests/test_vhc.py +67 -0
- tests/test_workflow.py +13 -28
- vectara_agentic/__init__.py +27 -4
- vectara_agentic/_callback.py +65 -67
- vectara_agentic/_observability.py +30 -30
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +565 -859
- vectara_agentic/agent_config.py +15 -14
- vectara_agentic/agent_core/__init__.py +22 -0
- vectara_agentic/agent_core/factory.py +383 -0
- vectara_agentic/{_prompts.py → agent_core/prompts.py} +21 -46
- vectara_agentic/agent_core/serialization.py +348 -0
- vectara_agentic/agent_core/streaming.py +483 -0
- vectara_agentic/agent_core/utils/__init__.py +29 -0
- vectara_agentic/agent_core/utils/hallucination.py +157 -0
- vectara_agentic/agent_core/utils/logging.py +52 -0
- vectara_agentic/agent_core/utils/schemas.py +87 -0
- vectara_agentic/agent_core/utils/tools.py +125 -0
- vectara_agentic/agent_endpoint.py +4 -6
- vectara_agentic/db_tools.py +37 -12
- vectara_agentic/llm_utils.py +42 -43
- vectara_agentic/sub_query_workflow.py +9 -14
- vectara_agentic/tool_utils.py +138 -83
- vectara_agentic/tools.py +36 -21
- vectara_agentic/tools_catalog.py +16 -16
- vectara_agentic/types.py +106 -8
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/METADATA +111 -31
- vectara_agentic-0.4.1.dist-info/RECORD +53 -0
- tests/test_agent_planning.py +0 -64
- tests/test_hhem.py +0 -100
- vectara_agentic/hhem.py +0 -82
- vectara_agentic-0.3.3.dist-info/RECORD +0 -39
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,8 @@ that takes a user question and a list of tools, and outputs a list of sub-questi
|
|
|
5
5
|
|
|
6
6
|
import re
|
|
7
7
|
import json
|
|
8
|
+
import logging
|
|
9
|
+
|
|
8
10
|
from pydantic import BaseModel, Field
|
|
9
11
|
|
|
10
12
|
from llama_index.core.workflow import (
|
|
@@ -70,7 +72,6 @@ class SubQuestionQueryWorkflow(Workflow):
|
|
|
70
72
|
|
|
71
73
|
query = ev.inputs.query
|
|
72
74
|
await ctx.set("original_query", query)
|
|
73
|
-
print(f"Query is {query}")
|
|
74
75
|
|
|
75
76
|
required_attrs = ["agent", "llm", "tools"]
|
|
76
77
|
for attr in required_attrs:
|
|
@@ -114,9 +115,6 @@ class SubQuestionQueryWorkflow(Workflow):
|
|
|
114
115
|
""",
|
|
115
116
|
)
|
|
116
117
|
|
|
117
|
-
if await ctx.get("verbose"):
|
|
118
|
-
print(f"Sub-questions are {response}")
|
|
119
|
-
|
|
120
118
|
response_str = str(response)
|
|
121
119
|
if not response_str:
|
|
122
120
|
raise ValueError(
|
|
@@ -139,7 +137,6 @@ class SubQuestionQueryWorkflow(Workflow):
|
|
|
139
137
|
if not sub_questions:
|
|
140
138
|
# If the LLM returns an empty list, we need to handle it gracefully
|
|
141
139
|
# We use the original query as a single question fallback
|
|
142
|
-
print("LLM returned empty sub-questions list")
|
|
143
140
|
sub_questions = [original_query]
|
|
144
141
|
|
|
145
142
|
await ctx.set("sub_question_count", len(sub_questions))
|
|
@@ -154,7 +151,7 @@ class SubQuestionQueryWorkflow(Workflow):
|
|
|
154
151
|
Given a sub-question, return the answer to the sub-question, using the agent.
|
|
155
152
|
"""
|
|
156
153
|
if await ctx.get("verbose"):
|
|
157
|
-
|
|
154
|
+
logging.info(f"Sub-question is {ev.question}")
|
|
158
155
|
agent = await ctx.get("agent")
|
|
159
156
|
question = ev.question
|
|
160
157
|
response = await agent.achat(question)
|
|
@@ -188,14 +185,13 @@ class SubQuestionQueryWorkflow(Workflow):
|
|
|
188
185
|
{answers}
|
|
189
186
|
"""
|
|
190
187
|
if await ctx.get("verbose"):
|
|
191
|
-
|
|
188
|
+
logging.info(f"Final prompt is {prompt}")
|
|
192
189
|
|
|
193
190
|
llm = await ctx.get("llm")
|
|
194
191
|
response = llm.complete(prompt)
|
|
195
192
|
|
|
196
193
|
if await ctx.get("verbose"):
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
logging.info(f"Final response is {response}")
|
|
199
195
|
return StopEvent(result=self.OutputsModel(response=str(response)))
|
|
200
196
|
|
|
201
197
|
|
|
@@ -274,8 +270,7 @@ class SequentialSubQuestionsWorkflow(Workflow):
|
|
|
274
270
|
|
|
275
271
|
original_query = await ctx.get("original_query")
|
|
276
272
|
if ev.verbose:
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
logging.info(f"Query is {original_query}")
|
|
279
274
|
llm = await ctx.get("llm")
|
|
280
275
|
response = llm.complete(
|
|
281
276
|
f"""
|
|
@@ -326,7 +321,7 @@ class SequentialSubQuestionsWorkflow(Workflow):
|
|
|
326
321
|
|
|
327
322
|
await ctx.set("sub_questions", sub_questions)
|
|
328
323
|
if await ctx.get("verbose"):
|
|
329
|
-
|
|
324
|
+
logging.info(f"Sub-questions are {sub_questions}")
|
|
330
325
|
|
|
331
326
|
return self.QueryEvent(question=sub_questions[0], prev_answer="", num=0)
|
|
332
327
|
|
|
@@ -338,7 +333,7 @@ class SequentialSubQuestionsWorkflow(Workflow):
|
|
|
338
333
|
Given a sub-question, return the answer to the sub-question, using the agent.
|
|
339
334
|
"""
|
|
340
335
|
if await ctx.get("verbose"):
|
|
341
|
-
|
|
336
|
+
logging.info(f"Sub-question is {ev.question}")
|
|
342
337
|
agent = await ctx.get("agent")
|
|
343
338
|
sub_questions = await ctx.get("sub_questions")
|
|
344
339
|
question = ev.question
|
|
@@ -353,7 +348,7 @@ class SequentialSubQuestionsWorkflow(Workflow):
|
|
|
353
348
|
response = await agent.achat(question)
|
|
354
349
|
answer = response.response
|
|
355
350
|
if await ctx.get("verbose"):
|
|
356
|
-
|
|
351
|
+
logging.info(f"Answer is {answer}")
|
|
357
352
|
|
|
358
353
|
if ev.num + 1 < len(sub_questions):
|
|
359
354
|
await ctx.set("qna", await ctx.get("qna", []) + [(question, answer)])
|
vectara_agentic/tool_utils.py
CHANGED
|
@@ -4,6 +4,7 @@ This module contains the ToolsFactory class for creating agent tools.
|
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
6
|
import re
|
|
7
|
+
import traceback
|
|
7
8
|
|
|
8
9
|
from typing import (
|
|
9
10
|
Callable,
|
|
@@ -17,7 +18,7 @@ from typing import (
|
|
|
17
18
|
get_origin,
|
|
18
19
|
get_args,
|
|
19
20
|
)
|
|
20
|
-
from pydantic import BaseModel, create_model
|
|
21
|
+
from pydantic import BaseModel, create_model, Field
|
|
21
22
|
from pydantic_core import PydanticUndefined
|
|
22
23
|
|
|
23
24
|
from llama_index.core.tools import FunctionTool
|
|
@@ -31,21 +32,26 @@ from .utils import is_float
|
|
|
31
32
|
|
|
32
33
|
class VectaraToolMetadata(ToolMetadata):
|
|
33
34
|
"""
|
|
34
|
-
A subclass of ToolMetadata adding the tool_type
|
|
35
|
+
A subclass of ToolMetadata adding the tool_type and vhc_eligible attributes.
|
|
35
36
|
"""
|
|
36
37
|
|
|
37
38
|
tool_type: ToolType
|
|
39
|
+
vhc_eligible: bool
|
|
38
40
|
|
|
39
|
-
def __init__(self, tool_type: ToolType, **kwargs):
|
|
41
|
+
def __init__(self, tool_type: ToolType, vhc_eligible: bool = True, **kwargs):
|
|
40
42
|
super().__init__(**kwargs)
|
|
41
43
|
self.tool_type = tool_type
|
|
44
|
+
self.vhc_eligible = vhc_eligible
|
|
42
45
|
|
|
43
46
|
def __repr__(self) -> str:
|
|
44
47
|
"""
|
|
45
|
-
Returns a string representation of the VectaraToolMetadata object,
|
|
48
|
+
Returns a string representation of the VectaraToolMetadata object,
|
|
49
|
+
including the tool_type and vhc_eligible attributes.
|
|
46
50
|
"""
|
|
47
51
|
base_repr = super().__repr__()
|
|
48
|
-
return
|
|
52
|
+
return (
|
|
53
|
+
f"{base_repr}, tool_type={self.tool_type}, vhc_eligible={self.vhc_eligible}"
|
|
54
|
+
)
|
|
49
55
|
|
|
50
56
|
|
|
51
57
|
class VectaraTool(FunctionTool):
|
|
@@ -59,11 +65,17 @@ class VectaraTool(FunctionTool):
|
|
|
59
65
|
metadata: ToolMetadata,
|
|
60
66
|
fn: Optional[Callable[..., Any]] = None,
|
|
61
67
|
async_fn: Optional[AsyncCallable] = None,
|
|
68
|
+
vhc_eligible: bool = True,
|
|
62
69
|
) -> None:
|
|
70
|
+
# Use Pydantic v2 compatible method for extracting metadata
|
|
63
71
|
metadata_dict = (
|
|
64
|
-
metadata.
|
|
72
|
+
metadata.model_dump()
|
|
73
|
+
if hasattr(metadata, "model_dump")
|
|
74
|
+
else metadata.dict() if hasattr(metadata, "dict") else metadata.__dict__
|
|
75
|
+
)
|
|
76
|
+
vm = VectaraToolMetadata(
|
|
77
|
+
tool_type=tool_type, vhc_eligible=vhc_eligible, **metadata_dict
|
|
65
78
|
)
|
|
66
|
-
vm = VectaraToolMetadata(tool_type=tool_type, **metadata_dict)
|
|
67
79
|
super().__init__(fn, vm, async_fn)
|
|
68
80
|
|
|
69
81
|
@classmethod
|
|
@@ -80,6 +92,7 @@ class VectaraTool(FunctionTool):
|
|
|
80
92
|
async_callback: Optional[AsyncCallable] = None,
|
|
81
93
|
partial_params: Optional[Dict[str, Any]] = None,
|
|
82
94
|
tool_type: ToolType = ToolType.QUERY,
|
|
95
|
+
vhc_eligible: bool = True,
|
|
83
96
|
) -> "VectaraTool":
|
|
84
97
|
tool = FunctionTool.from_defaults(
|
|
85
98
|
fn,
|
|
@@ -98,6 +111,7 @@ class VectaraTool(FunctionTool):
|
|
|
98
111
|
fn=tool.fn,
|
|
99
112
|
metadata=tool.metadata,
|
|
100
113
|
async_fn=tool.async_fn,
|
|
114
|
+
vhc_eligible=vhc_eligible,
|
|
101
115
|
)
|
|
102
116
|
return vectara_tool
|
|
103
117
|
|
|
@@ -123,94 +137,82 @@ class VectaraTool(FunctionTool):
|
|
|
123
137
|
)
|
|
124
138
|
return is_equal
|
|
125
139
|
|
|
126
|
-
def
|
|
127
|
-
self,
|
|
140
|
+
def _create_tool_error_output(
|
|
141
|
+
self, error: Exception, args: Any, kwargs: Any, include_traceback: bool = False
|
|
128
142
|
) -> ToolOutput:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
except TypeError as e:
|
|
143
|
+
"""Create standardized error output for tool execution failures."""
|
|
144
|
+
if isinstance(error, TypeError):
|
|
145
|
+
# Parameter validation error handling
|
|
133
146
|
sig = inspect.signature(self.metadata.fn_schema)
|
|
134
147
|
valid_parameters = list(sig.parameters.keys())
|
|
135
148
|
params_str = ", ".join(valid_parameters)
|
|
136
|
-
|
|
137
|
-
err_output = ToolOutput(
|
|
149
|
+
return ToolOutput(
|
|
138
150
|
tool_name=self.metadata.name,
|
|
139
151
|
content=(
|
|
140
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(
|
|
141
|
-
f"Valid arguments: {params_str}.
|
|
152
|
+
f"Wrong argument used when calling {self.metadata.name}: {str(error)}. "
|
|
153
|
+
f"Valid arguments: {params_str}. Please call the tool again with the correct arguments."
|
|
142
154
|
),
|
|
143
155
|
raw_input={"args": args, "kwargs": kwargs},
|
|
144
|
-
raw_output={"response": str(
|
|
156
|
+
raw_output={"response": str(error)},
|
|
145
157
|
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
158
|
+
else:
|
|
159
|
+
# General execution error handling
|
|
160
|
+
content = f"Tool {self.metadata.name} Malfunction: {str(error)}"
|
|
161
|
+
if include_traceback:
|
|
162
|
+
content += f", traceback: {traceback.format_exc()}"
|
|
163
|
+
|
|
164
|
+
return ToolOutput(
|
|
149
165
|
tool_name=self.metadata.name,
|
|
150
|
-
content=
|
|
166
|
+
content=content,
|
|
151
167
|
raw_input={"args": args, "kwargs": kwargs},
|
|
152
|
-
raw_output={"response": str(
|
|
168
|
+
raw_output={"response": str(error)},
|
|
153
169
|
)
|
|
154
|
-
return err_output
|
|
155
170
|
|
|
156
|
-
|
|
171
|
+
def call(
|
|
157
172
|
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
158
173
|
) -> ToolOutput:
|
|
159
174
|
try:
|
|
160
|
-
|
|
175
|
+
# Only pass ctx if it's not None to avoid passing unwanted kwargs to the function
|
|
176
|
+
if ctx is not None:
|
|
177
|
+
result = super().call(*args, ctx=ctx, **kwargs)
|
|
178
|
+
else:
|
|
179
|
+
result = super().call(*args, **kwargs)
|
|
161
180
|
return self._format_tool_output(result)
|
|
162
|
-
except TypeError as e:
|
|
163
|
-
sig = inspect.signature(self.metadata.fn_schema)
|
|
164
|
-
valid_parameters = list(sig.parameters.keys())
|
|
165
|
-
params_str = ", ".join(valid_parameters)
|
|
166
|
-
|
|
167
|
-
err_output = ToolOutput(
|
|
168
|
-
tool_name=self.metadata.name,
|
|
169
|
-
content=(
|
|
170
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
|
|
171
|
-
f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
|
|
172
|
-
),
|
|
173
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
174
|
-
raw_output={"response": str(e)},
|
|
175
|
-
)
|
|
176
|
-
return err_output
|
|
177
181
|
except Exception as e:
|
|
178
|
-
|
|
182
|
+
return self._create_tool_error_output(e, args, kwargs)
|
|
179
183
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
async def acall(
|
|
185
|
+
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
186
|
+
) -> ToolOutput:
|
|
187
|
+
try:
|
|
188
|
+
# Only pass ctx if it's not None to avoid passing unwanted kwargs to the function
|
|
189
|
+
if ctx is not None:
|
|
190
|
+
result = await super().acall(*args, ctx=ctx, **kwargs)
|
|
191
|
+
else:
|
|
192
|
+
result = await super().acall(*args, **kwargs)
|
|
193
|
+
return self._format_tool_output(result)
|
|
194
|
+
except Exception as e:
|
|
195
|
+
return self._create_tool_error_output(
|
|
196
|
+
e, args, kwargs, include_traceback=True
|
|
185
197
|
)
|
|
186
|
-
return err_output
|
|
187
198
|
|
|
188
199
|
def _format_tool_output(self, result: ToolOutput) -> ToolOutput:
|
|
189
|
-
"""Format tool output
|
|
190
|
-
|
|
200
|
+
"""Format tool output by converting human-readable wrappers to formatted content immediately."""
|
|
201
|
+
import logging
|
|
202
|
+
|
|
203
|
+
# If the raw_output has human-readable formatting, use it for the content
|
|
204
|
+
if hasattr(result, "raw_output") and _is_human_readable_output(
|
|
205
|
+
result.raw_output
|
|
206
|
+
):
|
|
191
207
|
try:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return ToolOutput(
|
|
196
|
-
tool_name=result.tool_name,
|
|
197
|
-
content=human_readable_content,
|
|
198
|
-
raw_input=result.raw_input,
|
|
199
|
-
raw_output=raw_output,
|
|
200
|
-
)
|
|
208
|
+
formatted_content = result.raw_output.to_human_readable()
|
|
209
|
+
# Replace the content with the formatted version
|
|
210
|
+
result.content = formatted_content
|
|
201
211
|
except Exception as e:
|
|
202
|
-
# If formatting fails, fall back to original content with error info
|
|
203
|
-
import logging
|
|
204
|
-
|
|
205
212
|
logging.warning(
|
|
206
|
-
f"Failed to
|
|
207
|
-
)
|
|
208
|
-
return ToolOutput(
|
|
209
|
-
tool_name=result.tool_name,
|
|
210
|
-
content=f"[Formatting Error] {str(result.content)}",
|
|
211
|
-
raw_input=result.raw_input,
|
|
212
|
-
raw_output={"error": str(e), "original_content": result.content},
|
|
213
|
+
f"{self.metadata.name}: Failed to convert to human-readable: {e}"
|
|
213
214
|
)
|
|
215
|
+
|
|
214
216
|
return result
|
|
215
217
|
|
|
216
218
|
|
|
@@ -257,7 +259,6 @@ def _make_docstring(
|
|
|
257
259
|
tool_description: str,
|
|
258
260
|
fn_schema: Type[BaseModel],
|
|
259
261
|
all_params: List[inspect.Parameter],
|
|
260
|
-
compact_docstring: bool,
|
|
261
262
|
) -> str:
|
|
262
263
|
"""
|
|
263
264
|
Generates a docstring for a function based on its signature, description,
|
|
@@ -269,7 +270,6 @@ def _make_docstring(
|
|
|
269
270
|
tool_description: The main description of the tool/function.
|
|
270
271
|
fn_schema: The Pydantic model representing the function's arguments schema.
|
|
271
272
|
all_params: A list of inspect.Parameter objects for the function signature.
|
|
272
|
-
compact_docstring: If True, omits the signature line in the main description.
|
|
273
273
|
|
|
274
274
|
Returns:
|
|
275
275
|
A formatted docstring string.
|
|
@@ -282,10 +282,7 @@ def _make_docstring(
|
|
|
282
282
|
params_str = ", ".join(params_str_parts)
|
|
283
283
|
signature_line = f"{tool_name}({params_str}) -> dict[str, Any]"
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
doc_lines = [tool_description.strip()]
|
|
287
|
-
else:
|
|
288
|
-
doc_lines = [signature_line, "", tool_description.strip()]
|
|
285
|
+
doc_lines = [signature_line, "", tool_description.strip()]
|
|
289
286
|
|
|
290
287
|
full_schema = fn_schema.model_json_schema()
|
|
291
288
|
props = full_schema.get("properties", {})
|
|
@@ -339,14 +336,61 @@ def _make_docstring(
|
|
|
339
336
|
return final_docstring
|
|
340
337
|
|
|
341
338
|
|
|
339
|
+
def _auto_fix_field_if_needed(
|
|
340
|
+
field_name: str, field_info, annotation
|
|
341
|
+
) -> Tuple[Any, Any]:
|
|
342
|
+
"""
|
|
343
|
+
Auto-fix problematic Field definitions: convert non-Optional types with any default value to Optional.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
field_name: Name of the field
|
|
347
|
+
field_info: The Pydantic FieldInfo object
|
|
348
|
+
annotation: The type annotation for the field
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Tuple of (possibly_modified_annotation, possibly_modified_field_info)
|
|
352
|
+
"""
|
|
353
|
+
# Check for problematic pattern: non-Optional type with any default value
|
|
354
|
+
if (
|
|
355
|
+
field_info.default is not PydanticUndefined
|
|
356
|
+
and annotation is not None
|
|
357
|
+
and get_origin(annotation) is not Union
|
|
358
|
+
):
|
|
359
|
+
|
|
360
|
+
# Convert to Optional[OriginalType] and keep the original default value
|
|
361
|
+
new_annotation = Union[annotation, type(None)]
|
|
362
|
+
# Create new field_info preserving the original default value
|
|
363
|
+
new_field_info = Field(
|
|
364
|
+
default=field_info.default,
|
|
365
|
+
description=field_info.description,
|
|
366
|
+
examples=getattr(field_info, "examples", None),
|
|
367
|
+
title=getattr(field_info, "title", None),
|
|
368
|
+
alias=getattr(field_info, "alias", None),
|
|
369
|
+
json_schema_extra=getattr(field_info, "json_schema_extra", None),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Optional: Log the auto-fix for debugging
|
|
373
|
+
import logging
|
|
374
|
+
|
|
375
|
+
logging.debug(
|
|
376
|
+
f"Auto-fixed field '{field_name}': "
|
|
377
|
+
f"converted {annotation} with default={field_info.default} to Optional[{annotation.__name__}]"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
return new_annotation, new_field_info
|
|
381
|
+
else:
|
|
382
|
+
# Keep original field definition
|
|
383
|
+
return annotation, field_info
|
|
384
|
+
|
|
385
|
+
|
|
342
386
|
def create_tool_from_dynamic_function(
|
|
343
387
|
function: Callable[..., ToolOutput],
|
|
344
388
|
tool_name: str,
|
|
345
389
|
tool_description: str,
|
|
346
390
|
base_params_model: Type[BaseModel],
|
|
347
391
|
tool_args_schema: Type[BaseModel],
|
|
348
|
-
compact_docstring: bool = False,
|
|
349
392
|
return_direct: bool = False,
|
|
393
|
+
vhc_eligible: bool = True,
|
|
350
394
|
) -> VectaraTool:
|
|
351
395
|
"""
|
|
352
396
|
Create a VectaraTool from a dynamic function.
|
|
@@ -356,7 +400,7 @@ def create_tool_from_dynamic_function(
|
|
|
356
400
|
tool_description (str): The description of the tool.
|
|
357
401
|
base_params_model (Type[BaseModel]): The Pydantic model for the base parameters.
|
|
358
402
|
tool_args_schema (Type[BaseModel]): The Pydantic model for the tool arguments.
|
|
359
|
-
|
|
403
|
+
return_direct (bool): Whether to return the tool output directly.
|
|
360
404
|
Returns:
|
|
361
405
|
VectaraTool: The created VectaraTool.
|
|
362
406
|
"""
|
|
@@ -371,6 +415,11 @@ def create_tool_from_dynamic_function(
|
|
|
371
415
|
fields: Dict[str, Any] = {}
|
|
372
416
|
base_params = []
|
|
373
417
|
for field_name, field_info in base_params_model.model_fields.items():
|
|
418
|
+
# Apply auto-conversion if needed
|
|
419
|
+
annotation, field_info = _auto_fix_field_if_needed(
|
|
420
|
+
field_name, field_info, field_info.annotation
|
|
421
|
+
)
|
|
422
|
+
|
|
374
423
|
default = (
|
|
375
424
|
Ellipsis if field_info.default is PydanticUndefined else field_info.default
|
|
376
425
|
)
|
|
@@ -378,16 +427,21 @@ def create_tool_from_dynamic_function(
|
|
|
378
427
|
field_name,
|
|
379
428
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
380
429
|
default=default if default is not Ellipsis else inspect.Parameter.empty,
|
|
381
|
-
annotation=
|
|
430
|
+
annotation=annotation,
|
|
382
431
|
)
|
|
383
432
|
base_params.append(param)
|
|
384
|
-
fields[field_name] = (
|
|
433
|
+
fields[field_name] = (annotation, field_info)
|
|
385
434
|
|
|
386
435
|
# Add tool_args_schema fields to the fields dict if not already included.
|
|
387
436
|
for field_name, field_info in tool_args_schema.model_fields.items():
|
|
388
437
|
if field_name in fields:
|
|
389
438
|
continue
|
|
390
439
|
|
|
440
|
+
# Apply auto-conversion if needed
|
|
441
|
+
annotation, field_info = _auto_fix_field_if_needed(
|
|
442
|
+
field_name, field_info, field_info.annotation
|
|
443
|
+
)
|
|
444
|
+
|
|
391
445
|
default = (
|
|
392
446
|
Ellipsis if field_info.default is PydanticUndefined else field_info.default
|
|
393
447
|
)
|
|
@@ -395,12 +449,12 @@ def create_tool_from_dynamic_function(
|
|
|
395
449
|
field_name,
|
|
396
450
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
397
451
|
default=default if default is not Ellipsis else inspect.Parameter.empty,
|
|
398
|
-
annotation=
|
|
452
|
+
annotation=annotation,
|
|
399
453
|
)
|
|
400
454
|
base_params.append(param)
|
|
401
|
-
fields[field_name] = (
|
|
455
|
+
fields[field_name] = (annotation, field_info)
|
|
402
456
|
|
|
403
|
-
# Create the dynamic schema with both base_params_model and tool_args_schema fields
|
|
457
|
+
# Create the dynamic schema with both base_params_model and tool_args_schema fields (auto-fixed)
|
|
404
458
|
fn_schema = create_model(f"{tool_name}_schema", **fields)
|
|
405
459
|
|
|
406
460
|
# Combine parameters into a function signature.
|
|
@@ -414,7 +468,7 @@ def create_tool_from_dynamic_function(
|
|
|
414
468
|
function.__name__ = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
415
469
|
|
|
416
470
|
function.__doc__ = _make_docstring(
|
|
417
|
-
function, tool_name, tool_description, fn_schema, all_params
|
|
471
|
+
function, tool_name, tool_description, fn_schema, all_params
|
|
418
472
|
)
|
|
419
473
|
tool = VectaraTool.from_defaults(
|
|
420
474
|
fn=function,
|
|
@@ -423,6 +477,7 @@ def create_tool_from_dynamic_function(
|
|
|
423
477
|
fn_schema=fn_schema,
|
|
424
478
|
tool_type=ToolType.QUERY,
|
|
425
479
|
return_direct=return_direct,
|
|
480
|
+
vhc_eligible=vhc_eligible,
|
|
426
481
|
)
|
|
427
482
|
return tool
|
|
428
483
|
|
vectara_agentic/tools.py
CHANGED
|
@@ -74,20 +74,16 @@ class VectaraToolFactory:
|
|
|
74
74
|
self,
|
|
75
75
|
vectara_corpus_key: str = str(os.environ.get("VECTARA_CORPUS_KEY", "")),
|
|
76
76
|
vectara_api_key: str = str(os.environ.get("VECTARA_API_KEY", "")),
|
|
77
|
-
compact_docstring: bool = False,
|
|
78
77
|
) -> None:
|
|
79
78
|
"""
|
|
80
79
|
Initialize the VectaraToolFactory
|
|
81
80
|
Args:
|
|
82
81
|
vectara_corpus_key (str): The Vectara corpus key (or comma separated list of keys).
|
|
83
82
|
vectara_api_key (str): The Vectara API key.
|
|
84
|
-
compact_docstring (bool): Whether to use a compact docstring format for tools
|
|
85
|
-
This is useful if OpenAI complains on the 1024 token limit.
|
|
86
83
|
"""
|
|
87
84
|
self.vectara_corpus_key = vectara_corpus_key
|
|
88
85
|
self.vectara_api_key = vectara_api_key
|
|
89
86
|
self.num_corpora = len(vectara_corpus_key.split(","))
|
|
90
|
-
self.compact_docstring = compact_docstring
|
|
91
87
|
|
|
92
88
|
def create_search_tool(
|
|
93
89
|
self,
|
|
@@ -116,6 +112,7 @@ class VectaraToolFactory:
|
|
|
116
112
|
verbose: bool = False,
|
|
117
113
|
vectara_base_url: str = "https://api.vectara.io",
|
|
118
114
|
vectara_verify_ssl: bool = True,
|
|
115
|
+
vhc_eligible: bool = True,
|
|
119
116
|
) -> VectaraTool:
|
|
120
117
|
"""
|
|
121
118
|
Creates a Vectara search/retrieval tool
|
|
@@ -280,7 +277,7 @@ class VectaraToolFactory:
|
|
|
280
277
|
# Add all matching text if available
|
|
281
278
|
matches = result["metadata"]["matching_text"]
|
|
282
279
|
if matches:
|
|
283
|
-
result_str +=
|
|
280
|
+
result_str += "".join(
|
|
284
281
|
f"Match #{inx} Text: {match}\n"
|
|
285
282
|
for inx, match in enumerate(matches, 1)
|
|
286
283
|
)
|
|
@@ -312,7 +309,7 @@ class VectaraToolFactory:
|
|
|
312
309
|
description="The search query to perform, in the form of a question.",
|
|
313
310
|
)
|
|
314
311
|
top_k: int = Field(
|
|
315
|
-
10, description="The number of top documents to retrieve."
|
|
312
|
+
default=10, description="The number of top documents to retrieve."
|
|
316
313
|
)
|
|
317
314
|
|
|
318
315
|
search_tool_extra_desc = (
|
|
@@ -331,8 +328,8 @@ class VectaraToolFactory:
|
|
|
331
328
|
else SearchToolBaseParamsWithoutSummarize
|
|
332
329
|
),
|
|
333
330
|
tool_args_schema,
|
|
334
|
-
compact_docstring=self.compact_docstring,
|
|
335
331
|
return_direct=return_direct,
|
|
332
|
+
vhc_eligible=vhc_eligible,
|
|
336
333
|
)
|
|
337
334
|
return tool
|
|
338
335
|
|
|
@@ -376,6 +373,7 @@ class VectaraToolFactory:
|
|
|
376
373
|
verbose: bool = False,
|
|
377
374
|
vectara_base_url: str = "https://api.vectara.io",
|
|
378
375
|
vectara_verify_ssl: bool = True,
|
|
376
|
+
vhc_eligible: bool = True,
|
|
379
377
|
) -> VectaraTool:
|
|
380
378
|
"""
|
|
381
379
|
Creates a RAG (Retrieve and Generate) tool.
|
|
@@ -426,7 +424,7 @@ class VectaraToolFactory:
|
|
|
426
424
|
citation_url_pattern (str, optional): The pattern for the citations in the response.
|
|
427
425
|
Default is "{doc.url}" which uses the document URL.
|
|
428
426
|
If include_citations is False, this parameter is ignored.
|
|
429
|
-
citation_pattern (str, optional): old name for citation_url_pattern.
|
|
427
|
+
citation_pattern (str, optional): old name for citation_url_pattern. Deprecated.
|
|
430
428
|
citation_text_pattern (str, optional): The text pattern for citations in the response.
|
|
431
429
|
Default is "{doc.title}" which uses the title of the document.
|
|
432
430
|
If include_citations is False, this parameter is ignored.
|
|
@@ -475,6 +473,15 @@ class VectaraToolFactory:
|
|
|
475
473
|
)
|
|
476
474
|
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
477
475
|
|
|
476
|
+
citations_url_pattern = (
|
|
477
|
+
(
|
|
478
|
+
citation_url_pattern
|
|
479
|
+
if citation_url_pattern is not None
|
|
480
|
+
else citation_pattern
|
|
481
|
+
)
|
|
482
|
+
if include_citations
|
|
483
|
+
else None
|
|
484
|
+
)
|
|
478
485
|
vectara_query_engine = vectara.as_query_engine(
|
|
479
486
|
summary_enabled=True,
|
|
480
487
|
similarity_top_k=summary_num_results,
|
|
@@ -507,8 +514,10 @@ class VectaraToolFactory:
|
|
|
507
514
|
frequency_penalty=frequency_penalty,
|
|
508
515
|
presence_penalty=presence_penalty,
|
|
509
516
|
citations_style="markdown" if include_citations else None,
|
|
510
|
-
citations_url_pattern=
|
|
511
|
-
citations_text_pattern=
|
|
517
|
+
citations_url_pattern=citations_url_pattern,
|
|
518
|
+
citations_text_pattern=(
|
|
519
|
+
citation_text_pattern if include_citations else None
|
|
520
|
+
),
|
|
512
521
|
save_history=save_history,
|
|
513
522
|
x_source_str="vectara-agentic",
|
|
514
523
|
verbose=verbose,
|
|
@@ -520,9 +529,11 @@ class VectaraToolFactory:
|
|
|
520
529
|
"Tool failed to generate a response since no matches were found. "
|
|
521
530
|
"Please check the arguments and try again."
|
|
522
531
|
)
|
|
532
|
+
kwargs["query"] = query
|
|
523
533
|
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
524
534
|
if str(response) == "None":
|
|
525
535
|
msg = "Tool failed to generate a response."
|
|
536
|
+
kwargs["query"] = query
|
|
526
537
|
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
527
538
|
|
|
528
539
|
# Extract citation metadata
|
|
@@ -564,11 +575,8 @@ class VectaraToolFactory:
|
|
|
564
575
|
if key.isdigit():
|
|
565
576
|
doc = value.get("document", {})
|
|
566
577
|
doc_metadata = f"{key}: " + "; ".join(
|
|
567
|
-
[
|
|
568
|
-
|
|
569
|
-
for k, v in doc.items()
|
|
570
|
-
] +
|
|
571
|
-
[
|
|
578
|
+
[f"{k}='{v}'" for k, v in doc.items()]
|
|
579
|
+
+ [
|
|
572
580
|
f"{k}='{v}'"
|
|
573
581
|
for k, v in value.items()
|
|
574
582
|
if k not in ["document"] + keys_to_ignore
|
|
@@ -596,8 +604,8 @@ class VectaraToolFactory:
|
|
|
596
604
|
tool_description,
|
|
597
605
|
RagToolBaseParams,
|
|
598
606
|
tool_args_schema,
|
|
599
|
-
compact_docstring=self.compact_docstring,
|
|
600
607
|
return_direct=return_direct,
|
|
608
|
+
vhc_eligible=vhc_eligible,
|
|
601
609
|
)
|
|
602
610
|
return tool
|
|
603
611
|
|
|
@@ -611,7 +619,10 @@ class ToolsFactory:
|
|
|
611
619
|
self.agent_config = agent_config
|
|
612
620
|
|
|
613
621
|
def create_tool(
|
|
614
|
-
self,
|
|
622
|
+
self,
|
|
623
|
+
function: Callable,
|
|
624
|
+
tool_type: ToolType = ToolType.QUERY,
|
|
625
|
+
vhc_eligible: bool = True,
|
|
615
626
|
) -> VectaraTool:
|
|
616
627
|
"""
|
|
617
628
|
Create a tool from a function.
|
|
@@ -623,7 +634,9 @@ class ToolsFactory:
|
|
|
623
634
|
Returns:
|
|
624
635
|
VectaraTool: A VectaraTool object.
|
|
625
636
|
"""
|
|
626
|
-
return VectaraTool.from_defaults(
|
|
637
|
+
return VectaraTool.from_defaults(
|
|
638
|
+
tool_type=tool_type, fn=function, vhc_eligible=vhc_eligible
|
|
639
|
+
)
|
|
627
640
|
|
|
628
641
|
def get_llama_index_tools(
|
|
629
642
|
self,
|
|
@@ -684,7 +697,7 @@ class ToolsFactory:
|
|
|
684
697
|
"""
|
|
685
698
|
tc = ToolsCatalog(self.agent_config)
|
|
686
699
|
return [
|
|
687
|
-
self.create_tool(tool)
|
|
700
|
+
self.create_tool(tool, vhc_eligible=True)
|
|
688
701
|
for tool in [tc.summarize_text, tc.rephrase_text, tc.critique_text]
|
|
689
702
|
]
|
|
690
703
|
|
|
@@ -692,7 +705,7 @@ class ToolsFactory:
|
|
|
692
705
|
"""
|
|
693
706
|
Create a list of guardrail tools to avoid controversial topics.
|
|
694
707
|
"""
|
|
695
|
-
return [self.create_tool(get_bad_topics)]
|
|
708
|
+
return [self.create_tool(get_bad_topics, vhc_eligible=False)]
|
|
696
709
|
|
|
697
710
|
def financial_tools(self):
|
|
698
711
|
"""
|
|
@@ -733,7 +746,8 @@ class ToolsFactory:
|
|
|
733
746
|
)
|
|
734
747
|
|
|
735
748
|
return [
|
|
736
|
-
self.create_tool(tool
|
|
749
|
+
self.create_tool(tool, vhc_eligible=False)
|
|
750
|
+
for tool in [summarize_legal_text, critique_as_judge]
|
|
737
751
|
]
|
|
738
752
|
|
|
739
753
|
def database_tools(
|
|
@@ -808,6 +822,7 @@ class ToolsFactory:
|
|
|
808
822
|
fn=tool.fn,
|
|
809
823
|
async_fn=tool.async_fn,
|
|
810
824
|
metadata=tool.metadata,
|
|
825
|
+
vhc_eligible=True,
|
|
811
826
|
)
|
|
812
827
|
vtools.append(vtool)
|
|
813
828
|
return vtools
|