vectara-agentic 0.3.3__py3-none-any.whl → 0.4.0__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 +312 -0
- tests/endpoint.py +54 -17
- tests/run_tests.py +111 -0
- tests/test_agent.py +10 -5
- tests/test_agent_type.py +82 -143
- tests/test_api_endpoint.py +4 -0
- tests/test_bedrock.py +4 -0
- tests/test_fallback.py +4 -0
- tests/test_gemini.py +28 -45
- tests/test_groq.py +4 -0
- tests/test_private_llm.py +11 -2
- tests/test_return_direct.py +6 -2
- tests/test_serialization.py +4 -0
- tests/test_streaming.py +88 -0
- tests/test_tools.py +10 -82
- tests/test_vectara_llms.py +4 -0
- tests/test_vhc.py +66 -0
- tests/test_workflow.py +4 -0
- 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 +375 -848
- vectara_agentic/agent_config.py +15 -14
- vectara_agentic/agent_core/__init__.py +22 -0
- vectara_agentic/agent_core/factory.py +501 -0
- vectara_agentic/{_prompts.py → agent_core/prompts.py} +3 -35
- vectara_agentic/agent_core/serialization.py +345 -0
- vectara_agentic/agent_core/streaming.py +495 -0
- vectara_agentic/agent_core/utils/__init__.py +34 -0
- vectara_agentic/agent_core/utils/hallucination.py +202 -0
- vectara_agentic/agent_core/utils/logging.py +52 -0
- vectara_agentic/agent_core/utils/prompt_formatting.py +56 -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 +41 -42
- 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 +98 -6
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.0.dist-info}/METADATA +69 -30
- vectara_agentic-0.4.0.dist-info/RECORD +50 -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.0.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent serialization and deserialization utilities.
|
|
3
|
+
|
|
4
|
+
This module handles saving and restoring agent state, including tools,
|
|
5
|
+
memory, configuration, and all associated metadata. It provides both
|
|
6
|
+
modern JSON-based serialization and legacy pickle fallbacks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import importlib
|
|
11
|
+
import inspect
|
|
12
|
+
from typing import Dict, Any, List, Optional, Callable
|
|
13
|
+
|
|
14
|
+
import cloudpickle as pickle
|
|
15
|
+
from pydantic import Field, create_model, BaseModel
|
|
16
|
+
from llama_index.core.memory import Memory
|
|
17
|
+
from llama_index.core.llms import ChatMessage
|
|
18
|
+
from llama_index.core.tools import FunctionTool
|
|
19
|
+
|
|
20
|
+
from ..agent_config import AgentConfig
|
|
21
|
+
from ..tools import VectaraTool
|
|
22
|
+
from ..types import ToolType
|
|
23
|
+
from .utils.schemas import get_field_type
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def restore_memory_from_dict(data: Dict[str, Any], token_limit: int = 65536) -> Memory:
|
|
27
|
+
"""
|
|
28
|
+
Restore agent memory from serialized dictionary data.
|
|
29
|
+
|
|
30
|
+
Supports both modern JSON format and legacy pickle format for backward compatibility.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
data: Serialized agent data dictionary
|
|
34
|
+
token_limit: Token limit for the memory instance
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Memory: Restored memory instance
|
|
38
|
+
"""
|
|
39
|
+
session_id = data.get("memory_session_id", "default")
|
|
40
|
+
mem = Memory.from_defaults(session_id=session_id, token_limit=token_limit)
|
|
41
|
+
|
|
42
|
+
# New JSON dump format
|
|
43
|
+
dump = data.get("memory_dump", [])
|
|
44
|
+
if dump:
|
|
45
|
+
mem.put_messages([ChatMessage(**m) for m in dump])
|
|
46
|
+
|
|
47
|
+
# Legacy pickle fallback
|
|
48
|
+
legacy_blob = data.get("memory")
|
|
49
|
+
if legacy_blob and not dump:
|
|
50
|
+
try:
|
|
51
|
+
legacy_mem = pickle.loads(legacy_blob.encode("latin-1"))
|
|
52
|
+
mem.put_messages(legacy_mem.get())
|
|
53
|
+
except Exception:
|
|
54
|
+
logging.debug("Legacy memory pickle could not be loaded; ignoring.")
|
|
55
|
+
|
|
56
|
+
return mem
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def serialize_tools(tools: List[FunctionTool]) -> List[Dict[str, Any]]:
|
|
60
|
+
"""
|
|
61
|
+
Serialize a list of tools to dictionary format.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
tools: List of FunctionTool objects to serialize
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List[Dict[str, Any]]: Serialized tool data
|
|
68
|
+
"""
|
|
69
|
+
tool_info = []
|
|
70
|
+
for tool in tools:
|
|
71
|
+
# Serialize function schema if available
|
|
72
|
+
if hasattr(tool.metadata, "fn_schema"):
|
|
73
|
+
fn_schema_cls = tool.metadata.fn_schema
|
|
74
|
+
fn_schema_serialized = {
|
|
75
|
+
"schema": (
|
|
76
|
+
fn_schema_cls.model_json_schema()
|
|
77
|
+
if fn_schema_cls and hasattr(fn_schema_cls, "model_json_schema")
|
|
78
|
+
else None
|
|
79
|
+
),
|
|
80
|
+
"metadata": {
|
|
81
|
+
"module": fn_schema_cls.__module__ if fn_schema_cls else None,
|
|
82
|
+
"class": fn_schema_cls.__name__ if fn_schema_cls else None,
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
else:
|
|
86
|
+
fn_schema_serialized = None
|
|
87
|
+
|
|
88
|
+
# Serialize tool data
|
|
89
|
+
tool_dict = {
|
|
90
|
+
"tool_type": tool.metadata.tool_type.value,
|
|
91
|
+
"name": tool.metadata.name,
|
|
92
|
+
"description": tool.metadata.description,
|
|
93
|
+
"fn": (
|
|
94
|
+
pickle.dumps(getattr(tool, "fn", None)).decode("latin-1")
|
|
95
|
+
if getattr(tool, "fn", None)
|
|
96
|
+
else None
|
|
97
|
+
),
|
|
98
|
+
"async_fn": (
|
|
99
|
+
pickle.dumps(getattr(tool, "async_fn", None)).decode("latin-1")
|
|
100
|
+
if getattr(tool, "async_fn", None)
|
|
101
|
+
else None
|
|
102
|
+
),
|
|
103
|
+
"fn_schema": fn_schema_serialized,
|
|
104
|
+
}
|
|
105
|
+
tool_info.append(tool_dict)
|
|
106
|
+
|
|
107
|
+
return tool_info
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def deserialize_tools(tool_data_list: List[Dict[str, Any]]) -> List[FunctionTool]:
|
|
111
|
+
"""
|
|
112
|
+
Deserialize tools from dictionary format.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
tool_data_list: List of serialized tool dictionaries
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List[FunctionTool]: Deserialized tools
|
|
119
|
+
"""
|
|
120
|
+
tools: List[FunctionTool] = []
|
|
121
|
+
|
|
122
|
+
for tool_data in tool_data_list:
|
|
123
|
+
query_args_model = None
|
|
124
|
+
|
|
125
|
+
# Reconstruct function schema if available
|
|
126
|
+
if tool_data.get("fn_schema"):
|
|
127
|
+
query_args_model = _reconstruct_tool_schema(tool_data)
|
|
128
|
+
|
|
129
|
+
# If fn_schema was not in tool_data or reconstruction failed, default to empty pydantic model
|
|
130
|
+
if query_args_model is None:
|
|
131
|
+
query_args_model = create_model(f"{tool_data['name']}_QueryArgs")
|
|
132
|
+
|
|
133
|
+
# Deserialize function objects with error handling
|
|
134
|
+
fn = None
|
|
135
|
+
async_fn = None
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
if tool_data["fn"]:
|
|
139
|
+
fn = pickle.loads(tool_data["fn"].encode("latin-1"))
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logging.warning(
|
|
142
|
+
f"⚠️ [TOOL_DESERIALIZE] Failed to deserialize fn for tool '{tool_data['name']}': {e}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
if tool_data["async_fn"]:
|
|
147
|
+
async_fn = pickle.loads(tool_data["async_fn"].encode("latin-1"))
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logging.warning(
|
|
150
|
+
f"⚠️ [TOOL_DESERIALIZE] Failed to deserialize async_fn for tool '{tool_data['name']}': {e}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Create tool instance with enhanced error handling
|
|
154
|
+
try:
|
|
155
|
+
tool = VectaraTool.from_defaults(
|
|
156
|
+
name=tool_data["name"],
|
|
157
|
+
description=tool_data["description"],
|
|
158
|
+
fn=fn,
|
|
159
|
+
async_fn=async_fn,
|
|
160
|
+
fn_schema=query_args_model,
|
|
161
|
+
tool_type=ToolType(tool_data["tool_type"]),
|
|
162
|
+
)
|
|
163
|
+
except ValueError as e:
|
|
164
|
+
if "invalid method signature" in str(e):
|
|
165
|
+
logging.warning(
|
|
166
|
+
f"Skipping tool '{tool_data['name']}' due to invalid method signature"
|
|
167
|
+
)
|
|
168
|
+
continue # Skip this tool and continue with others
|
|
169
|
+
tools.append(tool)
|
|
170
|
+
|
|
171
|
+
return tools
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _reconstruct_tool_schema(tool_data: Dict[str, Any]):
|
|
175
|
+
"""
|
|
176
|
+
Reconstruct Pydantic schema for a tool from serialized data.
|
|
177
|
+
|
|
178
|
+
First attempts to import the original class, falls back to JSON schema reconstruction.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
tool_data: Serialized tool data containing schema information
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Pydantic model class or None if reconstruction fails
|
|
185
|
+
"""
|
|
186
|
+
schema_info = tool_data["fn_schema"]
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
# Try to import original class
|
|
190
|
+
module_name = schema_info["metadata"]["module"]
|
|
191
|
+
class_name = schema_info["metadata"]["class"]
|
|
192
|
+
mod = importlib.import_module(module_name)
|
|
193
|
+
candidate_cls = getattr(mod, class_name)
|
|
194
|
+
|
|
195
|
+
if inspect.isclass(candidate_cls) and issubclass(candidate_cls, BaseModel):
|
|
196
|
+
return candidate_cls
|
|
197
|
+
else:
|
|
198
|
+
# Force fallback to JSON schema reconstruction
|
|
199
|
+
raise ImportError(
|
|
200
|
+
f"Retrieved '{class_name}' from '{module_name}' is not a Pydantic BaseModel class. "
|
|
201
|
+
"Falling back to JSON schema reconstruction."
|
|
202
|
+
)
|
|
203
|
+
except Exception:
|
|
204
|
+
# Fallback: rebuild using the JSON schema
|
|
205
|
+
return _rebuild_schema_from_json(schema_info, tool_data["name"])
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _rebuild_schema_from_json(schema_info: Dict[str, Any], tool_name: str):
|
|
209
|
+
"""
|
|
210
|
+
Rebuild Pydantic schema from JSON schema information.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
schema_info: Schema information dictionary
|
|
214
|
+
tool_name: Name of the tool for fallback naming
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Pydantic model class
|
|
218
|
+
"""
|
|
219
|
+
field_definitions = {}
|
|
220
|
+
json_schema_to_rebuild = schema_info.get("schema")
|
|
221
|
+
|
|
222
|
+
if json_schema_to_rebuild and isinstance(json_schema_to_rebuild, dict):
|
|
223
|
+
for field, values in json_schema_to_rebuild.get("properties", {}).items():
|
|
224
|
+
field_type = get_field_type(values)
|
|
225
|
+
field_description = values.get("description") # Defaults to None
|
|
226
|
+
|
|
227
|
+
if "default" in values:
|
|
228
|
+
field_definitions[field] = (
|
|
229
|
+
field_type,
|
|
230
|
+
Field(
|
|
231
|
+
description=field_description,
|
|
232
|
+
default=values["default"],
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
field_definitions[field] = (
|
|
237
|
+
field_type,
|
|
238
|
+
Field(description=field_description),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return create_model(
|
|
242
|
+
json_schema_to_rebuild.get("title", f"{tool_name}_QueryArgs"),
|
|
243
|
+
**field_definitions,
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
# If schema part is missing or not a dict, create a default empty model
|
|
247
|
+
return create_model(f"{tool_name}_QueryArgs")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def serialize_agent_to_dict(agent) -> Dict[str, Any]:
|
|
251
|
+
"""
|
|
252
|
+
Serialize an Agent instance to a dictionary.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
agent: Agent instance to serialize
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Dict[str, Any]: Serialized agent data
|
|
259
|
+
"""
|
|
260
|
+
return {
|
|
261
|
+
"agent_type": agent.agent_config.agent_type.value,
|
|
262
|
+
"memory_dump": [m.model_dump() for m in agent.memory.get()],
|
|
263
|
+
"memory_session_id": getattr(agent.memory, "session_id", None),
|
|
264
|
+
"tools": serialize_tools(agent.tools),
|
|
265
|
+
# pylint: disable=protected-access
|
|
266
|
+
"topic": agent._topic,
|
|
267
|
+
# pylint: disable=protected-access
|
|
268
|
+
"custom_instructions": agent._custom_instructions,
|
|
269
|
+
"verbose": agent.verbose,
|
|
270
|
+
"agent_config": agent.agent_config.to_dict(),
|
|
271
|
+
"fallback_agent_config": (
|
|
272
|
+
agent.fallback_agent_config.to_dict()
|
|
273
|
+
if agent.fallback_agent_config
|
|
274
|
+
else None
|
|
275
|
+
),
|
|
276
|
+
"workflow_cls": agent.workflow_cls if agent.workflow_cls else None,
|
|
277
|
+
"vectara_api_key": agent.vectara_api_key,
|
|
278
|
+
# Custom metadata for agent-specific settings (e.g., use_waii for EV agent)
|
|
279
|
+
"custom_metadata": getattr(agent, "_custom_metadata", {}),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def deserialize_agent_from_dict(
|
|
284
|
+
agent_cls,
|
|
285
|
+
data: Dict[str, Any],
|
|
286
|
+
agent_progress_callback: Optional[Callable] = None,
|
|
287
|
+
query_logging_callback: Optional[Callable] = None,
|
|
288
|
+
):
|
|
289
|
+
"""
|
|
290
|
+
Create an Agent instance from a dictionary.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
agent_cls: Agent class to instantiate
|
|
294
|
+
data: Serialized agent data
|
|
295
|
+
agent_progress_callback: Optional progress callback
|
|
296
|
+
query_logging_callback: Optional query logging callback
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Agent instance restored from data
|
|
300
|
+
"""
|
|
301
|
+
# Restore configurations
|
|
302
|
+
agent_config = AgentConfig.from_dict(data["agent_config"])
|
|
303
|
+
fallback_agent_config = (
|
|
304
|
+
AgentConfig.from_dict(data["fallback_agent_config"])
|
|
305
|
+
if data.get("fallback_agent_config")
|
|
306
|
+
else None
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Restore tools with error handling and fallback
|
|
310
|
+
try:
|
|
311
|
+
tools = deserialize_tools(data["tools"])
|
|
312
|
+
except Exception as e:
|
|
313
|
+
raise ValueError(f"❌ [AGENT_DESERIALIZE] Tool deserialization failed: {e}") from e
|
|
314
|
+
|
|
315
|
+
# Create agent instance
|
|
316
|
+
agent = agent_cls(
|
|
317
|
+
tools=tools,
|
|
318
|
+
agent_config=agent_config,
|
|
319
|
+
topic=data["topic"],
|
|
320
|
+
custom_instructions=data["custom_instructions"],
|
|
321
|
+
verbose=data["verbose"],
|
|
322
|
+
fallback_agent_config=fallback_agent_config,
|
|
323
|
+
workflow_cls=data["workflow_cls"],
|
|
324
|
+
agent_progress_callback=agent_progress_callback,
|
|
325
|
+
query_logging_callback=query_logging_callback,
|
|
326
|
+
vectara_api_key=data.get("vectara_api_key"),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Restore custom metadata (backward compatible)
|
|
330
|
+
# pylint: disable=protected-access
|
|
331
|
+
agent._custom_metadata = data.get("custom_metadata", {})
|
|
332
|
+
|
|
333
|
+
# Restore memory
|
|
334
|
+
mem = restore_memory_from_dict(data, token_limit=65536)
|
|
335
|
+
agent.memory = mem
|
|
336
|
+
|
|
337
|
+
# Keep inner agent (if already built) in sync
|
|
338
|
+
# pylint: disable=protected-access
|
|
339
|
+
if getattr(agent, "_agent", None) is not None:
|
|
340
|
+
agent._agent.memory = mem
|
|
341
|
+
# pylint: disable=protected-access
|
|
342
|
+
if getattr(agent, "_fallback_agent", None):
|
|
343
|
+
agent._fallback_agent.memory = mem
|
|
344
|
+
|
|
345
|
+
return agent
|