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
vectara_agentic/agent.py
CHANGED
|
@@ -2,85 +2,70 @@
|
|
|
2
2
|
This module contains the Agent class for handling different types of agents and their interactions.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
8
|
+
|
|
9
|
+
# pylint: disable=wrong-import-position
|
|
10
|
+
from typing import List, Callable, Optional, Dict, Any, Tuple, TYPE_CHECKING
|
|
6
11
|
import os
|
|
7
|
-
import re
|
|
8
12
|
from datetime import date
|
|
9
|
-
import time
|
|
10
13
|
import json
|
|
11
14
|
import logging
|
|
12
15
|
import asyncio
|
|
13
|
-
import importlib
|
|
14
|
-
from collections import Counter
|
|
15
|
-
import inspect
|
|
16
|
-
from inspect import Signature, Parameter, ismethod
|
|
17
|
-
from pydantic import Field, create_model, ValidationError, BaseModel
|
|
18
|
-
from pydantic_core import PydanticUndefined
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
from pydantic import ValidationError
|
|
18
|
+
from pydantic_core import PydanticUndefined
|
|
21
19
|
|
|
22
20
|
from dotenv import load_dotenv
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
from llama_index.core.llms import
|
|
26
|
-
from llama_index.core.
|
|
27
|
-
from llama_index.core.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
from llama_index.core.
|
|
33
|
-
from llama_index.agent.
|
|
34
|
-
from llama_index.
|
|
35
|
-
|
|
36
|
-
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
|
37
|
-
from llama_index.agent.openai import OpenAIAgent
|
|
38
|
-
from llama_index.core.agent.runner.base import AgentRunner
|
|
39
|
-
from llama_index.core.agent.types import BaseAgent
|
|
40
|
-
from llama_index.core.workflow import Workflow, Context
|
|
22
|
+
# Runtime imports for components used at module level
|
|
23
|
+
from llama_index.core.llms import MessageRole, ChatMessage
|
|
24
|
+
from llama_index.core.callbacks import CallbackManager
|
|
25
|
+
from llama_index.core.memory import Memory
|
|
26
|
+
|
|
27
|
+
# Heavy llama_index imports moved to TYPE_CHECKING for lazy loading
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from llama_index.core.tools import FunctionTool
|
|
30
|
+
from llama_index.core.workflow import Workflow
|
|
31
|
+
from llama_index.core.agent.types import BaseAgent
|
|
32
|
+
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
|
33
|
+
|
|
41
34
|
|
|
42
35
|
from .types import (
|
|
43
36
|
AgentType,
|
|
44
37
|
AgentStatusType,
|
|
45
38
|
LLMRole,
|
|
46
|
-
ToolType,
|
|
47
39
|
ModelProvider,
|
|
48
40
|
AgentResponse,
|
|
49
41
|
AgentStreamingResponse,
|
|
50
42
|
AgentConfigType,
|
|
51
43
|
)
|
|
52
|
-
from .llm_utils import get_llm
|
|
53
|
-
from .
|
|
54
|
-
REACT_PROMPT_TEMPLATE,
|
|
55
|
-
GENERAL_PROMPT_TEMPLATE,
|
|
56
|
-
GENERAL_INSTRUCTIONS,
|
|
57
|
-
STRUCTURED_PLANNER_PLAN_REFINE_PROMPT,
|
|
58
|
-
STRUCTURED_PLANNER_INITIAL_PLAN_PROMPT,
|
|
59
|
-
)
|
|
44
|
+
from .llm_utils import get_llm
|
|
45
|
+
from .agent_core.prompts import GENERAL_INSTRUCTIONS
|
|
60
46
|
from ._callback import AgentCallbackHandler
|
|
61
|
-
from ._observability import setup_observer
|
|
62
|
-
from .tools import
|
|
63
|
-
from .tool_utils import _is_human_readable_output
|
|
47
|
+
from ._observability import setup_observer
|
|
48
|
+
from .tools import ToolsFactory
|
|
64
49
|
from .tools_catalog import get_current_date
|
|
65
50
|
from .agent_config import AgentConfig
|
|
66
|
-
from .hhem import HHEM
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class IgnoreUnpickleableAttributeFilter(logging.Filter):
|
|
70
|
-
"""
|
|
71
|
-
Filter to ignore log messages that contain certain strings
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
def filter(self, record):
|
|
75
|
-
msgs_to_ignore = [
|
|
76
|
-
"Removing unpickleable private attribute _chunking_tokenizer_fn",
|
|
77
|
-
"Removing unpickleable private attribute _split_fns",
|
|
78
|
-
"Removing unpickleable private attribute _sub_sentence_split_fns",
|
|
79
|
-
]
|
|
80
|
-
return all(msg not in record.getMessage() for msg in msgs_to_ignore)
|
|
81
51
|
|
|
52
|
+
# Import utilities from agent core modules
|
|
53
|
+
from .agent_core.streaming import (
|
|
54
|
+
FunctionCallingStreamHandler,
|
|
55
|
+
execute_post_stream_processing,
|
|
56
|
+
)
|
|
57
|
+
from .agent_core.factory import create_agent_from_config, create_agent_from_corpus
|
|
58
|
+
from .agent_core.serialization import (
|
|
59
|
+
serialize_agent_to_dict,
|
|
60
|
+
deserialize_agent_from_dict,
|
|
61
|
+
)
|
|
62
|
+
from .agent_core.utils import (
|
|
63
|
+
sanitize_tools_for_gemini,
|
|
64
|
+
setup_agent_logging,
|
|
65
|
+
)
|
|
66
|
+
from .agent_core.utils.tools import validate_tool_consistency
|
|
82
67
|
|
|
83
|
-
|
|
68
|
+
setup_agent_logging()
|
|
84
69
|
|
|
85
70
|
logger = logging.getLogger("opentelemetry.exporter.otlp.proto.http.trace_exporter")
|
|
86
71
|
logger.setLevel(logging.CRITICAL)
|
|
@@ -88,113 +73,6 @@ logger.setLevel(logging.CRITICAL)
|
|
|
88
73
|
load_dotenv(override=True)
|
|
89
74
|
|
|
90
75
|
|
|
91
|
-
def _get_prompt(
|
|
92
|
-
prompt_template: str,
|
|
93
|
-
general_instructions: str,
|
|
94
|
-
topic: str,
|
|
95
|
-
custom_instructions: str,
|
|
96
|
-
):
|
|
97
|
-
"""
|
|
98
|
-
Generate a prompt by replacing placeholders with topic and date.
|
|
99
|
-
|
|
100
|
-
Args:
|
|
101
|
-
prompt_template (str): The template for the prompt.
|
|
102
|
-
general_instructions (str): General instructions to be included in the prompt.
|
|
103
|
-
topic (str): The topic to be included in the prompt.
|
|
104
|
-
custom_instructions(str): The custom instructions to be included in the prompt.
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
str: The formatted prompt.
|
|
108
|
-
"""
|
|
109
|
-
return (
|
|
110
|
-
prompt_template.replace("{chat_topic}", topic)
|
|
111
|
-
.replace("{today}", date.today().strftime("%A, %B %d, %Y"))
|
|
112
|
-
.replace("{custom_instructions}", custom_instructions)
|
|
113
|
-
.replace("{INSTRUCTIONS}", general_instructions)
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _get_llm_compiler_prompt(
|
|
118
|
-
prompt: str, general_instructions: str, topic: str, custom_instructions: str
|
|
119
|
-
) -> str:
|
|
120
|
-
"""
|
|
121
|
-
Add custom instructions to the prompt.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
prompt (str): The prompt to which custom instructions should be added.
|
|
125
|
-
|
|
126
|
-
Returns:
|
|
127
|
-
str: The prompt with custom instructions added.
|
|
128
|
-
"""
|
|
129
|
-
prompt += "\nAdditional Instructions:\n"
|
|
130
|
-
prompt += f"You have experise in {topic}.\n"
|
|
131
|
-
prompt += general_instructions
|
|
132
|
-
prompt += custom_instructions
|
|
133
|
-
prompt += f"Today is {date.today().strftime('%A, %B %d, %Y')}"
|
|
134
|
-
return prompt
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def get_field_type(field_schema: dict) -> Any:
|
|
138
|
-
"""
|
|
139
|
-
Convert a JSON schema field definition to a Python type.
|
|
140
|
-
Handles 'type' and 'anyOf' cases.
|
|
141
|
-
"""
|
|
142
|
-
json_type_to_python = {
|
|
143
|
-
"string": str,
|
|
144
|
-
"integer": int,
|
|
145
|
-
"boolean": bool,
|
|
146
|
-
"array": list,
|
|
147
|
-
"object": dict,
|
|
148
|
-
"number": float,
|
|
149
|
-
"null": type(None),
|
|
150
|
-
}
|
|
151
|
-
if not field_schema: # Handles empty schema {}
|
|
152
|
-
return Any
|
|
153
|
-
|
|
154
|
-
if "anyOf" in field_schema:
|
|
155
|
-
types = []
|
|
156
|
-
for option_schema in field_schema["anyOf"]:
|
|
157
|
-
types.append(get_field_type(option_schema)) # Recursive call
|
|
158
|
-
if not types:
|
|
159
|
-
return Any
|
|
160
|
-
return Union[tuple(types)]
|
|
161
|
-
|
|
162
|
-
if "type" in field_schema and isinstance(field_schema["type"], list):
|
|
163
|
-
types = []
|
|
164
|
-
for type_name in field_schema["type"]:
|
|
165
|
-
if type_name == "array":
|
|
166
|
-
item_schema = field_schema.get("items", {})
|
|
167
|
-
types.append(List[get_field_type(item_schema)])
|
|
168
|
-
elif type_name in json_type_to_python:
|
|
169
|
-
types.append(json_type_to_python[type_name])
|
|
170
|
-
else:
|
|
171
|
-
types.append(Any) # Fallback for unknown types in the list
|
|
172
|
-
if not types:
|
|
173
|
-
return Any
|
|
174
|
-
return Union[tuple(types)] # type: ignore
|
|
175
|
-
|
|
176
|
-
if "type" in field_schema:
|
|
177
|
-
schema_type_name = field_schema["type"]
|
|
178
|
-
if schema_type_name == "array":
|
|
179
|
-
item_schema = field_schema.get(
|
|
180
|
-
"items", {}
|
|
181
|
-
) # Default to Any if "items" is missing
|
|
182
|
-
return List[get_field_type(item_schema)]
|
|
183
|
-
|
|
184
|
-
return json_type_to_python.get(schema_type_name, Any)
|
|
185
|
-
|
|
186
|
-
# If only "items" is present (implies array by some conventions, but less standard)
|
|
187
|
-
# Or if it's a schema with other keywords like 'properties' (implying object)
|
|
188
|
-
# For simplicity, if no "type" or "anyOf" at this point, default to Any or add more specific handling.
|
|
189
|
-
# If 'properties' in field_schema or 'additionalProperties' in field_schema, it's likely an object.
|
|
190
|
-
if "properties" in field_schema or "additionalProperties" in field_schema:
|
|
191
|
-
# This path might need to reconstruct a nested Pydantic model if you encounter such schemas.
|
|
192
|
-
# For now, treating as 'dict' or 'Any' might be a simpler placeholder.
|
|
193
|
-
return dict # Or Any, or more sophisticated object reconstruction.
|
|
194
|
-
|
|
195
|
-
return Any
|
|
196
|
-
|
|
197
|
-
|
|
198
76
|
class Agent:
|
|
199
77
|
"""
|
|
200
78
|
Agent class for handling different types of agents and their interactions.
|
|
@@ -202,13 +80,11 @@ class Agent:
|
|
|
202
80
|
|
|
203
81
|
def __init__(
|
|
204
82
|
self,
|
|
205
|
-
tools: List[FunctionTool],
|
|
83
|
+
tools: List["FunctionTool"],
|
|
206
84
|
topic: str = "general",
|
|
207
85
|
custom_instructions: str = "",
|
|
208
86
|
general_instructions: str = GENERAL_INSTRUCTIONS,
|
|
209
|
-
verbose: bool =
|
|
210
|
-
use_structured_planning: bool = False,
|
|
211
|
-
update_func: Optional[Callable[[AgentStatusType, dict, str], None]] = None,
|
|
87
|
+
verbose: bool = False,
|
|
212
88
|
agent_progress_callback: Optional[
|
|
213
89
|
Callable[[AgentStatusType, dict, str], None]
|
|
214
90
|
] = None,
|
|
@@ -217,9 +93,10 @@ class Agent:
|
|
|
217
93
|
fallback_agent_config: Optional[AgentConfig] = None,
|
|
218
94
|
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
219
95
|
validate_tools: bool = False,
|
|
220
|
-
workflow_cls: Optional[Workflow] = None,
|
|
96
|
+
workflow_cls: Optional["Workflow"] = None,
|
|
221
97
|
workflow_timeout: int = 120,
|
|
222
98
|
vectara_api_key: Optional[str] = None,
|
|
99
|
+
session_id: Optional[str] = None,
|
|
223
100
|
) -> None:
|
|
224
101
|
"""
|
|
225
102
|
Initialize the agent with the specified type, tools, topic, and system message.
|
|
@@ -232,11 +109,8 @@ class Agent:
|
|
|
232
109
|
general_instructions (str, optional): General instructions for the agent.
|
|
233
110
|
The Agent has a default set of instructions that are crafted to help it operate effectively.
|
|
234
111
|
This allows you to customize the agent's behavior and personality, but use with caution.
|
|
235
|
-
verbose (bool, optional): Whether the agent should print its steps. Defaults to
|
|
236
|
-
use_structured_planning (bool, optional)
|
|
237
|
-
Whether or not we want to wrap the agent with LlamaIndex StructuredPlannerAgent.
|
|
112
|
+
verbose (bool, optional): Whether the agent should print its steps. Defaults to False.
|
|
238
113
|
agent_progress_callback (Callable): A callback function the code calls on any agent updates.
|
|
239
|
-
update_func (Callable): old name for agent_progress_callback. Will be deprecated in future.
|
|
240
114
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
241
115
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
242
116
|
Defaults to AgentConfig(), which reads from environment variables.
|
|
@@ -247,103 +121,61 @@ class Agent:
|
|
|
247
121
|
Defaults to False.
|
|
248
122
|
workflow_cls (Workflow, optional): The workflow class to be used with run(). Defaults to None.
|
|
249
123
|
workflow_timeout (int, optional): The timeout for the workflow in seconds. Defaults to 120.
|
|
250
|
-
vectara_api_key (str, optional): The Vectara API key for
|
|
124
|
+
vectara_api_key (str, optional): The Vectara API key for VHC computation. Defaults to None.
|
|
125
|
+
session_id (str, optional): The session ID for memory persistence.
|
|
126
|
+
If None, auto-generates from topic and date. Defaults to None.
|
|
251
127
|
"""
|
|
252
128
|
self.agent_config = agent_config or AgentConfig()
|
|
253
129
|
self.agent_config_type = AgentConfigType.DEFAULT
|
|
254
130
|
self.tools = tools
|
|
255
131
|
if not any(tool.metadata.name == "get_current_date" for tool in self.tools):
|
|
256
|
-
self.tools += [
|
|
132
|
+
self.tools += [
|
|
133
|
+
ToolsFactory().create_tool(get_current_date, vhc_eligible=False)
|
|
134
|
+
]
|
|
257
135
|
self.agent_type = self.agent_config.agent_type
|
|
258
|
-
self.use_structured_planning = use_structured_planning
|
|
259
136
|
self._llm = None # Lazy loading
|
|
260
137
|
self._custom_instructions = custom_instructions
|
|
261
138
|
self._general_instructions = general_instructions
|
|
262
139
|
self._topic = topic
|
|
263
|
-
self.agent_progress_callback =
|
|
264
|
-
agent_progress_callback if agent_progress_callback else update_func
|
|
265
|
-
)
|
|
266
|
-
self.query_logging_callback = query_logging_callback
|
|
140
|
+
self.agent_progress_callback = agent_progress_callback
|
|
267
141
|
|
|
142
|
+
self.query_logging_callback = query_logging_callback
|
|
268
143
|
self.workflow_cls = workflow_cls
|
|
269
144
|
self.workflow_timeout = workflow_timeout
|
|
270
145
|
self.vectara_api_key = vectara_api_key or os.environ.get("VECTARA_API_KEY", "")
|
|
271
146
|
|
|
272
147
|
# Sanitize tools for Gemini if needed
|
|
273
148
|
if self.agent_config.main_llm_provider == ModelProvider.GEMINI:
|
|
274
|
-
self.tools =
|
|
149
|
+
self.tools = sanitize_tools_for_gemini(self.tools)
|
|
275
150
|
|
|
276
151
|
# Validate tools
|
|
277
|
-
# Check for:
|
|
278
|
-
# 1. multiple copies of the same tool
|
|
279
|
-
# 2. Instructions for using tools that do not exist
|
|
280
|
-
tool_names = [tool.metadata.name for tool in self.tools]
|
|
281
|
-
duplicates = [tool for tool, count in Counter(tool_names).items() if count > 1]
|
|
282
|
-
if duplicates:
|
|
283
|
-
raise ValueError(f"Duplicate tools detected: {', '.join(duplicates)}")
|
|
284
|
-
|
|
285
152
|
if validate_tools:
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
And these instructions:
|
|
290
|
-
<instructions>
|
|
291
|
-
{self._custom_instructions}
|
|
292
|
-
</instructions>
|
|
293
|
-
Your task is to identify invalid tools.
|
|
294
|
-
A tool is invalid if it is mentioned in the instructions but not in the tools list.
|
|
295
|
-
A tool's name must have at least two characters.
|
|
296
|
-
Your response should be a comma-separated list of the invalid tools.
|
|
297
|
-
If no invalid tools exist, respond with "<OKAY>" (and nothing else).
|
|
298
|
-
"""
|
|
299
|
-
llm = get_llm(LLMRole.MAIN, config=self.agent_config)
|
|
300
|
-
bad_tools_str = llm.complete(prompt).text.strip("\n")
|
|
301
|
-
if bad_tools_str and bad_tools_str != "<OKAY>":
|
|
302
|
-
bad_tools = [tool.strip() for tool in bad_tools_str.split(",")]
|
|
303
|
-
numbered = ", ".join(
|
|
304
|
-
f"({i}) {tool}" for i, tool in enumerate(bad_tools, 1)
|
|
305
|
-
)
|
|
306
|
-
raise ValueError(
|
|
307
|
-
f"The Agent custom instructions mention these invalid tools: {numbered}"
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
# Create token counters for the main and tool LLMs
|
|
311
|
-
main_tok = get_tokenizer_for_model(role=LLMRole.MAIN)
|
|
312
|
-
self.main_token_counter = (
|
|
313
|
-
TokenCountingHandler(tokenizer=main_tok) if main_tok else None
|
|
314
|
-
)
|
|
315
|
-
tool_tok = get_tokenizer_for_model(role=LLMRole.TOOL)
|
|
316
|
-
self.tool_token_counter = (
|
|
317
|
-
TokenCountingHandler(tokenizer=tool_tok) if tool_tok else None
|
|
318
|
-
)
|
|
153
|
+
validate_tool_consistency(
|
|
154
|
+
self.tools, self._custom_instructions, self.agent_config
|
|
155
|
+
)
|
|
319
156
|
|
|
320
157
|
# Setup callback manager
|
|
321
158
|
callbacks: list[BaseCallbackHandler] = [
|
|
322
159
|
AgentCallbackHandler(self.agent_progress_callback)
|
|
323
160
|
]
|
|
324
|
-
if self.main_token_counter:
|
|
325
|
-
callbacks.append(self.main_token_counter)
|
|
326
|
-
if self.tool_token_counter:
|
|
327
|
-
callbacks.append(self.tool_token_counter)
|
|
328
161
|
self.callback_manager = CallbackManager(callbacks) # type: ignore
|
|
329
162
|
self.verbose = verbose
|
|
330
163
|
|
|
164
|
+
self.session_id = (
|
|
165
|
+
session_id
|
|
166
|
+
or getattr(self, "session_id", None)
|
|
167
|
+
or f"{topic}:{date.today().isoformat()}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self.memory = Memory.from_defaults(
|
|
171
|
+
session_id=self.session_id, token_limit=65536
|
|
172
|
+
)
|
|
331
173
|
if chat_history:
|
|
332
|
-
|
|
333
|
-
for
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
msg_history.append(
|
|
338
|
-
ChatMessage.from_str(
|
|
339
|
-
content=text_pairs[1], role=MessageRole.ASSISTANT
|
|
340
|
-
)
|
|
341
|
-
)
|
|
342
|
-
self.memory = ChatMemoryBuffer.from_defaults(
|
|
343
|
-
token_limit=128000, chat_history=msg_history
|
|
344
|
-
)
|
|
345
|
-
else:
|
|
346
|
-
self.memory = ChatMemoryBuffer.from_defaults(token_limit=128000)
|
|
174
|
+
msgs = []
|
|
175
|
+
for u, a in chat_history:
|
|
176
|
+
msgs.append(ChatMessage.from_str(u, role=MessageRole.USER))
|
|
177
|
+
msgs.append(ChatMessage.from_str(a, role=MessageRole.ASSISTANT))
|
|
178
|
+
self.memory.put_messages(msgs)
|
|
347
179
|
|
|
348
180
|
# Set up main agent and fallback agent
|
|
349
181
|
self._agent = None # Lazy loading
|
|
@@ -354,9 +186,15 @@ class Agent:
|
|
|
354
186
|
try:
|
|
355
187
|
self.observability_enabled = setup_observer(self.agent_config, self.verbose)
|
|
356
188
|
except Exception as e:
|
|
357
|
-
|
|
189
|
+
logger.warning(f"Failed to set up observer ({e}), ignoring")
|
|
358
190
|
self.observability_enabled = False
|
|
359
191
|
|
|
192
|
+
# VHC state tracking
|
|
193
|
+
self._vhc_cache = {} # Cache VHC results by query hash
|
|
194
|
+
self._last_query = None
|
|
195
|
+
self._last_response = None
|
|
196
|
+
self._current_tool_outputs = [] # Store tool outputs from current query for VHC
|
|
197
|
+
|
|
360
198
|
@property
|
|
361
199
|
def llm(self):
|
|
362
200
|
"""Lazy-loads the LLM."""
|
|
@@ -380,231 +218,56 @@ class Agent:
|
|
|
380
218
|
)
|
|
381
219
|
return self._fallback_agent
|
|
382
220
|
|
|
383
|
-
def _sanitize_tools_for_gemini(
|
|
384
|
-
self, tools: list[FunctionTool]
|
|
385
|
-
) -> list[FunctionTool]:
|
|
386
|
-
"""
|
|
387
|
-
Strip all default values from:
|
|
388
|
-
- tool.fn
|
|
389
|
-
- tool.async_fn
|
|
390
|
-
- tool.metadata.fn_schema
|
|
391
|
-
so Gemini sees *only* required parameters, no defaults.
|
|
392
|
-
"""
|
|
393
|
-
for tool in tools:
|
|
394
|
-
# 1) strip defaults off the actual callables
|
|
395
|
-
for func in (tool.fn, tool.async_fn):
|
|
396
|
-
if not func:
|
|
397
|
-
continue
|
|
398
|
-
orig_sig = inspect.signature(func)
|
|
399
|
-
new_params = [
|
|
400
|
-
p.replace(default=Parameter.empty)
|
|
401
|
-
for p in orig_sig.parameters.values()
|
|
402
|
-
]
|
|
403
|
-
new_sig = Signature(
|
|
404
|
-
new_params, return_annotation=orig_sig.return_annotation
|
|
405
|
-
)
|
|
406
|
-
if ismethod(func):
|
|
407
|
-
func.__func__.__signature__ = new_sig
|
|
408
|
-
else:
|
|
409
|
-
func.__signature__ = new_sig
|
|
410
|
-
|
|
411
|
-
# 2) rebuild the Pydantic schema so that *every* field is required
|
|
412
|
-
schema_cls = getattr(tool.metadata, "fn_schema", None)
|
|
413
|
-
if schema_cls and hasattr(schema_cls, "model_fields"):
|
|
414
|
-
# collect (name → (type, Field(...))) for all fields
|
|
415
|
-
new_fields: dict[str, tuple[type, Any]] = {}
|
|
416
|
-
for name, mf in schema_cls.model_fields.items():
|
|
417
|
-
typ = mf.annotation
|
|
418
|
-
desc = getattr(mf, "description", "")
|
|
419
|
-
# force required (no default) with Field(...)
|
|
420
|
-
new_fields[name] = (typ, Field(..., description=desc))
|
|
421
|
-
|
|
422
|
-
# make a brand-new schema class where every field is required
|
|
423
|
-
no_default_schema = create_model(
|
|
424
|
-
f"{schema_cls.__name__}", # new class name
|
|
425
|
-
**new_fields, # type: ignore
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
# give it a clean __signature__ so inspect.signature sees no defaults
|
|
429
|
-
params = [
|
|
430
|
-
Parameter(n, Parameter.POSITIONAL_OR_KEYWORD, annotation=typ)
|
|
431
|
-
for n, (typ, _) in new_fields.items()
|
|
432
|
-
]
|
|
433
|
-
no_default_schema.__signature__ = Signature(params)
|
|
434
|
-
|
|
435
|
-
# swap it back onto the tool
|
|
436
|
-
tool.metadata.fn_schema = no_default_schema
|
|
437
|
-
|
|
438
|
-
return tools
|
|
439
|
-
|
|
440
221
|
def _create_agent(
|
|
441
|
-
self, config: AgentConfig, llm_callback_manager: CallbackManager
|
|
442
|
-
) ->
|
|
222
|
+
self, config: AgentConfig, llm_callback_manager: "CallbackManager"
|
|
223
|
+
) -> "BaseAgent":
|
|
443
224
|
"""
|
|
444
225
|
Creates the agent based on the configuration object.
|
|
445
226
|
|
|
446
227
|
Args:
|
|
447
|
-
|
|
448
228
|
config: The configuration of the agent.
|
|
449
229
|
llm_callback_manager: The callback manager for the agent's llm.
|
|
450
230
|
|
|
451
231
|
Returns:
|
|
452
|
-
|
|
232
|
+
BaseAgent: The configured agent object.
|
|
453
233
|
"""
|
|
454
|
-
agent_type = config.agent_type
|
|
455
234
|
# Use the same LLM instance for consistency
|
|
456
|
-
llm =
|
|
235
|
+
llm = (
|
|
236
|
+
self.llm
|
|
237
|
+
if config == self.agent_config
|
|
238
|
+
else get_llm(LLMRole.MAIN, config=config)
|
|
239
|
+
)
|
|
457
240
|
llm.callback_manager = llm_callback_manager
|
|
458
241
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
agent = FunctionCallingAgent.from_tools(
|
|
471
|
-
tools=self.tools,
|
|
472
|
-
llm=llm,
|
|
473
|
-
memory=self.memory,
|
|
474
|
-
verbose=self.verbose,
|
|
475
|
-
max_function_calls=config.max_reasoning_steps,
|
|
476
|
-
callback_manager=llm_callback_manager,
|
|
477
|
-
system_prompt=prompt,
|
|
478
|
-
allow_parallel_tool_calls=True,
|
|
479
|
-
)
|
|
480
|
-
elif agent_type == AgentType.REACT:
|
|
481
|
-
prompt = _get_prompt(
|
|
482
|
-
REACT_PROMPT_TEMPLATE,
|
|
483
|
-
self._general_instructions,
|
|
484
|
-
self._topic,
|
|
485
|
-
self._custom_instructions,
|
|
486
|
-
)
|
|
487
|
-
agent = ReActAgent.from_tools(
|
|
488
|
-
tools=self.tools,
|
|
489
|
-
llm=llm,
|
|
490
|
-
memory=self.memory,
|
|
491
|
-
verbose=self.verbose,
|
|
492
|
-
react_chat_formatter=ReActChatFormatter(system_header=prompt),
|
|
493
|
-
max_iterations=config.max_reasoning_steps,
|
|
494
|
-
callable_manager=llm_callback_manager,
|
|
495
|
-
)
|
|
496
|
-
elif agent_type == AgentType.OPENAI:
|
|
497
|
-
if config.tool_llm_provider != ModelProvider.OPENAI:
|
|
498
|
-
raise ValueError(
|
|
499
|
-
"Vectara-agentic: OPENAI agent type requires the OpenAI LLM."
|
|
500
|
-
)
|
|
501
|
-
prompt = _get_prompt(
|
|
502
|
-
GENERAL_PROMPT_TEMPLATE,
|
|
503
|
-
self._general_instructions,
|
|
504
|
-
self._topic,
|
|
505
|
-
self._custom_instructions,
|
|
506
|
-
)
|
|
507
|
-
agent = OpenAIAgent.from_tools(
|
|
508
|
-
tools=self.tools,
|
|
509
|
-
llm=llm,
|
|
510
|
-
memory=self.memory,
|
|
511
|
-
verbose=self.verbose,
|
|
512
|
-
callback_manager=llm_callback_manager,
|
|
513
|
-
max_function_calls=config.max_reasoning_steps,
|
|
514
|
-
system_prompt=prompt,
|
|
515
|
-
)
|
|
516
|
-
elif agent_type == AgentType.LLMCOMPILER:
|
|
517
|
-
agent_worker = LLMCompilerAgentWorker.from_tools(
|
|
518
|
-
tools=self.tools,
|
|
519
|
-
llm=llm,
|
|
520
|
-
verbose=self.verbose,
|
|
521
|
-
callback_manager=llm_callback_manager,
|
|
522
|
-
)
|
|
523
|
-
agent_worker.system_prompt = _get_prompt(
|
|
524
|
-
prompt_template=_get_llm_compiler_prompt(
|
|
525
|
-
prompt=agent_worker.system_prompt,
|
|
526
|
-
general_instructions=self._general_instructions,
|
|
527
|
-
topic=self._topic,
|
|
528
|
-
custom_instructions=self._custom_instructions,
|
|
529
|
-
),
|
|
530
|
-
general_instructions=self._general_instructions,
|
|
531
|
-
topic=self._topic,
|
|
532
|
-
custom_instructions=self._custom_instructions,
|
|
533
|
-
)
|
|
534
|
-
agent_worker.system_prompt_replan = _get_prompt(
|
|
535
|
-
prompt_template=_get_llm_compiler_prompt(
|
|
536
|
-
prompt=agent_worker.system_prompt_replan,
|
|
537
|
-
general_instructions=GENERAL_INSTRUCTIONS,
|
|
538
|
-
topic=self._topic,
|
|
539
|
-
custom_instructions=self._custom_instructions,
|
|
540
|
-
),
|
|
541
|
-
general_instructions=GENERAL_INSTRUCTIONS,
|
|
542
|
-
topic=self._topic,
|
|
543
|
-
custom_instructions=self._custom_instructions,
|
|
544
|
-
)
|
|
545
|
-
agent = agent_worker.as_agent()
|
|
546
|
-
elif agent_type == AgentType.LATS:
|
|
547
|
-
agent_worker = LATSAgentWorker.from_tools(
|
|
548
|
-
tools=self.tools,
|
|
549
|
-
llm=llm,
|
|
550
|
-
num_expansions=3,
|
|
551
|
-
max_rollouts=-1,
|
|
552
|
-
verbose=self.verbose,
|
|
553
|
-
callback_manager=llm_callback_manager,
|
|
554
|
-
)
|
|
555
|
-
prompt = _get_prompt(
|
|
556
|
-
REACT_PROMPT_TEMPLATE,
|
|
557
|
-
self._general_instructions,
|
|
558
|
-
self._topic,
|
|
559
|
-
self._custom_instructions,
|
|
560
|
-
)
|
|
561
|
-
agent_worker.chat_formatter = ReActChatFormatter(system_header=prompt)
|
|
562
|
-
agent = agent_worker.as_agent()
|
|
563
|
-
else:
|
|
564
|
-
raise ValueError(f"Unknown agent type: {agent_type}")
|
|
565
|
-
|
|
566
|
-
# Set up structured planner if needed
|
|
567
|
-
if self.use_structured_planning or self.agent_type in [
|
|
568
|
-
AgentType.LLMCOMPILER,
|
|
569
|
-
AgentType.LATS,
|
|
570
|
-
]:
|
|
571
|
-
planner_llm = get_llm(LLMRole.TOOL, config=config)
|
|
572
|
-
agent = StructuredPlannerAgent(
|
|
573
|
-
agent_worker=agent.agent_worker,
|
|
574
|
-
tools=self.tools,
|
|
575
|
-
llm=planner_llm,
|
|
576
|
-
memory=self.memory,
|
|
577
|
-
verbose=self.verbose,
|
|
578
|
-
initial_plan_prompt=STRUCTURED_PLANNER_INITIAL_PLAN_PROMPT,
|
|
579
|
-
plan_refine_prompt=STRUCTURED_PLANNER_PLAN_REFINE_PROMPT,
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
return agent
|
|
242
|
+
return create_agent_from_config(
|
|
243
|
+
tools=self.tools,
|
|
244
|
+
llm=llm,
|
|
245
|
+
memory=self.memory,
|
|
246
|
+
config=config,
|
|
247
|
+
callback_manager=llm_callback_manager,
|
|
248
|
+
general_instructions=self._general_instructions,
|
|
249
|
+
topic=self._topic,
|
|
250
|
+
custom_instructions=self._custom_instructions,
|
|
251
|
+
verbose=self.verbose,
|
|
252
|
+
)
|
|
583
253
|
|
|
584
254
|
def clear_memory(self) -> None:
|
|
585
|
-
"""
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
elif (
|
|
591
|
-
self.agent_config_type == AgentConfigType.FALLBACK
|
|
592
|
-
and self.fallback_agent_config
|
|
593
|
-
):
|
|
594
|
-
self.fallback_agent.memory.reset()
|
|
595
|
-
else:
|
|
596
|
-
raise ValueError(f"Invalid agent config type {self.agent_config_type}")
|
|
255
|
+
"""Clear the agent's memory and reset agent instances to ensure consistency."""
|
|
256
|
+
self.memory.reset()
|
|
257
|
+
# Clear agent instances so they get recreated with the cleared memory
|
|
258
|
+
self._agent = None
|
|
259
|
+
self._fallback_agent = None
|
|
597
260
|
|
|
598
261
|
def __eq__(self, other):
|
|
599
262
|
if not isinstance(other, Agent):
|
|
600
|
-
|
|
263
|
+
logger.debug(
|
|
601
264
|
f"Comparison failed: other is not an instance of Agent. (self: {type(self)}, other: {type(other)})"
|
|
602
265
|
)
|
|
603
266
|
return False
|
|
604
267
|
|
|
605
268
|
# Compare agent_type
|
|
606
269
|
if self.agent_config.agent_type != other.agent_config.agent_type:
|
|
607
|
-
|
|
270
|
+
logger.debug(
|
|
608
271
|
f"Comparison failed: agent_type differs. (self.agent_config.agent_type: {self.agent_config.agent_type},"
|
|
609
272
|
f" other.agent_config.agent_type: {other.agent_config.agent_type})"
|
|
610
273
|
)
|
|
@@ -612,7 +275,7 @@ class Agent:
|
|
|
612
275
|
|
|
613
276
|
# Compare tools
|
|
614
277
|
if self.tools != other.tools:
|
|
615
|
-
|
|
278
|
+
logger.debug(
|
|
616
279
|
"Comparison failed: tools differ."
|
|
617
280
|
f"(self.tools: {[t.metadata.name for t in self.tools]}, "
|
|
618
281
|
f"other.tools: {[t.metadata.name for t in other.tools]})"
|
|
@@ -621,14 +284,14 @@ class Agent:
|
|
|
621
284
|
|
|
622
285
|
# Compare topic
|
|
623
286
|
if self._topic != other._topic:
|
|
624
|
-
|
|
287
|
+
logger.debug(
|
|
625
288
|
f"Comparison failed: topic differs. (self.topic: {self._topic}, other.topic: {other._topic})"
|
|
626
289
|
)
|
|
627
290
|
return False
|
|
628
291
|
|
|
629
292
|
# Compare custom_instructions
|
|
630
293
|
if self._custom_instructions != other._custom_instructions:
|
|
631
|
-
|
|
294
|
+
logger.debug(
|
|
632
295
|
"Comparison failed: custom_instructions differ. (self.custom_instructions: "
|
|
633
296
|
f"{self._custom_instructions}, other.custom_instructions: {other._custom_instructions})"
|
|
634
297
|
)
|
|
@@ -636,31 +299,27 @@ class Agent:
|
|
|
636
299
|
|
|
637
300
|
# Compare verbose
|
|
638
301
|
if self.verbose != other.verbose:
|
|
639
|
-
|
|
302
|
+
logger.debug(
|
|
640
303
|
f"Comparison failed: verbose differs. (self.verbose: {self.verbose}, other.verbose: {other.verbose})"
|
|
641
304
|
)
|
|
642
305
|
return False
|
|
643
306
|
|
|
644
307
|
# Compare agent memory
|
|
645
|
-
if self.
|
|
646
|
-
|
|
647
|
-
f"Comparison failed: agent memory differs. (self.agent: {repr(self.agent.memory.chat_store)}, "
|
|
648
|
-
f"other.agent: {repr(other.agent.memory.chat_store)})"
|
|
649
|
-
)
|
|
308
|
+
if self.memory.get() != other.memory.get():
|
|
309
|
+
logger.debug("Comparison failed: agent memory differs.")
|
|
650
310
|
return False
|
|
651
311
|
|
|
652
312
|
# If all comparisons pass
|
|
653
|
-
|
|
313
|
+
logger.debug("All comparisons passed. Objects are equal.")
|
|
654
314
|
return True
|
|
655
315
|
|
|
656
316
|
@classmethod
|
|
657
317
|
def from_tools(
|
|
658
318
|
cls,
|
|
659
|
-
tools: List[FunctionTool],
|
|
319
|
+
tools: List["FunctionTool"],
|
|
660
320
|
topic: str = "general",
|
|
661
321
|
custom_instructions: str = "",
|
|
662
322
|
verbose: bool = True,
|
|
663
|
-
update_func: Optional[Callable[[AgentStatusType, dict, str], None]] = None,
|
|
664
323
|
agent_progress_callback: Optional[
|
|
665
324
|
Callable[[AgentStatusType, dict, str], None]
|
|
666
325
|
] = None,
|
|
@@ -669,8 +328,9 @@ class Agent:
|
|
|
669
328
|
validate_tools: bool = False,
|
|
670
329
|
fallback_agent_config: Optional[AgentConfig] = None,
|
|
671
330
|
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
672
|
-
workflow_cls: Optional[Workflow] = None,
|
|
331
|
+
workflow_cls: Optional["Workflow"] = None,
|
|
673
332
|
workflow_timeout: int = 120,
|
|
333
|
+
session_id: Optional[str] = None,
|
|
674
334
|
) -> "Agent":
|
|
675
335
|
"""
|
|
676
336
|
Create an agent from tools, agent type, and language model.
|
|
@@ -682,7 +342,6 @@ class Agent:
|
|
|
682
342
|
custom_instructions (str, optional): custom instructions for the agent. Defaults to ''.
|
|
683
343
|
verbose (bool, optional): Whether the agent should print its steps. Defaults to True.
|
|
684
344
|
agent_progress_callback (Callable): A callback function the code calls on any agent updates.
|
|
685
|
-
update_func (Callable): old name for agent_progress_callback. Will be deprecated in future.
|
|
686
345
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
687
346
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
688
347
|
fallback_agent_config (AgentConfig, optional): The fallback configuration of the agent.
|
|
@@ -691,6 +350,8 @@ class Agent:
|
|
|
691
350
|
Defaults to False.
|
|
692
351
|
workflow_cls (Workflow, optional): The workflow class to be used with run(). Defaults to None.
|
|
693
352
|
workflow_timeout (int, optional): The timeout for the workflow in seconds. Defaults to 120.
|
|
353
|
+
session_id (str, optional): The session ID for memory persistence.
|
|
354
|
+
If None, auto-generates from topic and date. Defaults to None.
|
|
694
355
|
|
|
695
356
|
Returns:
|
|
696
357
|
Agent: An instance of the Agent class.
|
|
@@ -702,13 +363,13 @@ class Agent:
|
|
|
702
363
|
verbose=verbose,
|
|
703
364
|
agent_progress_callback=agent_progress_callback,
|
|
704
365
|
query_logging_callback=query_logging_callback,
|
|
705
|
-
update_func=update_func,
|
|
706
366
|
agent_config=agent_config,
|
|
707
367
|
chat_history=chat_history,
|
|
708
368
|
validate_tools=validate_tools,
|
|
709
369
|
fallback_agent_config=fallback_agent_config,
|
|
710
370
|
workflow_cls=workflow_cls,
|
|
711
371
|
workflow_timeout=workflow_timeout,
|
|
372
|
+
session_id=session_id,
|
|
712
373
|
)
|
|
713
374
|
|
|
714
375
|
@classmethod
|
|
@@ -753,141 +414,78 @@ class Agent:
|
|
|
753
414
|
vectara_presence_penalty: Optional[float] = None,
|
|
754
415
|
vectara_save_history: bool = True,
|
|
755
416
|
return_direct: bool = False,
|
|
417
|
+
session_id: Optional[str] = None,
|
|
756
418
|
) -> "Agent":
|
|
757
|
-
"""
|
|
758
|
-
Create an agent from a single Vectara corpus
|
|
419
|
+
"""Create an agent from a single Vectara corpus using the factory function.
|
|
759
420
|
|
|
760
421
|
Args:
|
|
761
|
-
tool_name (str):
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
fallback_agent_config (AgentConfig, optional): The fallback configuration of the agent.
|
|
768
|
-
chat_history (Tuple[str, str], optional): A list of user/agent chat pairs to initialize the agent memory.
|
|
769
|
-
data_description (str): The description of the data.
|
|
770
|
-
assistant_specialty (str): The specialty of the assistant.
|
|
771
|
-
general_instructions (str, optional): General instructions for the agent.
|
|
772
|
-
The Agent has a default set of instructions that are crafted to help it operate effectively.
|
|
773
|
-
This allows you to customize the agent's behavior and personality, but use with caution.
|
|
774
|
-
verbose (bool, optional): Whether to print verbose output.
|
|
775
|
-
vectara_filter_fields (List[dict], optional): The filterable attributes
|
|
776
|
-
(each dict maps field name to Tuple[type, description]).
|
|
777
|
-
vectara_offset (int, optional): Number of results to skip.
|
|
778
|
-
vectara_lambda_val (float, optional): Lambda value for Vectara hybrid search.
|
|
779
|
-
vectara_semantics: (str, optional): Indicates whether the query is intended as a query or response.
|
|
780
|
-
vectara_custom_dimensions: (Dict, optional): Custom dimensions for the query.
|
|
781
|
-
vectara_reranker (str, optional): The Vectara reranker name (default "slingshot")
|
|
782
|
-
vectara_rerank_k (int, optional): The number of results to use with reranking.
|
|
783
|
-
vectara_rerank_limit: (int, optional): The maximum number of results to return after reranking.
|
|
784
|
-
vectara_rerank_cutoff: (float, optional): The minimum score threshold for results to include after
|
|
785
|
-
reranking.
|
|
786
|
-
vectara_diversity_bias (float, optional): The MMR diversity bias.
|
|
787
|
-
vectara_udf_expression (str, optional): The user defined expression for reranking results.
|
|
788
|
-
vectara_rerank_chain (List[Dict], optional): A list of Vectara rerankers to be applied sequentially.
|
|
789
|
-
vectara_n_sentences_before (int, optional): The number of sentences before the matching text
|
|
790
|
-
vectara_n_sentences_after (int, optional): The number of sentences after the matching text.
|
|
791
|
-
vectara_summary_num_results (int, optional): The number of results to use in summarization.
|
|
792
|
-
vectara_summarizer (str, optional): The Vectara summarizer name.
|
|
793
|
-
vectara_summary_response_language (str, optional): The response language for the Vectara summary.
|
|
794
|
-
vectara_summary_prompt_text (str, optional): The custom prompt, using appropriate prompt variables and
|
|
795
|
-
functions.
|
|
796
|
-
vectara_max_response_chars (int, optional): The desired maximum number of characters for the generated
|
|
797
|
-
summary.
|
|
798
|
-
vectara_max_tokens (int, optional): The maximum number of tokens to be returned by the LLM.
|
|
799
|
-
vectara_temperature (float, optional): The sampling temperature; higher values lead to more randomness.
|
|
800
|
-
vectara_frequency_penalty (float, optional): How much to penalize repeating tokens in the response,
|
|
801
|
-
higher values reducing likelihood of repeating the same line.
|
|
802
|
-
vectara_presence_penalty (float, optional): How much to penalize repeating tokens in the response,
|
|
803
|
-
higher values increasing the diversity of topics.
|
|
804
|
-
vectara_save_history (bool, optional): Whether to save the query in history.
|
|
805
|
-
return_direct (bool, optional): Whether the agent should return the tool's response directly.
|
|
806
|
-
|
|
807
|
-
Returns:
|
|
808
|
-
Agent: An instance of the Agent class.
|
|
422
|
+
tool_name (str): Name of the tool to be created.
|
|
423
|
+
data_description (str): Description of the data/corpus.
|
|
424
|
+
assistant_specialty (str): The specialty/topic of the assistant.
|
|
425
|
+
session_id (str, optional): The session ID for memory persistence.
|
|
426
|
+
If None, auto-generates from topic and date. Defaults to None.
|
|
427
|
+
... (other parameters as documented in factory function)
|
|
809
428
|
"""
|
|
810
|
-
|
|
811
|
-
|
|
429
|
+
# Use the factory function to avoid code duplication
|
|
430
|
+
config = create_agent_from_corpus(
|
|
431
|
+
tool_name=tool_name,
|
|
432
|
+
data_description=data_description,
|
|
433
|
+
assistant_specialty=assistant_specialty,
|
|
434
|
+
general_instructions=general_instructions,
|
|
812
435
|
vectara_corpus_key=vectara_corpus_key,
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
for field in vectara_filter_fields:
|
|
817
|
-
field_definitions[field["name"]] = (
|
|
818
|
-
eval(field["type"]),
|
|
819
|
-
Field(description=field["description"]),
|
|
820
|
-
) # type: ignore
|
|
821
|
-
query_args = create_model("QueryArgs", **field_definitions) # type: ignore
|
|
822
|
-
|
|
823
|
-
# tool name must be valid Python function name
|
|
824
|
-
if tool_name:
|
|
825
|
-
tool_name = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
826
|
-
|
|
827
|
-
vectara_tool = vec_factory.create_rag_tool(
|
|
828
|
-
tool_name=tool_name or f"vectara_{vectara_corpus_key}",
|
|
829
|
-
tool_description=f"""
|
|
830
|
-
Given a user query,
|
|
831
|
-
returns a response (str) to a user question about {data_description}.
|
|
832
|
-
""",
|
|
833
|
-
tool_args_schema=query_args,
|
|
834
|
-
reranker=vectara_reranker,
|
|
835
|
-
rerank_k=vectara_rerank_k,
|
|
836
|
-
rerank_limit=vectara_rerank_limit,
|
|
837
|
-
rerank_cutoff=vectara_rerank_cutoff,
|
|
838
|
-
mmr_diversity_bias=vectara_diversity_bias,
|
|
839
|
-
udf_expression=vectara_udf_expression,
|
|
840
|
-
rerank_chain=vectara_rerank_chain,
|
|
841
|
-
n_sentences_before=vectara_n_sentences_before,
|
|
842
|
-
n_sentences_after=vectara_n_sentences_after,
|
|
843
|
-
offset=vectara_offset,
|
|
844
|
-
lambda_val=vectara_lambda_val,
|
|
845
|
-
semantics=vectara_semantics,
|
|
846
|
-
custom_dimensions=vectara_custom_dimensions,
|
|
847
|
-
summary_num_results=vectara_summary_num_results,
|
|
848
|
-
vectara_summarizer=vectara_summarizer,
|
|
849
|
-
summary_response_lang=vectara_summary_response_language,
|
|
850
|
-
vectara_prompt_text=vectara_summary_prompt_text,
|
|
851
|
-
max_response_chars=vectara_max_response_chars,
|
|
852
|
-
max_tokens=vectara_max_tokens,
|
|
853
|
-
temperature=vectara_temperature,
|
|
854
|
-
frequency_penalty=vectara_frequency_penalty,
|
|
855
|
-
presence_penalty=vectara_presence_penalty,
|
|
856
|
-
save_history=vectara_save_history,
|
|
857
|
-
include_citations=True,
|
|
436
|
+
vectara_api_key=vectara_api_key,
|
|
437
|
+
agent_config=agent_config,
|
|
438
|
+
fallback_agent_config=fallback_agent_config,
|
|
858
439
|
verbose=verbose,
|
|
440
|
+
vectara_filter_fields=vectara_filter_fields,
|
|
441
|
+
vectara_offset=vectara_offset,
|
|
442
|
+
vectara_lambda_val=vectara_lambda_val,
|
|
443
|
+
vectara_semantics=vectara_semantics,
|
|
444
|
+
vectara_custom_dimensions=vectara_custom_dimensions,
|
|
445
|
+
vectara_reranker=vectara_reranker,
|
|
446
|
+
vectara_rerank_k=vectara_rerank_k,
|
|
447
|
+
vectara_rerank_limit=vectara_rerank_limit,
|
|
448
|
+
vectara_rerank_cutoff=vectara_rerank_cutoff,
|
|
449
|
+
vectara_diversity_bias=vectara_diversity_bias,
|
|
450
|
+
vectara_udf_expression=vectara_udf_expression,
|
|
451
|
+
vectara_rerank_chain=vectara_rerank_chain,
|
|
452
|
+
vectara_n_sentences_before=vectara_n_sentences_before,
|
|
453
|
+
vectara_n_sentences_after=vectara_n_sentences_after,
|
|
454
|
+
vectara_summary_num_results=vectara_summary_num_results,
|
|
455
|
+
vectara_summarizer=vectara_summarizer,
|
|
456
|
+
vectara_summary_response_language=vectara_summary_response_language,
|
|
457
|
+
vectara_summary_prompt_text=vectara_summary_prompt_text,
|
|
458
|
+
vectara_max_response_chars=vectara_max_response_chars,
|
|
459
|
+
vectara_max_tokens=vectara_max_tokens,
|
|
460
|
+
vectara_temperature=vectara_temperature,
|
|
461
|
+
vectara_frequency_penalty=vectara_frequency_penalty,
|
|
462
|
+
vectara_presence_penalty=vectara_presence_penalty,
|
|
463
|
+
vectara_save_history=vectara_save_history,
|
|
859
464
|
return_direct=return_direct,
|
|
860
465
|
)
|
|
861
466
|
|
|
862
|
-
assistant_instructions = f"""
|
|
863
|
-
- You are a helpful {assistant_specialty} assistant.
|
|
864
|
-
- You can answer questions about {data_description}.
|
|
865
|
-
- Never discuss politics, and always respond politely.
|
|
866
|
-
"""
|
|
867
|
-
|
|
868
467
|
return cls(
|
|
869
|
-
|
|
870
|
-
topic=assistant_specialty,
|
|
871
|
-
custom_instructions=assistant_instructions,
|
|
872
|
-
general_instructions=general_instructions,
|
|
873
|
-
verbose=verbose,
|
|
468
|
+
chat_history=chat_history,
|
|
874
469
|
agent_progress_callback=agent_progress_callback,
|
|
875
470
|
query_logging_callback=query_logging_callback,
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
chat_history=chat_history,
|
|
879
|
-
vectara_api_key=vectara_api_key,
|
|
471
|
+
session_id=session_id,
|
|
472
|
+
**config,
|
|
880
473
|
)
|
|
881
474
|
|
|
882
475
|
def _switch_agent_config(self) -> None:
|
|
883
|
-
"""
|
|
476
|
+
"""
|
|
884
477
|
Switch the configuration type of the agent.
|
|
885
478
|
This function is called automatically to switch the agent configuration if the current configuration fails.
|
|
479
|
+
Ensures memory consistency by clearing agent instances so they are recreated with current memory.
|
|
886
480
|
"""
|
|
887
481
|
if self.agent_config_type == AgentConfigType.DEFAULT:
|
|
888
482
|
self.agent_config_type = AgentConfigType.FALLBACK
|
|
483
|
+
# Clear the fallback agent so it gets recreated with current memory
|
|
484
|
+
self._fallback_agent = None
|
|
889
485
|
else:
|
|
890
486
|
self.agent_config_type = AgentConfigType.DEFAULT
|
|
487
|
+
# Clear the main agent so it gets recreated with current memory
|
|
488
|
+
self._agent = None
|
|
891
489
|
|
|
892
490
|
def report(self, detailed: bool = False) -> None:
|
|
893
491
|
"""
|
|
@@ -899,45 +497,25 @@ class Agent:
|
|
|
899
497
|
Returns:
|
|
900
498
|
str: The report from the agent.
|
|
901
499
|
"""
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
500
|
+
logger.info("Vectara agentic Report:")
|
|
501
|
+
logger.info(f"Agent Type = {self.agent_config.agent_type}")
|
|
502
|
+
logger.info(f"Topic = {self._topic}")
|
|
503
|
+
logger.info("Tools:")
|
|
906
504
|
for tool in self.tools:
|
|
907
505
|
if hasattr(tool, "metadata"):
|
|
908
506
|
if detailed:
|
|
909
|
-
|
|
507
|
+
logger.info(f"- {tool.metadata.description}")
|
|
910
508
|
else:
|
|
911
|
-
|
|
509
|
+
logger.info(f"- {tool.metadata.name}")
|
|
912
510
|
else:
|
|
913
|
-
|
|
914
|
-
|
|
511
|
+
logger.info("- tool without metadata")
|
|
512
|
+
logger.info(
|
|
915
513
|
f"Agent LLM = {get_llm(LLMRole.MAIN, config=self.agent_config).metadata.model_name}"
|
|
916
514
|
)
|
|
917
|
-
|
|
515
|
+
logger.info(
|
|
918
516
|
f"Tool LLM = {get_llm(LLMRole.TOOL, config=self.agent_config).metadata.model_name}"
|
|
919
517
|
)
|
|
920
518
|
|
|
921
|
-
def token_counts(self) -> dict:
|
|
922
|
-
"""
|
|
923
|
-
Get the token counts for the agent and tools.
|
|
924
|
-
|
|
925
|
-
Returns:
|
|
926
|
-
dict: The token counts for the agent and tools.
|
|
927
|
-
"""
|
|
928
|
-
return {
|
|
929
|
-
"main token count": (
|
|
930
|
-
self.main_token_counter.total_llm_token_count
|
|
931
|
-
if self.main_token_counter
|
|
932
|
-
else -1
|
|
933
|
-
),
|
|
934
|
-
"tool token count": (
|
|
935
|
-
self.tool_token_counter.total_llm_token_count
|
|
936
|
-
if self.tool_token_counter
|
|
937
|
-
else -1
|
|
938
|
-
),
|
|
939
|
-
}
|
|
940
|
-
|
|
941
519
|
def _get_current_agent(self):
|
|
942
520
|
return (
|
|
943
521
|
self.agent
|
|
@@ -949,23 +527,11 @@ class Agent:
|
|
|
949
527
|
return (
|
|
950
528
|
self.agent_config.agent_type
|
|
951
529
|
if self.agent_config_type == AgentConfigType.DEFAULT
|
|
530
|
+
or not self.fallback_agent_config
|
|
952
531
|
else self.fallback_agent_config.agent_type
|
|
953
532
|
)
|
|
954
533
|
|
|
955
|
-
|
|
956
|
-
llm_prompt = f"""
|
|
957
|
-
Given the question '{prompt}', and agent response '{agent_response.response}',
|
|
958
|
-
Please provide a well formatted final response to the query.
|
|
959
|
-
final response:
|
|
960
|
-
"""
|
|
961
|
-
agent_type = self._get_current_agent_type()
|
|
962
|
-
if agent_type != AgentType.LATS:
|
|
963
|
-
return
|
|
964
|
-
|
|
965
|
-
agent = self._get_current_agent()
|
|
966
|
-
agent_response.response = str(agent.llm.acomplete(llm_prompt))
|
|
967
|
-
|
|
968
|
-
def chat(self, prompt: str) -> AgentResponse: # type: ignore
|
|
534
|
+
def chat(self, prompt: str) -> AgentResponse:
|
|
969
535
|
"""
|
|
970
536
|
Interact with the agent using a chat prompt.
|
|
971
537
|
|
|
@@ -975,48 +541,15 @@ class Agent:
|
|
|
975
541
|
Returns:
|
|
976
542
|
AgentResponse: The response from the agent.
|
|
977
543
|
"""
|
|
978
|
-
return asyncio.run(self.achat(prompt))
|
|
979
|
-
|
|
980
|
-
def _calc_fcs(self, agent_response: AgentResponse) -> None:
|
|
981
|
-
"""
|
|
982
|
-
Calculate the Factual consistency score for the agent response.
|
|
983
|
-
"""
|
|
984
|
-
if not self.vectara_api_key:
|
|
985
|
-
logging.debug("FCS calculation skipped: 'vectara_api_key' is missing.")
|
|
986
|
-
return # can't calculate FCS without Vectara API key
|
|
987
|
-
|
|
988
|
-
chat_history = self.memory.get()
|
|
989
|
-
context = []
|
|
990
|
-
for msg in chat_history:
|
|
991
|
-
if msg.role == MessageRole.TOOL:
|
|
992
|
-
content = msg.content
|
|
993
|
-
if _is_human_readable_output(content):
|
|
994
|
-
try:
|
|
995
|
-
content = content.to_human_readable()
|
|
996
|
-
except Exception as e:
|
|
997
|
-
logging.debug(
|
|
998
|
-
f"Failed to get human-readable format for FCS calculation: {e}"
|
|
999
|
-
)
|
|
1000
|
-
# Fall back to string representation of the object
|
|
1001
|
-
content = str(content)
|
|
1002
|
-
|
|
1003
|
-
context.append(content)
|
|
1004
|
-
elif msg.role in [MessageRole.USER, MessageRole.ASSISTANT] and msg.content:
|
|
1005
|
-
context.append(msg.content)
|
|
1006
|
-
|
|
1007
|
-
if not context:
|
|
1008
|
-
return
|
|
1009
|
-
|
|
1010
|
-
context_str = "\n".join(context)
|
|
1011
544
|
try:
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
)
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
545
|
+
_ = asyncio.get_running_loop()
|
|
546
|
+
except RuntimeError:
|
|
547
|
+
return asyncio.run(self.achat(prompt))
|
|
548
|
+
|
|
549
|
+
# We are inside a running loop (Jupyter, uvicorn, etc.)
|
|
550
|
+
raise RuntimeError(
|
|
551
|
+
"Use `await agent.achat(...)` inside an event loop (e.g. Jupyter)."
|
|
552
|
+
)
|
|
1020
553
|
|
|
1021
554
|
async def achat(self, prompt: str) -> AgentResponse: # type: ignore
|
|
1022
555
|
"""
|
|
@@ -1028,6 +561,9 @@ class Agent:
|
|
|
1028
561
|
Returns:
|
|
1029
562
|
AgentResponse: The response from the agent.
|
|
1030
563
|
"""
|
|
564
|
+
if not prompt:
|
|
565
|
+
return AgentResponse(response="")
|
|
566
|
+
|
|
1031
567
|
max_attempts = 4 if self.fallback_agent_config else 2
|
|
1032
568
|
attempt = 0
|
|
1033
569
|
orig_llm = self.llm.metadata.model_name
|
|
@@ -1035,36 +571,205 @@ class Agent:
|
|
|
1035
571
|
while attempt < max_attempts:
|
|
1036
572
|
try:
|
|
1037
573
|
current_agent = self._get_current_agent()
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
574
|
+
|
|
575
|
+
# Deal with workflow-based agent types (Function Calling and ReAct)
|
|
576
|
+
if self._get_current_agent_type() in [
|
|
577
|
+
AgentType.FUNCTION_CALLING,
|
|
578
|
+
AgentType.REACT,
|
|
579
|
+
]:
|
|
580
|
+
from llama_index.core.workflow import Context
|
|
581
|
+
|
|
582
|
+
# Create context and pass memory to the workflow agent
|
|
583
|
+
# According to LlamaIndex docs, we should let the workflow manage memory internally
|
|
584
|
+
ctx = Context(current_agent)
|
|
585
|
+
|
|
586
|
+
handler = current_agent.run(
|
|
587
|
+
user_msg=prompt, memory=self.memory, ctx=ctx
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# Listen to workflow events if progress callback is set
|
|
591
|
+
if self.agent_progress_callback:
|
|
592
|
+
# Create event tracker for consistent event ID generation
|
|
593
|
+
from .agent_core.streaming import ToolEventTracker
|
|
594
|
+
|
|
595
|
+
event_tracker = ToolEventTracker()
|
|
596
|
+
|
|
597
|
+
async for event in handler.stream_events():
|
|
598
|
+
# Use consistent event ID tracking to ensure tool calls and outputs are paired
|
|
599
|
+
event_id = event_tracker.get_event_id(event)
|
|
600
|
+
|
|
601
|
+
# Handle different types of workflow events using same logic as FunctionCallingStreamHandler
|
|
602
|
+
from llama_index.core.agent.workflow import (
|
|
603
|
+
ToolCall,
|
|
604
|
+
ToolCallResult,
|
|
605
|
+
AgentInput,
|
|
606
|
+
AgentOutput,
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if isinstance(event, ToolCall):
|
|
610
|
+
self.agent_progress_callback(
|
|
611
|
+
status_type=AgentStatusType.TOOL_CALL,
|
|
612
|
+
msg={
|
|
613
|
+
"tool_name": event.tool_name,
|
|
614
|
+
"arguments": json.dumps(event.tool_kwargs),
|
|
615
|
+
},
|
|
616
|
+
event_id=event_id,
|
|
617
|
+
)
|
|
618
|
+
elif isinstance(event, ToolCallResult):
|
|
619
|
+
self.agent_progress_callback(
|
|
620
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
621
|
+
msg={
|
|
622
|
+
"tool_name": event.tool_name,
|
|
623
|
+
"content": str(event.tool_output),
|
|
624
|
+
},
|
|
625
|
+
event_id=event_id,
|
|
626
|
+
)
|
|
627
|
+
elif isinstance(event, AgentInput):
|
|
628
|
+
self.agent_progress_callback(
|
|
629
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
630
|
+
msg={"content": f"Agent input: {event.input}"},
|
|
631
|
+
event_id=event_id,
|
|
632
|
+
)
|
|
633
|
+
elif isinstance(event, AgentOutput):
|
|
634
|
+
self.agent_progress_callback(
|
|
635
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
636
|
+
msg={"content": f"Agent output: {event.response}"},
|
|
637
|
+
event_id=event_id,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
result = await handler
|
|
641
|
+
|
|
642
|
+
# Ensure we have an AgentResponse object with a string response
|
|
643
|
+
if hasattr(result, "response"):
|
|
644
|
+
response_text = result.response
|
|
645
|
+
else:
|
|
646
|
+
response_text = str(result)
|
|
647
|
+
|
|
648
|
+
# Handle case where response is a ChatMessage object
|
|
649
|
+
if hasattr(response_text, "content"):
|
|
650
|
+
response_text = response_text.content
|
|
651
|
+
elif not isinstance(response_text, str):
|
|
652
|
+
response_text = str(response_text)
|
|
653
|
+
|
|
654
|
+
if response_text is None or response_text == "None":
|
|
655
|
+
# Try to find tool outputs in the result object
|
|
656
|
+
response_text = None
|
|
657
|
+
|
|
658
|
+
# Check various possible locations for tool outputs
|
|
659
|
+
if hasattr(result, "tool_outputs") and result.tool_outputs:
|
|
660
|
+
# Get the latest tool output
|
|
661
|
+
latest_output = (
|
|
662
|
+
result.tool_outputs[-1]
|
|
663
|
+
if isinstance(result.tool_outputs, list)
|
|
664
|
+
else result.tool_outputs
|
|
665
|
+
)
|
|
666
|
+
response_text = str(latest_output)
|
|
667
|
+
|
|
668
|
+
# Check if there are tool_calls with results
|
|
669
|
+
elif hasattr(result, "tool_calls") and result.tool_calls:
|
|
670
|
+
# Tool calls might contain the outputs - let's try to extract them
|
|
671
|
+
for tool_call in result.tool_calls:
|
|
672
|
+
if (
|
|
673
|
+
hasattr(tool_call, "tool_output")
|
|
674
|
+
and tool_call.tool_output is not None
|
|
675
|
+
):
|
|
676
|
+
response_text = str(tool_call.tool_output)
|
|
677
|
+
break
|
|
678
|
+
|
|
679
|
+
elif hasattr(result, "sources") or hasattr(
|
|
680
|
+
result, "source_nodes"
|
|
681
|
+
):
|
|
682
|
+
sources = getattr(
|
|
683
|
+
result, "sources", getattr(result, "source_nodes", [])
|
|
684
|
+
)
|
|
685
|
+
if (
|
|
686
|
+
sources
|
|
687
|
+
and len(sources) > 0
|
|
688
|
+
and hasattr(sources[0], "text")
|
|
689
|
+
):
|
|
690
|
+
response_text = sources[0].text
|
|
691
|
+
|
|
692
|
+
# Check for workflow context or chat history that might contain tool results
|
|
693
|
+
elif hasattr(result, "chat_history"):
|
|
694
|
+
# Look for the most recent assistant message that might contain tool results
|
|
695
|
+
chat_history = result.chat_history
|
|
696
|
+
if chat_history and len(chat_history) > 0:
|
|
697
|
+
for msg in reversed(chat_history):
|
|
698
|
+
if (
|
|
699
|
+
msg.role == MessageRole.TOOL
|
|
700
|
+
and msg.content
|
|
701
|
+
and str(msg.content).strip()
|
|
702
|
+
):
|
|
703
|
+
response_text = msg.content
|
|
704
|
+
break
|
|
705
|
+
if (
|
|
706
|
+
hasattr(msg, "content")
|
|
707
|
+
and msg.content
|
|
708
|
+
and str(msg.content).strip()
|
|
709
|
+
):
|
|
710
|
+
response_text = msg.content
|
|
711
|
+
break
|
|
712
|
+
|
|
713
|
+
# If we still don't have a response, provide a fallback
|
|
714
|
+
if response_text is None or response_text == "None":
|
|
715
|
+
response_text = "Response completed."
|
|
716
|
+
|
|
717
|
+
agent_response = AgentResponse(
|
|
718
|
+
response=response_text, metadata=getattr(result, "metadata", {})
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
# Retrieve updated memory from workflow context
|
|
722
|
+
# According to LlamaIndex docs, workflow agents manage memory internally
|
|
723
|
+
# and we can access it via ctx.store.get("memory")
|
|
724
|
+
try:
|
|
725
|
+
workflow_memory = await ctx.store.get("memory")
|
|
726
|
+
if workflow_memory:
|
|
727
|
+
# Update our external memory with the workflow's memory
|
|
728
|
+
self.memory = workflow_memory
|
|
729
|
+
except Exception as e:
|
|
730
|
+
# If we can't retrieve workflow memory, fall back to manual management
|
|
731
|
+
warning_msg = (
|
|
732
|
+
f"Could not retrieve workflow memory, falling back to "
|
|
733
|
+
f"manual management: {e}"
|
|
734
|
+
)
|
|
735
|
+
logger.warning(warning_msg)
|
|
736
|
+
user_msg = ChatMessage.from_str(prompt, role=MessageRole.USER)
|
|
737
|
+
assistant_msg = ChatMessage.from_str(
|
|
738
|
+
response_text, role=MessageRole.ASSISTANT
|
|
739
|
+
)
|
|
740
|
+
self.memory.put_messages([user_msg, assistant_msg])
|
|
741
|
+
|
|
742
|
+
# Standard chat interaction for other agent types
|
|
743
|
+
else:
|
|
744
|
+
agent_response = await current_agent.achat(prompt)
|
|
745
|
+
|
|
746
|
+
# Post processing after response is generated
|
|
747
|
+
agent_response.metadata = agent_response.metadata or {}
|
|
748
|
+
user_metadata = agent_response.metadata
|
|
749
|
+
agent_response = await execute_post_stream_processing(
|
|
750
|
+
agent_response, prompt, self, user_metadata
|
|
751
|
+
)
|
|
1045
752
|
return agent_response
|
|
1046
753
|
|
|
1047
754
|
except Exception as e:
|
|
1048
755
|
last_error = e
|
|
1049
756
|
if self.verbose:
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
f"LLM call failed on attempt {attempt}. Switching agent configuration."
|
|
1055
|
-
)
|
|
757
|
+
logger.warning(
|
|
758
|
+
f"LLM call failed on attempt {attempt}. " f"Error: {e}."
|
|
759
|
+
)
|
|
760
|
+
if attempt >= 2 and self.fallback_agent_config:
|
|
1056
761
|
self._switch_agent_config()
|
|
1057
|
-
|
|
762
|
+
await asyncio.sleep(1)
|
|
1058
763
|
attempt += 1
|
|
1059
764
|
|
|
1060
765
|
return AgentResponse(
|
|
1061
766
|
response=(
|
|
1062
767
|
f"For {orig_llm} LLM - failure can't be resolved after "
|
|
1063
|
-
f"{max_attempts} attempts ({last_error}."
|
|
768
|
+
f"{max_attempts} attempts ({last_error})."
|
|
1064
769
|
)
|
|
1065
770
|
)
|
|
1066
771
|
|
|
1067
|
-
def stream_chat(self, prompt: str) -> AgentStreamingResponse:
|
|
772
|
+
def stream_chat(self, prompt: str) -> AgentStreamingResponse:
|
|
1068
773
|
"""
|
|
1069
774
|
Interact with the agent using a chat prompt with streaming.
|
|
1070
775
|
Args:
|
|
@@ -1072,7 +777,13 @@ class Agent:
|
|
|
1072
777
|
Returns:
|
|
1073
778
|
AgentStreamingResponse: The streaming response from the agent.
|
|
1074
779
|
"""
|
|
1075
|
-
|
|
780
|
+
try:
|
|
781
|
+
_ = asyncio.get_running_loop()
|
|
782
|
+
except RuntimeError:
|
|
783
|
+
return asyncio.run(self.astream_chat(prompt))
|
|
784
|
+
raise RuntimeError(
|
|
785
|
+
"Use `await agent.astream_chat(...)` inside an event loop (e.g. Jupyter)."
|
|
786
|
+
)
|
|
1076
787
|
|
|
1077
788
|
async def astream_chat(self, prompt: str) -> AgentStreamingResponse: # type: ignore
|
|
1078
789
|
"""
|
|
@@ -1082,50 +793,199 @@ class Agent:
|
|
|
1082
793
|
Returns:
|
|
1083
794
|
AgentStreamingResponse: The streaming response from the agent.
|
|
1084
795
|
"""
|
|
796
|
+
# Store query for VHC processing and clear previous tool outputs
|
|
797
|
+
self._last_query = prompt
|
|
798
|
+
self._clear_tool_outputs()
|
|
1085
799
|
max_attempts = 4 if self.fallback_agent_config else 2
|
|
1086
800
|
attempt = 0
|
|
1087
801
|
orig_llm = self.llm.metadata.model_name
|
|
802
|
+
last_error = None
|
|
1088
803
|
while attempt < max_attempts:
|
|
1089
804
|
try:
|
|
1090
805
|
current_agent = self._get_current_agent()
|
|
1091
|
-
|
|
1092
|
-
|
|
806
|
+
user_meta: Dict[str, Any] = {}
|
|
807
|
+
|
|
808
|
+
# Deal with Function Calling agent type
|
|
809
|
+
if self._get_current_agent_type() == AgentType.FUNCTION_CALLING:
|
|
810
|
+
from llama_index.core.workflow import Context
|
|
811
|
+
|
|
812
|
+
# Create context and pass memory to the workflow agent
|
|
813
|
+
# According to LlamaIndex docs, we should let the workflow manage memory internally
|
|
814
|
+
ctx = Context(current_agent)
|
|
815
|
+
|
|
816
|
+
handler = current_agent.run(
|
|
817
|
+
user_msg=prompt, memory=self.memory, ctx=ctx
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
# Use the dedicated FunctionCallingStreamHandler
|
|
821
|
+
stream_handler = FunctionCallingStreamHandler(self, handler, prompt)
|
|
822
|
+
streaming_adapter = stream_handler.create_streaming_response(
|
|
823
|
+
user_meta
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
return AgentStreamingResponse(
|
|
827
|
+
base=streaming_adapter, metadata=user_meta
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
#
|
|
831
|
+
# For other agent types, use the standard async chat method
|
|
832
|
+
#
|
|
833
|
+
li_stream = await current_agent.astream_chat(prompt)
|
|
834
|
+
orig_async = li_stream.async_response_gen
|
|
1093
835
|
|
|
1094
836
|
# Define a wrapper to preserve streaming behavior while executing post-stream logic.
|
|
1095
837
|
async def _stream_response_wrapper():
|
|
1096
|
-
async for
|
|
1097
|
-
yield
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
agent_response.async_response_gen = (
|
|
1107
|
-
_stream_response_wrapper # Override the generator
|
|
1108
|
-
)
|
|
1109
|
-
return agent_response
|
|
838
|
+
async for tok in orig_async():
|
|
839
|
+
yield tok
|
|
840
|
+
|
|
841
|
+
# Use shared post-processing function
|
|
842
|
+
await execute_post_stream_processing(
|
|
843
|
+
li_stream, prompt, self, user_meta
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
li_stream.async_response_gen = _stream_response_wrapper
|
|
847
|
+
return AgentStreamingResponse(base=li_stream, metadata=user_meta)
|
|
1110
848
|
|
|
1111
849
|
except Exception as e:
|
|
1112
850
|
last_error = e
|
|
1113
|
-
if attempt >= 2:
|
|
1114
|
-
if self.verbose:
|
|
1115
|
-
print(
|
|
1116
|
-
f"LLM call failed on attempt {attempt}. Switching agent configuration."
|
|
1117
|
-
)
|
|
851
|
+
if attempt >= 2 and self.fallback_agent_config:
|
|
1118
852
|
self._switch_agent_config()
|
|
1119
|
-
|
|
853
|
+
await asyncio.sleep(1)
|
|
1120
854
|
attempt += 1
|
|
1121
855
|
|
|
1122
|
-
return AgentStreamingResponse(
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
f"{max_attempts} attempts ({last_error})."
|
|
1126
|
-
)
|
|
856
|
+
return AgentStreamingResponse.from_error(
|
|
857
|
+
f"For {orig_llm} LLM - failure can't be resolved after "
|
|
858
|
+
f"{max_attempts} attempts ({last_error})."
|
|
1127
859
|
)
|
|
1128
860
|
|
|
861
|
+
def _clear_tool_outputs(self):
|
|
862
|
+
"""Clear stored tool outputs at the start of a new query."""
|
|
863
|
+
self._current_tool_outputs.clear()
|
|
864
|
+
logging.info("🔧 [TOOL_STORAGE] Cleared stored tool outputs for new query")
|
|
865
|
+
|
|
866
|
+
def _add_tool_output(self, tool_name: str, content: str):
|
|
867
|
+
"""Add a tool output to the current collection for VHC."""
|
|
868
|
+
tool_output = {
|
|
869
|
+
'status_type': 'TOOL_OUTPUT',
|
|
870
|
+
'content': content,
|
|
871
|
+
'tool_name': tool_name
|
|
872
|
+
}
|
|
873
|
+
self._current_tool_outputs.append(tool_output)
|
|
874
|
+
logging.info(f"🔧 [TOOL_STORAGE] Added tool output from '{tool_name}': {len(content)} chars")
|
|
875
|
+
|
|
876
|
+
def _get_stored_tool_outputs(self) -> List[dict]:
|
|
877
|
+
"""Get the stored tool outputs from the current query."""
|
|
878
|
+
logging.info(f"🔧 [TOOL_STORAGE] Retrieved {len(self._current_tool_outputs)} stored tool outputs")
|
|
879
|
+
return self._current_tool_outputs.copy()
|
|
880
|
+
|
|
881
|
+
async def acompute_vhc(self) -> Dict[str, Any]:
|
|
882
|
+
"""
|
|
883
|
+
Compute VHC for the last query/response pair (async version).
|
|
884
|
+
Results are cached for subsequent calls. Tool outputs are automatically
|
|
885
|
+
collected during streaming and used internally.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
Dict[str, Any]: Dictionary containing 'corrected_text' and 'corrections'
|
|
889
|
+
"""
|
|
890
|
+
logging.info(
|
|
891
|
+
f"🔍🔍🔍 [VHC_AGENT_ENTRY] UNIQUE_DEBUG_MESSAGE acompute_vhc method called - "
|
|
892
|
+
f"stored_tool_outputs_count={len(self._current_tool_outputs)}"
|
|
893
|
+
)
|
|
894
|
+
logging.info(
|
|
895
|
+
f"🔍🔍🔍 [VHC_AGENT_ENTRY] _last_query: {'set' if self._last_query else 'None'}"
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
if not self._last_query:
|
|
899
|
+
logging.info("🔍 [VHC_AGENT] Returning early - no _last_query")
|
|
900
|
+
return {"corrected_text": None, "corrections": []}
|
|
901
|
+
|
|
902
|
+
# For VHC to work, we need the response text from memory
|
|
903
|
+
# Get the latest assistant response from memory
|
|
904
|
+
messages = self.memory.get()
|
|
905
|
+
logging.info(
|
|
906
|
+
f"🔍 [VHC_AGENT] memory.get() returned {len(messages) if messages else 0} messages"
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
if not messages:
|
|
910
|
+
logging.info("🔍 [VHC_AGENT] Returning early - no messages in memory")
|
|
911
|
+
return {"corrected_text": None, "corrections": []}
|
|
912
|
+
|
|
913
|
+
# Find the last assistant message
|
|
914
|
+
last_response = None
|
|
915
|
+
for msg in reversed(messages):
|
|
916
|
+
if msg.role == MessageRole.ASSISTANT:
|
|
917
|
+
last_response = msg.content
|
|
918
|
+
break
|
|
919
|
+
|
|
920
|
+
logging.info(
|
|
921
|
+
f"🔍 [VHC_AGENT] Found last_response: {'set' if last_response else 'None'}"
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
if not last_response:
|
|
925
|
+
logging.info("🔍 [VHC_AGENT] Returning early - no last assistant response found")
|
|
926
|
+
return {"corrected_text": None, "corrections": []}
|
|
927
|
+
|
|
928
|
+
# Update stored response for caching
|
|
929
|
+
self._last_response = last_response
|
|
930
|
+
|
|
931
|
+
# Create cache key from query + response
|
|
932
|
+
cache_key = hash(f"{self._last_query}:{self._last_response}")
|
|
933
|
+
|
|
934
|
+
# Return cached results if available
|
|
935
|
+
if cache_key in self._vhc_cache:
|
|
936
|
+
return self._vhc_cache[cache_key]
|
|
937
|
+
|
|
938
|
+
# Check if we have VHC API key
|
|
939
|
+
logging.info(
|
|
940
|
+
f"🔍 [VHC_AGENT] acompute_vhc called with vectara_api_key={'set' if self.vectara_api_key else 'None'}"
|
|
941
|
+
)
|
|
942
|
+
if not self.vectara_api_key:
|
|
943
|
+
logging.info("🔍 [VHC_AGENT] No vectara_api_key - returning early with None")
|
|
944
|
+
return {"corrected_text": None, "corrections": []}
|
|
945
|
+
|
|
946
|
+
# Compute VHC using existing library function
|
|
947
|
+
from .agent_core.utils.hallucination import analyze_hallucinations
|
|
948
|
+
|
|
949
|
+
try:
|
|
950
|
+
# Use stored tool outputs from current query
|
|
951
|
+
stored_tool_outputs = self._get_stored_tool_outputs()
|
|
952
|
+
logging.info(f"🔧 [VHC_AGENT] Using {len(stored_tool_outputs)} stored tool outputs for VHC")
|
|
953
|
+
|
|
954
|
+
corrected_text, corrections = analyze_hallucinations(
|
|
955
|
+
query=self._last_query,
|
|
956
|
+
chat_history=self.memory.get(),
|
|
957
|
+
agent_response=self._last_response,
|
|
958
|
+
tools=self.tools,
|
|
959
|
+
vectara_api_key=self.vectara_api_key,
|
|
960
|
+
tool_outputs=stored_tool_outputs,
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
# Cache results
|
|
964
|
+
results = {"corrected_text": corrected_text, "corrections": corrections}
|
|
965
|
+
self._vhc_cache[cache_key] = results
|
|
966
|
+
|
|
967
|
+
return results
|
|
968
|
+
|
|
969
|
+
except Exception as e:
|
|
970
|
+
logger.error(f"VHC computation failed: {e}")
|
|
971
|
+
return {"corrected_text": None, "corrections": []}
|
|
972
|
+
|
|
973
|
+
def compute_vhc(self) -> Dict[str, Any]:
|
|
974
|
+
"""
|
|
975
|
+
Compute VHC for the last query/response pair (sync version).
|
|
976
|
+
Results are cached for subsequent calls. Tool outputs are automatically
|
|
977
|
+
collected during streaming and used internally.
|
|
978
|
+
|
|
979
|
+
Returns:
|
|
980
|
+
Dict[str, Any]: Dictionary containing 'corrected_text' and 'corrections'
|
|
981
|
+
"""
|
|
982
|
+
try:
|
|
983
|
+
loop = asyncio.get_event_loop()
|
|
984
|
+
return loop.run_until_complete(self.acompute_vhc())
|
|
985
|
+
except RuntimeError:
|
|
986
|
+
# No event loop running, create a new one
|
|
987
|
+
return asyncio.run(self.acompute_vhc())
|
|
988
|
+
|
|
1129
989
|
#
|
|
1130
990
|
# run() method for running a workflow
|
|
1131
991
|
# workflow will always get these arguments in the StartEvent: agent, tools, llm, verbose
|
|
@@ -1168,6 +1028,8 @@ class Agent:
|
|
|
1168
1028
|
f"Fields without default values: {fields_without_default}"
|
|
1169
1029
|
)
|
|
1170
1030
|
|
|
1031
|
+
from llama_index.core.workflow import Context
|
|
1032
|
+
|
|
1171
1033
|
workflow_context = Context(workflow=workflow)
|
|
1172
1034
|
try:
|
|
1173
1035
|
# run workflow
|
|
@@ -1197,7 +1059,9 @@ class Agent:
|
|
|
1197
1059
|
input_dict[key] = value
|
|
1198
1060
|
output = outputs_model_on_fail_cls.model_validate(input_dict)
|
|
1199
1061
|
else:
|
|
1200
|
-
|
|
1062
|
+
logger.warning(
|
|
1063
|
+
f"Vectara Agentic: Workflow failed with unexpected error: {e}"
|
|
1064
|
+
)
|
|
1201
1065
|
raise type(e)(str(e)).with_traceback(e.__traceback__)
|
|
1202
1066
|
|
|
1203
1067
|
return output
|
|
@@ -1225,57 +1089,7 @@ class Agent:
|
|
|
1225
1089
|
|
|
1226
1090
|
def to_dict(self) -> Dict[str, Any]:
|
|
1227
1091
|
"""Serialize the Agent instance to a dictionary."""
|
|
1228
|
-
|
|
1229
|
-
for tool in self.tools:
|
|
1230
|
-
if hasattr(tool.metadata, "fn_schema"):
|
|
1231
|
-
fn_schema_cls = tool.metadata.fn_schema
|
|
1232
|
-
fn_schema_serialized = {
|
|
1233
|
-
"schema": (
|
|
1234
|
-
fn_schema_cls.model_json_schema()
|
|
1235
|
-
if fn_schema_cls and hasattr(fn_schema_cls, "model_json_schema")
|
|
1236
|
-
else None
|
|
1237
|
-
),
|
|
1238
|
-
"metadata": {
|
|
1239
|
-
"module": fn_schema_cls.__module__ if fn_schema_cls else None,
|
|
1240
|
-
"class": fn_schema_cls.__name__ if fn_schema_cls else None,
|
|
1241
|
-
},
|
|
1242
|
-
}
|
|
1243
|
-
else:
|
|
1244
|
-
fn_schema_serialized = None
|
|
1245
|
-
|
|
1246
|
-
tool_dict = {
|
|
1247
|
-
"tool_type": tool.metadata.tool_type.value,
|
|
1248
|
-
"name": tool.metadata.name,
|
|
1249
|
-
"description": tool.metadata.description,
|
|
1250
|
-
"fn": (
|
|
1251
|
-
pickle.dumps(getattr(tool, "fn", None)).decode("latin-1")
|
|
1252
|
-
if getattr(tool, "fn", None)
|
|
1253
|
-
else None
|
|
1254
|
-
),
|
|
1255
|
-
"async_fn": (
|
|
1256
|
-
pickle.dumps(getattr(tool, "async_fn", None)).decode("latin-1")
|
|
1257
|
-
if getattr(tool, "async_fn", None)
|
|
1258
|
-
else None
|
|
1259
|
-
),
|
|
1260
|
-
"fn_schema": fn_schema_serialized,
|
|
1261
|
-
}
|
|
1262
|
-
tool_info.append(tool_dict)
|
|
1263
|
-
|
|
1264
|
-
return {
|
|
1265
|
-
"agent_type": self.agent_config.agent_type.value,
|
|
1266
|
-
"memory": pickle.dumps(self.agent.memory).decode("latin-1"),
|
|
1267
|
-
"tools": tool_info,
|
|
1268
|
-
"topic": self._topic,
|
|
1269
|
-
"custom_instructions": self._custom_instructions,
|
|
1270
|
-
"verbose": self.verbose,
|
|
1271
|
-
"agent_config": self.agent_config.to_dict(),
|
|
1272
|
-
"fallback_agent": (
|
|
1273
|
-
self.fallback_agent_config.to_dict()
|
|
1274
|
-
if self.fallback_agent_config
|
|
1275
|
-
else None
|
|
1276
|
-
),
|
|
1277
|
-
"workflow_cls": self.workflow_cls if self.workflow_cls else None,
|
|
1278
|
-
}
|
|
1092
|
+
return serialize_agent_to_dict(self)
|
|
1279
1093
|
|
|
1280
1094
|
@classmethod
|
|
1281
1095
|
def from_dict(
|
|
@@ -1285,114 +1099,6 @@ class Agent:
|
|
|
1285
1099
|
query_logging_callback: Optional[Callable] = None,
|
|
1286
1100
|
) -> "Agent":
|
|
1287
1101
|
"""Create an Agent instance from a dictionary."""
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
AgentConfig.from_dict(data["fallback_agent_config"])
|
|
1291
|
-
if data.get("fallback_agent_config")
|
|
1292
|
-
else None
|
|
1293
|
-
)
|
|
1294
|
-
tools: list[FunctionTool] = []
|
|
1295
|
-
|
|
1296
|
-
for tool_data in data["tools"]:
|
|
1297
|
-
query_args_model = None
|
|
1298
|
-
if tool_data.get("fn_schema"):
|
|
1299
|
-
schema_info = tool_data["fn_schema"]
|
|
1300
|
-
try:
|
|
1301
|
-
module_name = schema_info["metadata"]["module"]
|
|
1302
|
-
class_name = schema_info["metadata"]["class"]
|
|
1303
|
-
mod = importlib.import_module(module_name)
|
|
1304
|
-
candidate_cls = getattr(mod, class_name)
|
|
1305
|
-
if inspect.isclass(candidate_cls) and issubclass(
|
|
1306
|
-
candidate_cls, BaseModel
|
|
1307
|
-
):
|
|
1308
|
-
query_args_model = candidate_cls
|
|
1309
|
-
else:
|
|
1310
|
-
# It's not the Pydantic model class we expected (e.g., it's the function itself)
|
|
1311
|
-
# Force fallback to JSON schema reconstruction by raising an error.
|
|
1312
|
-
raise ImportError(
|
|
1313
|
-
f"Retrieved '{class_name}' from '{module_name}' is not a Pydantic BaseModel class. "
|
|
1314
|
-
"Falling back to JSON schema reconstruction."
|
|
1315
|
-
)
|
|
1316
|
-
except Exception:
|
|
1317
|
-
# Fallback: rebuild using the JSON schema
|
|
1318
|
-
field_definitions = {}
|
|
1319
|
-
json_schema_to_rebuild = schema_info.get("schema")
|
|
1320
|
-
if json_schema_to_rebuild and isinstance(
|
|
1321
|
-
json_schema_to_rebuild, dict
|
|
1322
|
-
):
|
|
1323
|
-
for field, values in json_schema_to_rebuild.get(
|
|
1324
|
-
"properties", {}
|
|
1325
|
-
).items():
|
|
1326
|
-
field_type = get_field_type(values)
|
|
1327
|
-
field_description = values.get(
|
|
1328
|
-
"description"
|
|
1329
|
-
) # Defaults to None
|
|
1330
|
-
if "default" in values:
|
|
1331
|
-
field_definitions[field] = (
|
|
1332
|
-
field_type,
|
|
1333
|
-
Field(
|
|
1334
|
-
description=field_description,
|
|
1335
|
-
default=values["default"],
|
|
1336
|
-
),
|
|
1337
|
-
)
|
|
1338
|
-
else:
|
|
1339
|
-
field_definitions[field] = (
|
|
1340
|
-
field_type,
|
|
1341
|
-
Field(description=field_description),
|
|
1342
|
-
)
|
|
1343
|
-
query_args_model = create_model(
|
|
1344
|
-
json_schema_to_rebuild.get(
|
|
1345
|
-
"title", f"{tool_data['name']}_QueryArgs"
|
|
1346
|
-
),
|
|
1347
|
-
**field_definitions,
|
|
1348
|
-
)
|
|
1349
|
-
else: # If schema part is missing or not a dict, create a default empty model
|
|
1350
|
-
query_args_model = create_model(
|
|
1351
|
-
f"{tool_data['name']}_QueryArgs"
|
|
1352
|
-
)
|
|
1353
|
-
|
|
1354
|
-
# If fn_schema was not in tool_data or reconstruction failed badly, default to empty pydantic model
|
|
1355
|
-
if query_args_model is None:
|
|
1356
|
-
query_args_model = create_model(f"{tool_data['name']}_QueryArgs")
|
|
1357
|
-
|
|
1358
|
-
fn = (
|
|
1359
|
-
pickle.loads(tool_data["fn"].encode("latin-1"))
|
|
1360
|
-
if tool_data["fn"]
|
|
1361
|
-
else None
|
|
1362
|
-
)
|
|
1363
|
-
async_fn = (
|
|
1364
|
-
pickle.loads(tool_data["async_fn"].encode("latin-1"))
|
|
1365
|
-
if tool_data["async_fn"]
|
|
1366
|
-
else None
|
|
1367
|
-
)
|
|
1368
|
-
|
|
1369
|
-
tool = VectaraTool.from_defaults(
|
|
1370
|
-
name=tool_data["name"],
|
|
1371
|
-
description=tool_data["description"],
|
|
1372
|
-
fn=fn,
|
|
1373
|
-
async_fn=async_fn,
|
|
1374
|
-
fn_schema=query_args_model, # Re-assign the recreated dynamic model
|
|
1375
|
-
tool_type=ToolType(tool_data["tool_type"]),
|
|
1376
|
-
)
|
|
1377
|
-
tools.append(tool)
|
|
1378
|
-
|
|
1379
|
-
agent = cls(
|
|
1380
|
-
tools=tools,
|
|
1381
|
-
agent_config=agent_config,
|
|
1382
|
-
topic=data["topic"],
|
|
1383
|
-
custom_instructions=data["custom_instructions"],
|
|
1384
|
-
verbose=data["verbose"],
|
|
1385
|
-
fallback_agent_config=fallback_agent_config,
|
|
1386
|
-
workflow_cls=data["workflow_cls"],
|
|
1387
|
-
agent_progress_callback=agent_progress_callback,
|
|
1388
|
-
query_logging_callback=query_logging_callback,
|
|
1389
|
-
)
|
|
1390
|
-
memory = (
|
|
1391
|
-
pickle.loads(data["memory"].encode("latin-1"))
|
|
1392
|
-
if data.get("memory")
|
|
1393
|
-
else None
|
|
1102
|
+
return deserialize_agent_from_dict(
|
|
1103
|
+
cls, data, agent_progress_callback, query_logging_callback
|
|
1394
1104
|
)
|
|
1395
|
-
if memory:
|
|
1396
|
-
agent.agent.memory = memory
|
|
1397
|
-
agent.memory = memory
|
|
1398
|
-
return agent
|