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.

Files changed (56) hide show
  1. tests/__init__.py +7 -0
  2. tests/conftest.py +316 -0
  3. tests/endpoint.py +54 -17
  4. tests/run_tests.py +112 -0
  5. tests/test_agent.py +35 -33
  6. tests/test_agent_fallback_memory.py +270 -0
  7. tests/test_agent_memory_consistency.py +229 -0
  8. tests/test_agent_type.py +86 -143
  9. tests/test_api_endpoint.py +4 -0
  10. tests/test_bedrock.py +50 -31
  11. tests/test_fallback.py +4 -0
  12. tests/test_gemini.py +27 -59
  13. tests/test_groq.py +50 -31
  14. tests/test_private_llm.py +11 -2
  15. tests/test_return_direct.py +6 -2
  16. tests/test_serialization.py +7 -6
  17. tests/test_session_memory.py +252 -0
  18. tests/test_streaming.py +109 -0
  19. tests/test_together.py +62 -0
  20. tests/test_tools.py +10 -82
  21. tests/test_vectara_llms.py +4 -0
  22. tests/test_vhc.py +67 -0
  23. tests/test_workflow.py +13 -28
  24. vectara_agentic/__init__.py +27 -4
  25. vectara_agentic/_callback.py +65 -67
  26. vectara_agentic/_observability.py +30 -30
  27. vectara_agentic/_version.py +1 -1
  28. vectara_agentic/agent.py +565 -859
  29. vectara_agentic/agent_config.py +15 -14
  30. vectara_agentic/agent_core/__init__.py +22 -0
  31. vectara_agentic/agent_core/factory.py +383 -0
  32. vectara_agentic/{_prompts.py → agent_core/prompts.py} +21 -46
  33. vectara_agentic/agent_core/serialization.py +348 -0
  34. vectara_agentic/agent_core/streaming.py +483 -0
  35. vectara_agentic/agent_core/utils/__init__.py +29 -0
  36. vectara_agentic/agent_core/utils/hallucination.py +157 -0
  37. vectara_agentic/agent_core/utils/logging.py +52 -0
  38. vectara_agentic/agent_core/utils/schemas.py +87 -0
  39. vectara_agentic/agent_core/utils/tools.py +125 -0
  40. vectara_agentic/agent_endpoint.py +4 -6
  41. vectara_agentic/db_tools.py +37 -12
  42. vectara_agentic/llm_utils.py +42 -43
  43. vectara_agentic/sub_query_workflow.py +9 -14
  44. vectara_agentic/tool_utils.py +138 -83
  45. vectara_agentic/tools.py +36 -21
  46. vectara_agentic/tools_catalog.py +16 -16
  47. vectara_agentic/types.py +106 -8
  48. {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/METADATA +111 -31
  49. vectara_agentic-0.4.1.dist-info/RECORD +53 -0
  50. tests/test_agent_planning.py +0 -64
  51. tests/test_hhem.py +0 -100
  52. vectara_agentic/hhem.py +0 -82
  53. vectara_agentic-0.3.3.dist-info/RECORD +0 -39
  54. {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/WHEEL +0 -0
  55. {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/licenses/LICENSE +0 -0
  56. {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,348 @@
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
+ # Restore session_id to match the memory's session_id
338
+ agent.session_id = mem.session_id
339
+
340
+ # Keep inner agent (if already built) in sync
341
+ # pylint: disable=protected-access
342
+ if getattr(agent, "_agent", None) is not None:
343
+ agent._agent.memory = mem
344
+ # pylint: disable=protected-access
345
+ if getattr(agent, "_fallback_agent", None):
346
+ agent._fallback_agent.memory = mem
347
+
348
+ return agent