vectara-agentic 0.2.14__py3-none-any.whl → 0.2.16__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 +2 -2
- tests/test_agent_planning.py +1 -1
- tests/test_groq.py +3 -1
- tests/test_return_direct.py +49 -0
- tests/test_tools.py +120 -18
- tests/test_vectara_llms.py +4 -1
- vectara_agentic/_observability.py +43 -21
- vectara_agentic/_prompts.py +5 -3
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +114 -49
- vectara_agentic/db_tools.py +2 -2
- vectara_agentic/llm_utils.py +10 -6
- vectara_agentic/tool_utils.py +182 -133
- vectara_agentic/tools.py +19 -9
- vectara_agentic/tools_catalog.py +2 -1
- {vectara_agentic-0.2.14.dist-info → vectara_agentic-0.2.16.dist-info}/METADATA +5 -5
- vectara_agentic-0.2.16.dist-info/RECORD +34 -0
- {vectara_agentic-0.2.14.dist-info → vectara_agentic-0.2.16.dist-info}/WHEEL +1 -1
- vectara_agentic-0.2.14.dist-info/RECORD +0 -33
- {vectara_agentic-0.2.14.dist-info → vectara_agentic-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.14.dist-info → vectara_agentic-0.2.16.dist-info}/top_level.txt +0 -0
vectara_agentic/agent.py
CHANGED
|
@@ -14,14 +14,11 @@ import importlib
|
|
|
14
14
|
from collections import Counter
|
|
15
15
|
import inspect
|
|
16
16
|
from inspect import Signature, Parameter, ismethod
|
|
17
|
-
|
|
17
|
+
from pydantic import Field, create_model, ValidationError, BaseModel
|
|
18
18
|
import cloudpickle as pickle
|
|
19
19
|
|
|
20
20
|
from dotenv import load_dotenv
|
|
21
21
|
|
|
22
|
-
from pydantic import Field, create_model, ValidationError
|
|
23
|
-
|
|
24
|
-
|
|
25
22
|
from llama_index.core.memory import ChatMemoryBuffer
|
|
26
23
|
from llama_index.core.llms import ChatMessage, MessageRole
|
|
27
24
|
from llama_index.core.tools import FunctionTool
|
|
@@ -145,21 +142,53 @@ def get_field_type(field_schema: dict) -> Any:
|
|
|
145
142
|
"array": list,
|
|
146
143
|
"object": dict,
|
|
147
144
|
"number": float,
|
|
145
|
+
"null": type(None),
|
|
148
146
|
}
|
|
147
|
+
if not field_schema: # Handles empty schema {}
|
|
148
|
+
return Any
|
|
149
|
+
|
|
149
150
|
if "anyOf" in field_schema:
|
|
150
151
|
types = []
|
|
151
|
-
for
|
|
152
|
-
#
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
else:
|
|
156
|
-
types.append(Any)
|
|
157
|
-
# Return a Union of the types. For example, Union[str, int]
|
|
152
|
+
for option_schema in field_schema["anyOf"]:
|
|
153
|
+
types.append(get_field_type(option_schema)) # Recursive call
|
|
154
|
+
if not types:
|
|
155
|
+
return Any
|
|
158
156
|
return Union[tuple(types)]
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
|
|
158
|
+
if "type" in field_schema and isinstance(field_schema["type"], list):
|
|
159
|
+
types = []
|
|
160
|
+
for type_name in field_schema["type"]:
|
|
161
|
+
if type_name == "array":
|
|
162
|
+
item_schema = field_schema.get("items", {})
|
|
163
|
+
types.append(List[get_field_type(item_schema)])
|
|
164
|
+
elif type_name in json_type_to_python:
|
|
165
|
+
types.append(json_type_to_python[type_name])
|
|
166
|
+
else:
|
|
167
|
+
types.append(Any) # Fallback for unknown types in the list
|
|
168
|
+
if not types:
|
|
169
|
+
return Any
|
|
170
|
+
return Union[tuple(types)] # type: ignore
|
|
171
|
+
|
|
172
|
+
if "type" in field_schema:
|
|
173
|
+
schema_type_name = field_schema["type"]
|
|
174
|
+
if schema_type_name == "array":
|
|
175
|
+
item_schema = field_schema.get(
|
|
176
|
+
"items", {}
|
|
177
|
+
) # Default to Any if "items" is missing
|
|
178
|
+
return List[get_field_type(item_schema)]
|
|
179
|
+
|
|
180
|
+
return json_type_to_python.get(schema_type_name, Any)
|
|
181
|
+
|
|
182
|
+
# If only "items" is present (implies array by some conventions, but less standard)
|
|
183
|
+
# Or if it's a schema with other keywords like 'properties' (implying object)
|
|
184
|
+
# For simplicity, if no "type" or "anyOf" at this point, default to Any or add more specific handling.
|
|
185
|
+
# If 'properties' in field_schema or 'additionalProperties' in field_schema, it's likely an object.
|
|
186
|
+
if "properties" in field_schema or "additionalProperties" in field_schema:
|
|
187
|
+
# This path might need to reconstruct a nested Pydantic model if you encounter such schemas.
|
|
188
|
+
# For now, treating as 'dict' or 'Any' might be a simpler placeholder.
|
|
189
|
+
return dict # Or Any, or more sophisticated object reconstruction.
|
|
190
|
+
|
|
191
|
+
return Any
|
|
163
192
|
|
|
164
193
|
|
|
165
194
|
class Agent:
|
|
@@ -248,19 +277,27 @@ class Agent:
|
|
|
248
277
|
|
|
249
278
|
if validate_tools:
|
|
250
279
|
prompt = f"""
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
280
|
+
You are provided these tools:
|
|
281
|
+
<tools>{','.join(tool_names)}</tools>
|
|
282
|
+
And these instructions:
|
|
283
|
+
<instructions>
|
|
254
284
|
{self._custom_instructions}
|
|
255
|
-
|
|
256
|
-
Your
|
|
257
|
-
|
|
285
|
+
</instructions>
|
|
286
|
+
Your task is to identify invalid tools.
|
|
287
|
+
A tool is invalid if it is mentioned in the instructions but not in the tools list.
|
|
288
|
+
A tool's name must have at least two characters.
|
|
289
|
+
Your response should be a comma-separated list of the invalid tools.
|
|
290
|
+
If not invalid tools exist, respond with "<OKAY>".
|
|
258
291
|
"""
|
|
259
292
|
llm = get_llm(LLMRole.MAIN, config=self.agent_config)
|
|
260
|
-
|
|
261
|
-
if
|
|
293
|
+
bad_tools_str = llm.complete(prompt).text
|
|
294
|
+
if bad_tools_str and bad_tools_str != "<OKAY>":
|
|
295
|
+
bad_tools = [tool.strip() for tool in bad_tools_str.split(",")]
|
|
296
|
+
numbered = ", ".join(
|
|
297
|
+
f"({i}) {tool}" for i, tool in enumerate(bad_tools, 1)
|
|
298
|
+
)
|
|
262
299
|
raise ValueError(
|
|
263
|
-
f"The Agent custom instructions mention these invalid tools: {
|
|
300
|
+
f"The Agent custom instructions mention these invalid tools: {numbered}"
|
|
264
301
|
)
|
|
265
302
|
|
|
266
303
|
# Create token counters for the main and tool LLMs
|
|
@@ -689,6 +726,7 @@ class Agent:
|
|
|
689
726
|
vectara_frequency_penalty: Optional[float] = None,
|
|
690
727
|
vectara_presence_penalty: Optional[float] = None,
|
|
691
728
|
vectara_save_history: bool = True,
|
|
729
|
+
return_direct: bool = False,
|
|
692
730
|
) -> "Agent":
|
|
693
731
|
"""
|
|
694
732
|
Create an agent from a single Vectara corpus
|
|
@@ -738,6 +776,7 @@ class Agent:
|
|
|
738
776
|
vectara_presence_penalty (float, optional): How much to penalize repeating tokens in the response,
|
|
739
777
|
higher values increasing the diversity of topics.
|
|
740
778
|
vectara_save_history (bool, optional): Whether to save the query in history.
|
|
779
|
+
return_direct (bool, optional): Whether the agent should return the tool's response directly.
|
|
741
780
|
|
|
742
781
|
Returns:
|
|
743
782
|
Agent: An instance of the Agent class.
|
|
@@ -791,6 +830,7 @@ class Agent:
|
|
|
791
830
|
save_history=vectara_save_history,
|
|
792
831
|
include_citations=True,
|
|
793
832
|
verbose=verbose,
|
|
833
|
+
return_direct=return_direct,
|
|
794
834
|
)
|
|
795
835
|
|
|
796
836
|
assistant_instructions = f"""
|
|
@@ -1134,41 +1174,66 @@ class Agent:
|
|
|
1134
1174
|
tools = []
|
|
1135
1175
|
|
|
1136
1176
|
for tool_data in data["tools"]:
|
|
1137
|
-
|
|
1177
|
+
query_args_model = None
|
|
1138
1178
|
if tool_data.get("fn_schema"):
|
|
1139
1179
|
schema_info = tool_data["fn_schema"]
|
|
1140
1180
|
try:
|
|
1141
1181
|
module_name = schema_info["metadata"]["module"]
|
|
1142
1182
|
class_name = schema_info["metadata"]["class"]
|
|
1143
1183
|
mod = importlib.import_module(module_name)
|
|
1144
|
-
|
|
1145
|
-
|
|
1184
|
+
candidate_cls = getattr(mod, class_name)
|
|
1185
|
+
if inspect.isclass(candidate_cls) and issubclass(
|
|
1186
|
+
candidate_cls, BaseModel
|
|
1187
|
+
):
|
|
1188
|
+
query_args_model = candidate_cls
|
|
1189
|
+
else:
|
|
1190
|
+
# It's not the Pydantic model class we expected (e.g., it's the function itself)
|
|
1191
|
+
# Force fallback to JSON schema reconstruction by raising an error.
|
|
1192
|
+
raise ImportError(
|
|
1193
|
+
f"Retrieved '{class_name}' from '{module_name}' is not a Pydantic BaseModel class. "
|
|
1194
|
+
"Falling back to JSON schema reconstruction."
|
|
1195
|
+
)
|
|
1146
1196
|
except Exception:
|
|
1147
1197
|
# Fallback: rebuild using the JSON schema
|
|
1148
1198
|
field_definitions = {}
|
|
1149
|
-
|
|
1150
|
-
|
|
1199
|
+
json_schema_to_rebuild = schema_info.get("schema")
|
|
1200
|
+
if json_schema_to_rebuild and isinstance(
|
|
1201
|
+
json_schema_to_rebuild, dict
|
|
1151
1202
|
):
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1203
|
+
for field, values in json_schema_to_rebuild.get(
|
|
1204
|
+
"properties", {}
|
|
1205
|
+
).items():
|
|
1206
|
+
field_type = get_field_type(values)
|
|
1207
|
+
field_description = values.get(
|
|
1208
|
+
"description"
|
|
1209
|
+
) # Defaults to None
|
|
1210
|
+
if "default" in values:
|
|
1211
|
+
field_definitions[field] = (
|
|
1212
|
+
field_type,
|
|
1213
|
+
Field(
|
|
1214
|
+
description=field_description,
|
|
1215
|
+
default=values["default"],
|
|
1216
|
+
),
|
|
1217
|
+
)
|
|
1218
|
+
else:
|
|
1219
|
+
field_definitions[field] = (
|
|
1220
|
+
field_type,
|
|
1221
|
+
Field(description=field_description),
|
|
1222
|
+
)
|
|
1223
|
+
query_args_model = create_model(
|
|
1224
|
+
json_schema_to_rebuild.get(
|
|
1225
|
+
"title", f"{tool_data['name']}_QueryArgs"
|
|
1226
|
+
),
|
|
1227
|
+
**field_definitions,
|
|
1228
|
+
)
|
|
1229
|
+
else: # If schema part is missing or not a dict, create a default empty model
|
|
1230
|
+
query_args_model = create_model(
|
|
1231
|
+
f"{tool_data['name']}_QueryArgs"
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
# If fn_schema was not in tool_data or reconstruction failed badly, default to empty pydantic model
|
|
1235
|
+
if query_args_model is None:
|
|
1236
|
+
query_args_model = create_model(f"{tool_data['name']}_QueryArgs")
|
|
1172
1237
|
|
|
1173
1238
|
fn = (
|
|
1174
1239
|
pickle.loads(tool_data["fn"].encode("latin-1"))
|
vectara_agentic/db_tools.py
CHANGED
|
@@ -112,7 +112,7 @@ class DatabaseTools:
|
|
|
112
112
|
List[str]: a list of Document objects from the database.
|
|
113
113
|
"""
|
|
114
114
|
if sql_query is None:
|
|
115
|
-
raise ValueError("A query parameter is necessary to filter the data")
|
|
115
|
+
raise ValueError("A query parameter is necessary to filter the data.")
|
|
116
116
|
|
|
117
117
|
count_query = f"SELECT COUNT(*) FROM ({sql_query})"
|
|
118
118
|
try:
|
|
@@ -123,7 +123,7 @@ class DatabaseTools:
|
|
|
123
123
|
if num_rows > self.max_rows:
|
|
124
124
|
return [
|
|
125
125
|
f"The query is expected to return more than {self.max_rows} rows. "
|
|
126
|
-
"Please refactor your query to make it return less rows. "
|
|
126
|
+
"Please refactor your query to make it return less rows and try again. "
|
|
127
127
|
]
|
|
128
128
|
try:
|
|
129
129
|
res = self._load_data(sql_query)
|
vectara_agentic/llm_utils.py
CHANGED
|
@@ -69,12 +69,16 @@ def get_tokenizer_for_model(
|
|
|
69
69
|
"""
|
|
70
70
|
Get the tokenizer for the specified model, as determined by the role & config.
|
|
71
71
|
"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
try:
|
|
73
|
+
model_provider, model_name = _get_llm_params_for_role(role, config)
|
|
74
|
+
if model_provider == ModelProvider.OPENAI:
|
|
75
|
+
# This might raise an exception if the model_name is unknown to tiktoken
|
|
76
|
+
return tiktoken.encoding_for_model(model_name).encode
|
|
77
|
+
if model_provider == ModelProvider.ANTHROPIC:
|
|
78
|
+
return Anthropic().tokenizer
|
|
79
|
+
except Exception:
|
|
80
|
+
print(f"Error getting tokenizer for model {model_name}, ignoring")
|
|
81
|
+
return None
|
|
78
82
|
return None
|
|
79
83
|
|
|
80
84
|
|