vectara-agentic 0.2.7__py3-none-any.whl → 0.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vectara-agentic might be problematic. Click here for more details.
- tests/test_agent.py +0 -37
- tests/test_serialization.py +110 -0
- vectara_agentic/_callback.py +147 -45
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +18 -6
- vectara_agentic/agent_config.py +11 -0
- vectara_agentic/db_tools.py +205 -39
- vectara_agentic/sub_query_workflow.py +4 -3
- vectara_agentic/tools.py +52 -44
- vectara_agentic/utils.py +39 -11
- {vectara_agentic-0.2.7.dist-info → vectara_agentic-0.2.9.dist-info}/METADATA +2 -2
- {vectara_agentic-0.2.7.dist-info → vectara_agentic-0.2.9.dist-info}/RECORD +15 -14
- {vectara_agentic-0.2.7.dist-info → vectara_agentic-0.2.9.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.2.7.dist-info → vectara_agentic-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.7.dist-info → vectara_agentic-0.2.9.dist-info}/top_level.txt +0 -0
tests/test_agent.py
CHANGED
|
@@ -106,43 +106,6 @@ class TestAgentPackage(unittest.TestCase):
|
|
|
106
106
|
self.assertIsInstance(agent, Agent)
|
|
107
107
|
self.assertEqual(agent._topic, "question answering")
|
|
108
108
|
|
|
109
|
-
def test_serialization(self):
|
|
110
|
-
with ARIZE_LOCK:
|
|
111
|
-
config = AgentConfig(
|
|
112
|
-
agent_type=AgentType.REACT,
|
|
113
|
-
main_llm_provider=ModelProvider.ANTHROPIC,
|
|
114
|
-
tool_llm_provider=ModelProvider.TOGETHER,
|
|
115
|
-
observer=ObserverType.ARIZE_PHOENIX
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
agent = Agent.from_corpus(
|
|
119
|
-
tool_name="RAG Tool",
|
|
120
|
-
agent_config=config,
|
|
121
|
-
vectara_corpus_key="corpus_key",
|
|
122
|
-
vectara_api_key="api_key",
|
|
123
|
-
data_description="information",
|
|
124
|
-
assistant_specialty="question answering",
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
agent_reloaded = agent.loads(agent.dumps())
|
|
128
|
-
agent_reloaded_again = agent_reloaded.loads(agent_reloaded.dumps())
|
|
129
|
-
|
|
130
|
-
self.assertIsInstance(agent_reloaded, Agent)
|
|
131
|
-
self.assertEqual(agent, agent_reloaded)
|
|
132
|
-
self.assertEqual(agent.agent_type, agent_reloaded.agent_type)
|
|
133
|
-
|
|
134
|
-
self.assertEqual(agent.agent_config.observer, agent_reloaded.agent_config.observer)
|
|
135
|
-
self.assertEqual(agent.agent_config.main_llm_provider, agent_reloaded.agent_config.main_llm_provider)
|
|
136
|
-
self.assertEqual(agent.agent_config.tool_llm_provider, agent_reloaded.agent_config.tool_llm_provider)
|
|
137
|
-
|
|
138
|
-
self.assertIsInstance(agent_reloaded, Agent)
|
|
139
|
-
self.assertEqual(agent, agent_reloaded_again)
|
|
140
|
-
self.assertEqual(agent.agent_type, agent_reloaded_again.agent_type)
|
|
141
|
-
|
|
142
|
-
self.assertEqual(agent.agent_config.observer, agent_reloaded_again.agent_config.observer)
|
|
143
|
-
self.assertEqual(agent.agent_config.main_llm_provider, agent_reloaded_again.agent_config.main_llm_provider)
|
|
144
|
-
self.assertEqual(agent.agent_config.tool_llm_provider, agent_reloaded_again.agent_config.tool_llm_provider)
|
|
145
|
-
|
|
146
109
|
def test_chat_history(self):
|
|
147
110
|
tools = [ToolsFactory().create_tool(mult)]
|
|
148
111
|
topic = "AI topic"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import threading
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from vectara_agentic.agent import Agent, AgentType
|
|
6
|
+
from vectara_agentic.agent_config import AgentConfig
|
|
7
|
+
from vectara_agentic.types import ModelProvider, ObserverType
|
|
8
|
+
from vectara_agentic.tools import ToolsFactory
|
|
9
|
+
|
|
10
|
+
from llama_index.core.utilities.sql_wrapper import SQLDatabase
|
|
11
|
+
from sqlalchemy import create_engine
|
|
12
|
+
|
|
13
|
+
def mult(x: float, y: float) -> float:
|
|
14
|
+
return x * y
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
ARIZE_LOCK = threading.Lock()
|
|
18
|
+
|
|
19
|
+
class TestAgentSerialization(unittest.TestCase):
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def tearDown(cls):
|
|
23
|
+
try:
|
|
24
|
+
os.remove('ev_database.db')
|
|
25
|
+
except FileNotFoundError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def test_serialization(self):
|
|
29
|
+
with ARIZE_LOCK:
|
|
30
|
+
config = AgentConfig(
|
|
31
|
+
agent_type=AgentType.REACT,
|
|
32
|
+
main_llm_provider=ModelProvider.ANTHROPIC,
|
|
33
|
+
tool_llm_provider=ModelProvider.TOGETHER,
|
|
34
|
+
observer=ObserverType.ARIZE_PHOENIX
|
|
35
|
+
)
|
|
36
|
+
db_tools = ToolsFactory().database_tools(
|
|
37
|
+
tool_name_prefix = "ev",
|
|
38
|
+
content_description = 'Electric Vehicles in the state of Washington and other population information',
|
|
39
|
+
sql_database = SQLDatabase(create_engine('sqlite:///ev_database.db')),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
tools = [ToolsFactory().create_tool(mult)] + ToolsFactory().standard_tools() + db_tools
|
|
43
|
+
topic = "AI topic"
|
|
44
|
+
instructions = "Always do as your father tells you, if your mother agrees!"
|
|
45
|
+
agent = Agent(
|
|
46
|
+
tools=tools,
|
|
47
|
+
topic=topic,
|
|
48
|
+
custom_instructions=instructions,
|
|
49
|
+
agent_config=config
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
agent_reloaded = agent.loads(agent.dumps())
|
|
53
|
+
agent_reloaded_again = agent_reloaded.loads(agent_reloaded.dumps())
|
|
54
|
+
|
|
55
|
+
self.assertIsInstance(agent_reloaded, Agent)
|
|
56
|
+
self.assertEqual(agent, agent_reloaded)
|
|
57
|
+
self.assertEqual(agent.agent_type, agent_reloaded.agent_type)
|
|
58
|
+
|
|
59
|
+
self.assertEqual(agent.agent_config.observer, agent_reloaded.agent_config.observer)
|
|
60
|
+
self.assertEqual(agent.agent_config.main_llm_provider, agent_reloaded.agent_config.main_llm_provider)
|
|
61
|
+
self.assertEqual(agent.agent_config.tool_llm_provider, agent_reloaded.agent_config.tool_llm_provider)
|
|
62
|
+
|
|
63
|
+
self.assertIsInstance(agent_reloaded, Agent)
|
|
64
|
+
self.assertEqual(agent, agent_reloaded_again)
|
|
65
|
+
self.assertEqual(agent.agent_type, agent_reloaded_again.agent_type)
|
|
66
|
+
|
|
67
|
+
self.assertEqual(agent.agent_config.observer, agent_reloaded_again.agent_config.observer)
|
|
68
|
+
self.assertEqual(agent.agent_config.main_llm_provider, agent_reloaded_again.agent_config.main_llm_provider)
|
|
69
|
+
self.assertEqual(agent.agent_config.tool_llm_provider, agent_reloaded_again.agent_config.tool_llm_provider)
|
|
70
|
+
|
|
71
|
+
def test_serialization_from_corpus(self):
|
|
72
|
+
with ARIZE_LOCK:
|
|
73
|
+
config = AgentConfig(
|
|
74
|
+
agent_type=AgentType.REACT,
|
|
75
|
+
main_llm_provider=ModelProvider.ANTHROPIC,
|
|
76
|
+
tool_llm_provider=ModelProvider.TOGETHER,
|
|
77
|
+
observer=ObserverType.ARIZE_PHOENIX
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
agent = Agent.from_corpus(
|
|
81
|
+
tool_name="RAG Tool",
|
|
82
|
+
agent_config=config,
|
|
83
|
+
vectara_corpus_key="corpus_key",
|
|
84
|
+
vectara_api_key="api_key",
|
|
85
|
+
data_description="information",
|
|
86
|
+
assistant_specialty="question answering",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
agent_reloaded = agent.loads(agent.dumps())
|
|
90
|
+
agent_reloaded_again = agent_reloaded.loads(agent_reloaded.dumps())
|
|
91
|
+
|
|
92
|
+
self.assertIsInstance(agent_reloaded, Agent)
|
|
93
|
+
self.assertEqual(agent, agent_reloaded)
|
|
94
|
+
self.assertEqual(agent.agent_type, agent_reloaded.agent_type)
|
|
95
|
+
|
|
96
|
+
self.assertEqual(agent.agent_config.observer, agent_reloaded.agent_config.observer)
|
|
97
|
+
self.assertEqual(agent.agent_config.main_llm_provider, agent_reloaded.agent_config.main_llm_provider)
|
|
98
|
+
self.assertEqual(agent.agent_config.tool_llm_provider, agent_reloaded.agent_config.tool_llm_provider)
|
|
99
|
+
|
|
100
|
+
self.assertIsInstance(agent_reloaded, Agent)
|
|
101
|
+
self.assertEqual(agent, agent_reloaded_again)
|
|
102
|
+
self.assertEqual(agent.agent_type, agent_reloaded_again.agent_type)
|
|
103
|
+
|
|
104
|
+
self.assertEqual(agent.agent_config.observer, agent_reloaded_again.agent_config.observer)
|
|
105
|
+
self.assertEqual(agent.agent_config.main_llm_provider, agent_reloaded_again.agent_config.main_llm_provider)
|
|
106
|
+
self.assertEqual(agent.agent_config.tool_llm_provider, agent_reloaded_again.agent_config.tool_llm_provider)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
unittest.main()
|
vectara_agentic/_callback.py
CHANGED
|
@@ -4,12 +4,35 @@ Module to handle agent callbacks
|
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
6
|
from typing import Any, Dict, Optional, List, Callable
|
|
7
|
+
from functools import wraps
|
|
7
8
|
|
|
8
9
|
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
|
9
10
|
from llama_index.core.callbacks.schema import CBEventType, EventPayload
|
|
10
11
|
|
|
11
12
|
from .types import AgentStatusType
|
|
12
13
|
|
|
14
|
+
def wrap_callback_fn(callback):
|
|
15
|
+
"""
|
|
16
|
+
Wrap a callback function to ensure it only receives the parameters it can accept.
|
|
17
|
+
This is useful for ensuring that the callback function does not receive unexpected
|
|
18
|
+
parameters, especially when the callback is called from different contexts.
|
|
19
|
+
"""
|
|
20
|
+
if callback is None:
|
|
21
|
+
return None
|
|
22
|
+
try:
|
|
23
|
+
sig = inspect.signature(callback)
|
|
24
|
+
allowed_params = set(sig.parameters.keys())
|
|
25
|
+
except Exception:
|
|
26
|
+
# If we cannot determine the signature, return the callback as is.
|
|
27
|
+
return callback
|
|
28
|
+
|
|
29
|
+
@wraps(callback)
|
|
30
|
+
def new_callback(*args, **kwargs):
|
|
31
|
+
# Filter kwargs to only those that the original callback accepts.
|
|
32
|
+
filtered_kwargs = {k: v for k, v in kwargs.items() if k in allowed_params}
|
|
33
|
+
return callback(*args, **filtered_kwargs)
|
|
34
|
+
|
|
35
|
+
return new_callback
|
|
13
36
|
|
|
14
37
|
class AgentCallbackHandler(BaseCallbackHandler):
|
|
15
38
|
"""
|
|
@@ -24,7 +47,7 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
24
47
|
|
|
25
48
|
def __init__(self, fn: Optional[Callable] = None) -> None:
|
|
26
49
|
super().__init__(event_starts_to_ignore=[], event_ends_to_ignore=[])
|
|
27
|
-
self.fn = fn
|
|
50
|
+
self.fn = wrap_callback_fn(fn)
|
|
28
51
|
|
|
29
52
|
# Existing synchronous methods
|
|
30
53
|
def on_event_start(
|
|
@@ -37,9 +60,11 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
37
60
|
) -> str:
|
|
38
61
|
if self.fn is not None and payload is not None:
|
|
39
62
|
if inspect.iscoroutinefunction(self.fn):
|
|
40
|
-
raise ValueError(
|
|
63
|
+
raise ValueError(
|
|
64
|
+
"Synchronous callback handler cannot use async callback function"
|
|
65
|
+
)
|
|
41
66
|
# Handle events as before
|
|
42
|
-
self._handle_event(event_type, payload)
|
|
67
|
+
self._handle_event(event_type, payload, event_id)
|
|
43
68
|
return event_id
|
|
44
69
|
|
|
45
70
|
def start_trace(self, trace_id: Optional[str] = None) -> None:
|
|
@@ -73,9 +98,11 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
73
98
|
"""
|
|
74
99
|
if self.fn is not None and payload is not None:
|
|
75
100
|
if inspect.iscoroutinefunction(self.fn):
|
|
76
|
-
raise ValueError(
|
|
101
|
+
raise ValueError(
|
|
102
|
+
"Synchronous callback handler cannot use async callback function"
|
|
103
|
+
)
|
|
77
104
|
# Handle events as before
|
|
78
|
-
self._handle_event(event_type, payload)
|
|
105
|
+
self._handle_event(event_type, payload, event_id)
|
|
79
106
|
|
|
80
107
|
# New asynchronous methods
|
|
81
108
|
async def aon_event_start(
|
|
@@ -100,7 +127,7 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
100
127
|
event_id: the event ID
|
|
101
128
|
"""
|
|
102
129
|
if self.fn is not None and payload is not None:
|
|
103
|
-
await self._ahandle_event(event_type, payload)
|
|
130
|
+
await self._ahandle_event(event_type, payload, event_id)
|
|
104
131
|
return event_id
|
|
105
132
|
|
|
106
133
|
async def aon_event_end(
|
|
@@ -114,48 +141,66 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
114
141
|
Handle the end of an event (async)
|
|
115
142
|
"""
|
|
116
143
|
if self.fn is not None and payload is not None:
|
|
117
|
-
await self._ahandle_event(event_type, payload)
|
|
144
|
+
await self._ahandle_event(event_type, payload, event_id)
|
|
118
145
|
|
|
119
146
|
# Helper methods for handling events
|
|
120
|
-
def _handle_event(
|
|
147
|
+
def _handle_event(
|
|
148
|
+
self, event_type: CBEventType, payload: Dict[str, Any], event_id: str
|
|
149
|
+
) -> None:
|
|
121
150
|
if event_type == CBEventType.LLM:
|
|
122
|
-
self._handle_llm(payload)
|
|
151
|
+
self._handle_llm(payload, event_id)
|
|
123
152
|
elif event_type == CBEventType.FUNCTION_CALL:
|
|
124
|
-
self._handle_function_call(payload)
|
|
153
|
+
self._handle_function_call(payload, event_id)
|
|
125
154
|
elif event_type == CBEventType.AGENT_STEP:
|
|
126
|
-
self._handle_agent_step(payload)
|
|
155
|
+
self._handle_agent_step(payload, event_id)
|
|
127
156
|
elif event_type == CBEventType.EXCEPTION:
|
|
128
157
|
print(f"Exception: {payload.get(EventPayload.EXCEPTION)}")
|
|
129
158
|
else:
|
|
130
159
|
print(f"Unknown event type: {event_type}, payload={payload}")
|
|
131
160
|
|
|
132
|
-
async def _ahandle_event(
|
|
161
|
+
async def _ahandle_event(
|
|
162
|
+
self, event_type: CBEventType, payload: Dict[str, Any], event_id: str
|
|
163
|
+
) -> None:
|
|
133
164
|
if event_type == CBEventType.LLM:
|
|
134
|
-
await self._ahandle_llm(payload)
|
|
165
|
+
await self._ahandle_llm(payload, event_id)
|
|
135
166
|
elif event_type == CBEventType.FUNCTION_CALL:
|
|
136
|
-
await self._ahandle_function_call(payload)
|
|
167
|
+
await self._ahandle_function_call(payload, event_id)
|
|
137
168
|
elif event_type == CBEventType.AGENT_STEP:
|
|
138
|
-
await self._ahandle_agent_step(payload)
|
|
169
|
+
await self._ahandle_agent_step(payload, event_id)
|
|
139
170
|
elif event_type == CBEventType.EXCEPTION:
|
|
140
171
|
print(f"Exception: {payload.get(EventPayload.EXCEPTION)}")
|
|
141
172
|
else:
|
|
142
173
|
print(f"Unknown event type: {event_type}, payload={payload}")
|
|
143
174
|
|
|
144
175
|
# Synchronous handlers
|
|
145
|
-
def _handle_llm(
|
|
176
|
+
def _handle_llm(
|
|
177
|
+
self,
|
|
178
|
+
payload: dict,
|
|
179
|
+
event_id: str,
|
|
180
|
+
) -> None:
|
|
146
181
|
if EventPayload.MESSAGES in payload:
|
|
147
182
|
response = str(payload.get(EventPayload.RESPONSE))
|
|
148
183
|
if response and response not in ["None", "assistant: None"]:
|
|
149
184
|
if self.fn:
|
|
150
|
-
self.fn(
|
|
185
|
+
self.fn(
|
|
186
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
187
|
+
msg=response,
|
|
188
|
+
event_id=event_id,
|
|
189
|
+
)
|
|
151
190
|
elif EventPayload.PROMPT in payload:
|
|
152
191
|
prompt = str(payload.get(EventPayload.PROMPT))
|
|
153
192
|
if self.fn:
|
|
154
|
-
self.fn(
|
|
193
|
+
self.fn(
|
|
194
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
195
|
+
msg=prompt,
|
|
196
|
+
event_id=event_id,
|
|
197
|
+
)
|
|
155
198
|
else:
|
|
156
|
-
print(
|
|
199
|
+
print(
|
|
200
|
+
f"vectara-agentic llm callback: no messages or prompt found in payload {payload}"
|
|
201
|
+
)
|
|
157
202
|
|
|
158
|
-
def _handle_function_call(self, payload: dict) -> None:
|
|
203
|
+
def _handle_function_call(self, payload: dict, event_id: str) -> None:
|
|
159
204
|
if EventPayload.FUNCTION_CALL in payload:
|
|
160
205
|
fcall = str(payload.get(EventPayload.FUNCTION_CALL))
|
|
161
206
|
tool = payload.get(EventPayload.TOOL)
|
|
@@ -163,46 +208,77 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
163
208
|
tool_name = tool.name
|
|
164
209
|
if self.fn:
|
|
165
210
|
self.fn(
|
|
166
|
-
AgentStatusType.TOOL_CALL,
|
|
167
|
-
f"Executing '{tool_name}' with arguments: {fcall}",
|
|
211
|
+
status_type=AgentStatusType.TOOL_CALL,
|
|
212
|
+
msg=f"Executing '{tool_name}' with arguments: {fcall}",
|
|
213
|
+
event_id=event_id,
|
|
168
214
|
)
|
|
169
215
|
elif EventPayload.FUNCTION_OUTPUT in payload:
|
|
170
216
|
response = str(payload.get(EventPayload.FUNCTION_OUTPUT))
|
|
171
217
|
if self.fn:
|
|
172
|
-
self.fn(
|
|
218
|
+
self.fn(
|
|
219
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
220
|
+
msg=response,
|
|
221
|
+
event_id=event_id,
|
|
222
|
+
)
|
|
173
223
|
else:
|
|
174
|
-
print(
|
|
224
|
+
print(
|
|
225
|
+
f"Vectara-agentic callback handler: no function call or output found in payload {payload}"
|
|
226
|
+
)
|
|
175
227
|
|
|
176
|
-
def _handle_agent_step(self, payload: dict) -> None:
|
|
228
|
+
def _handle_agent_step(self, payload: dict, event_id: str) -> None:
|
|
177
229
|
if EventPayload.MESSAGES in payload:
|
|
178
230
|
msg = str(payload.get(EventPayload.MESSAGES))
|
|
179
231
|
if self.fn:
|
|
180
|
-
self.fn(
|
|
232
|
+
self.fn(
|
|
233
|
+
status_type=AgentStatusType.AGENT_STEP,
|
|
234
|
+
msg=msg,
|
|
235
|
+
event_id=event_id,
|
|
236
|
+
)
|
|
181
237
|
elif EventPayload.RESPONSE in payload:
|
|
182
238
|
response = str(payload.get(EventPayload.RESPONSE))
|
|
183
239
|
if self.fn:
|
|
184
|
-
self.fn(
|
|
240
|
+
self.fn(
|
|
241
|
+
status_type=AgentStatusType.AGENT_STEP,
|
|
242
|
+
msg=response,
|
|
243
|
+
event_id=event_id,
|
|
244
|
+
)
|
|
185
245
|
else:
|
|
186
|
-
print(
|
|
246
|
+
print(
|
|
247
|
+
f"Vectara-agentic agent_step: no messages or prompt found in payload {payload}"
|
|
248
|
+
)
|
|
187
249
|
|
|
188
250
|
# Asynchronous handlers
|
|
189
|
-
async def _ahandle_llm(self, payload: dict) -> None:
|
|
251
|
+
async def _ahandle_llm(self, payload: dict, event_id: str) -> None:
|
|
190
252
|
if EventPayload.MESSAGES in payload:
|
|
191
253
|
response = str(payload.get(EventPayload.RESPONSE))
|
|
192
254
|
if response and response not in ["None", "assistant: None"]:
|
|
193
255
|
if self.fn:
|
|
194
256
|
if inspect.iscoroutinefunction(self.fn):
|
|
195
|
-
await self.fn(
|
|
257
|
+
await self.fn(
|
|
258
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
259
|
+
msg=response,
|
|
260
|
+
event_id=event_id,
|
|
261
|
+
)
|
|
196
262
|
else:
|
|
197
|
-
self.fn(
|
|
263
|
+
self.fn(
|
|
264
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
265
|
+
msg=response,
|
|
266
|
+
event_id=event_id,
|
|
267
|
+
)
|
|
198
268
|
elif EventPayload.PROMPT in payload:
|
|
199
269
|
prompt = str(payload.get(EventPayload.PROMPT))
|
|
200
270
|
if self.fn:
|
|
201
|
-
self.fn(
|
|
271
|
+
self.fn(
|
|
272
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
273
|
+
msg=prompt,
|
|
274
|
+
event_id=event_id,
|
|
275
|
+
)
|
|
202
276
|
else:
|
|
203
|
-
print(
|
|
277
|
+
print(
|
|
278
|
+
f"vectara-agentic llm callback: no messages or prompt found in payload {payload}"
|
|
279
|
+
)
|
|
204
280
|
|
|
205
|
-
async def _ahandle_function_call(self, payload: dict) -> None:
|
|
281
|
+
async def _ahandle_function_call(self, payload: dict, event_id: str) -> None:
|
|
206
282
|
if EventPayload.FUNCTION_CALL in payload:
|
|
207
283
|
fcall = str(payload.get(EventPayload.FUNCTION_CALL))
|
|
208
284
|
tool = payload.get(EventPayload.TOOL)
|
|
@@ -211,38 +287,64 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
211
287
|
if self.fn:
|
|
212
288
|
if inspect.iscoroutinefunction(self.fn):
|
|
213
289
|
await self.fn(
|
|
214
|
-
AgentStatusType.TOOL_CALL,
|
|
215
|
-
f"Executing '{tool_name}' with arguments: {fcall}",
|
|
290
|
+
status_type=AgentStatusType.TOOL_CALL,
|
|
291
|
+
msg=f"Executing '{tool_name}' with arguments: {fcall}",
|
|
292
|
+
event_id=event_id,
|
|
216
293
|
)
|
|
217
294
|
else:
|
|
218
295
|
self.fn(
|
|
219
|
-
AgentStatusType.TOOL_CALL,
|
|
220
|
-
f"Executing '{tool_name}' with arguments: {fcall}",
|
|
296
|
+
status_type=AgentStatusType.TOOL_CALL,
|
|
297
|
+
msg=f"Executing '{tool_name}' with arguments: {fcall}",
|
|
298
|
+
event_id=event_id,
|
|
221
299
|
)
|
|
222
300
|
elif EventPayload.FUNCTION_OUTPUT in payload:
|
|
223
301
|
if self.fn:
|
|
224
302
|
response = str(payload.get(EventPayload.FUNCTION_OUTPUT))
|
|
225
303
|
if inspect.iscoroutinefunction(self.fn):
|
|
226
|
-
await self.fn(
|
|
304
|
+
await self.fn(
|
|
305
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
306
|
+
msg=response,
|
|
307
|
+
event_id=event_id,
|
|
308
|
+
)
|
|
227
309
|
else:
|
|
228
|
-
self.fn(
|
|
310
|
+
self.fn(
|
|
311
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
312
|
+
msg=response,
|
|
313
|
+
event_id=event_id,
|
|
314
|
+
)
|
|
229
315
|
else:
|
|
230
316
|
print(f"No function call or output found in payload {payload}")
|
|
231
317
|
|
|
232
|
-
async def _ahandle_agent_step(self, payload: dict) -> None:
|
|
318
|
+
async def _ahandle_agent_step(self, payload: dict, event_id: str) -> None:
|
|
233
319
|
if EventPayload.MESSAGES in payload:
|
|
234
320
|
if self.fn:
|
|
235
321
|
msg = str(payload.get(EventPayload.MESSAGES))
|
|
236
322
|
if inspect.iscoroutinefunction(self.fn):
|
|
237
|
-
await self.fn(
|
|
323
|
+
await self.fn(
|
|
324
|
+
status_type=AgentStatusType.AGENT_STEP,
|
|
325
|
+
msg=msg,
|
|
326
|
+
event_id=event_id,
|
|
327
|
+
)
|
|
238
328
|
else:
|
|
239
|
-
self.fn(
|
|
329
|
+
self.fn(
|
|
330
|
+
status_type=AgentStatusType.AGENT_STEP,
|
|
331
|
+
msg=msg,
|
|
332
|
+
event_id=event_id,
|
|
333
|
+
)
|
|
240
334
|
elif EventPayload.RESPONSE in payload:
|
|
241
335
|
if self.fn:
|
|
242
336
|
response = str(payload.get(EventPayload.RESPONSE))
|
|
243
337
|
if inspect.iscoroutinefunction(self.fn):
|
|
244
|
-
await self.fn(
|
|
338
|
+
await self.fn(
|
|
339
|
+
status_type=AgentStatusType.AGENT_STEP,
|
|
340
|
+
msg=response,
|
|
341
|
+
event_id=event_id,
|
|
342
|
+
)
|
|
245
343
|
else:
|
|
246
|
-
self.fn(
|
|
344
|
+
self.fn(
|
|
345
|
+
status_type=AgentStatusType.AGENT_STEP,
|
|
346
|
+
msg=response,
|
|
347
|
+
event_id=event_id,
|
|
348
|
+
)
|
|
247
349
|
else:
|
|
248
350
|
print(f"No messages or prompt found in payload {payload}")
|
vectara_agentic/_version.py
CHANGED
vectara_agentic/agent.py
CHANGED
|
@@ -33,7 +33,7 @@ from llama_index.core.agent.types import BaseAgent
|
|
|
33
33
|
from llama_index.core.workflow import Workflow
|
|
34
34
|
|
|
35
35
|
from .types import (
|
|
36
|
-
AgentType, AgentStatusType, LLMRole, ToolType,
|
|
36
|
+
AgentType, AgentStatusType, LLMRole, ToolType, ModelProvider,
|
|
37
37
|
AgentResponse, AgentStreamingResponse, AgentConfigType
|
|
38
38
|
)
|
|
39
39
|
from .utils import get_llm, get_tokenizer_for_model
|
|
@@ -278,6 +278,10 @@ class Agent:
|
|
|
278
278
|
llm.callback_manager = llm_callback_manager
|
|
279
279
|
|
|
280
280
|
if agent_type == AgentType.FUNCTION_CALLING:
|
|
281
|
+
if config.tool_llm_provider == ModelProvider.OPENAI:
|
|
282
|
+
raise ValueError(
|
|
283
|
+
"Vectara-agentic: Function calling agent type is not supported with the OpenAI LLM."
|
|
284
|
+
)
|
|
281
285
|
prompt = _get_prompt(GENERAL_PROMPT_TEMPLATE, self._topic, self._custom_instructions)
|
|
282
286
|
agent = FunctionCallingAgent.from_tools(
|
|
283
287
|
tools=self.tools,
|
|
@@ -286,7 +290,7 @@ class Agent:
|
|
|
286
290
|
verbose=self.verbose,
|
|
287
291
|
max_function_calls=config.max_reasoning_steps,
|
|
288
292
|
callback_manager=llm_callback_manager,
|
|
289
|
-
system_prompt
|
|
293
|
+
system_prompt=prompt,
|
|
290
294
|
allow_parallel_tool_calls=True,
|
|
291
295
|
)
|
|
292
296
|
elif agent_type == AgentType.REACT:
|
|
@@ -301,6 +305,10 @@ class Agent:
|
|
|
301
305
|
callable_manager=llm_callback_manager,
|
|
302
306
|
)
|
|
303
307
|
elif agent_type == AgentType.OPENAI:
|
|
308
|
+
if config.tool_llm_provider != ModelProvider.OPENAI:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
"Vectara-agentic: OPENAI agent type requires the OpenAI LLM."
|
|
311
|
+
)
|
|
304
312
|
prompt = _get_prompt(GENERAL_PROMPT_TEMPLATE, self._topic, self._custom_instructions)
|
|
305
313
|
agent = OpenAIAgent.from_tools(
|
|
306
314
|
tools=self.tools,
|
|
@@ -768,6 +776,7 @@ class Agent:
|
|
|
768
776
|
"""
|
|
769
777
|
max_attempts = 4 if self.fallback_agent_config else 2
|
|
770
778
|
attempt = 0
|
|
779
|
+
orig_llm = self.llm.metadata.model_name
|
|
771
780
|
while attempt < max_attempts:
|
|
772
781
|
try:
|
|
773
782
|
current_agent = self._get_current_agent()
|
|
@@ -788,16 +797,20 @@ class Agent:
|
|
|
788
797
|
agent_response.async_response_gen = _stream_response_wrapper # Override the generator
|
|
789
798
|
return agent_response
|
|
790
799
|
|
|
791
|
-
except Exception:
|
|
800
|
+
except Exception as e:
|
|
801
|
+
last_error = e
|
|
792
802
|
if attempt >= 2:
|
|
793
803
|
if self.verbose:
|
|
794
|
-
print("LLM call failed. Switching agent configuration.")
|
|
804
|
+
print(f"LLM call failed on attempt {attempt}. Switching agent configuration.")
|
|
795
805
|
self._switch_agent_config()
|
|
796
806
|
time.sleep(1)
|
|
797
807
|
attempt += 1
|
|
798
808
|
|
|
799
809
|
return AgentResponse(
|
|
800
|
-
response=
|
|
810
|
+
response=(
|
|
811
|
+
f"For {orig_llm} LLM - failure can't be resolved after "
|
|
812
|
+
f"{max_attempts} attempts ({last_error})."
|
|
813
|
+
)
|
|
801
814
|
)
|
|
802
815
|
|
|
803
816
|
#
|
|
@@ -861,7 +874,6 @@ class Agent:
|
|
|
861
874
|
def to_dict(self) -> Dict[str, Any]:
|
|
862
875
|
"""Serialize the Agent instance to a dictionary."""
|
|
863
876
|
tool_info = []
|
|
864
|
-
|
|
865
877
|
for tool in self.tools:
|
|
866
878
|
if hasattr(tool.metadata, "fn_schema"):
|
|
867
879
|
fn_schema_cls = tool.metadata.fn_schema
|
vectara_agentic/agent_config.py
CHANGED
|
@@ -71,6 +71,17 @@ class AgentConfig:
|
|
|
71
71
|
default_factory=lambda: int(os.getenv("VECTARA_AGENTIC_MAX_REASONING_STEPS", "50"))
|
|
72
72
|
)
|
|
73
73
|
|
|
74
|
+
def __post_init__(self):
|
|
75
|
+
# Use object.__setattr__ since the dataclass is frozen
|
|
76
|
+
if isinstance(self.agent_type, str):
|
|
77
|
+
object.__setattr__(self, "agent_type", AgentType(self.agent_type))
|
|
78
|
+
if isinstance(self.main_llm_provider, str):
|
|
79
|
+
object.__setattr__(self, "main_llm_provider", ModelProvider(self.main_llm_provider))
|
|
80
|
+
if isinstance(self.tool_llm_provider, str):
|
|
81
|
+
object.__setattr__(self, "tool_llm_provider", ModelProvider(self.tool_llm_provider))
|
|
82
|
+
if isinstance(self.observer, str):
|
|
83
|
+
object.__setattr__(self, "observer", ObserverType(self.observer))
|
|
84
|
+
|
|
74
85
|
def to_dict(self) -> dict:
|
|
75
86
|
"""
|
|
76
87
|
Convert the AgentConfig to a dictionary.
|
vectara_agentic/db_tools.py
CHANGED
|
@@ -1,38 +1,122 @@
|
|
|
1
1
|
"""
|
|
2
|
-
This module contains the code
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
This module contains the code adapted from DatabaseToolSpec
|
|
3
|
+
It makes the following adjustments:
|
|
4
|
+
* Adds load_sample_data and load_unique_values methods.
|
|
5
|
+
* Fixes serialization.
|
|
6
|
+
* Makes sure the load_data method returns a list of text values from the database (and not Document[] objects).
|
|
7
|
+
* Limits the returned rows to self.max_rows.
|
|
5
8
|
"""
|
|
6
|
-
from
|
|
7
|
-
|
|
9
|
+
from typing import Any, Optional, List, Awaitable, Callable
|
|
10
|
+
import asyncio
|
|
11
|
+
from inspect import signature
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
from sqlalchemy import MetaData, text
|
|
14
|
+
from sqlalchemy.engine import Engine
|
|
15
|
+
from sqlalchemy.exc import NoSuchTableError
|
|
16
|
+
from sqlalchemy.schema import CreateTable
|
|
17
|
+
|
|
18
|
+
from llama_index.core.readers.base import BaseReader
|
|
19
|
+
from llama_index.core.utilities.sql_wrapper import SQLDatabase
|
|
20
|
+
from llama_index.core.schema import Document
|
|
21
|
+
from llama_index.core.tools.function_tool import FunctionTool
|
|
22
|
+
from llama_index.core.tools.types import ToolMetadata
|
|
23
|
+
from llama_index.core.tools.utils import create_schema_from_function
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
AsyncCallable = Callable[..., Awaitable[Any]]
|
|
27
|
+
|
|
28
|
+
class DatabaseTools(BaseReader):
|
|
29
|
+
"""Database tools for vectara-agentic
|
|
30
|
+
This class provides a set of tools to interact with a database.
|
|
31
|
+
It allows you to load data, list tables, describe tables, and load unique values.
|
|
32
|
+
It also provides a method to load sample data from a specified table.
|
|
15
33
|
"""
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
spec_functions = [
|
|
35
|
+
"load_data", "load_sample_data", "list_tables",
|
|
36
|
+
"describe_tables", "load_unique_values",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*args: Any,
|
|
42
|
+
max_rows: int = 1000,
|
|
43
|
+
sql_database: Optional[SQLDatabase] = None,
|
|
44
|
+
engine: Optional[Engine] = None,
|
|
45
|
+
uri: Optional[str] = None,
|
|
46
|
+
scheme: Optional[str] = None,
|
|
47
|
+
host: Optional[str] = None,
|
|
48
|
+
port: Optional[str] = None,
|
|
49
|
+
user: Optional[str] = None,
|
|
50
|
+
password: Optional[str] = None,
|
|
51
|
+
dbname: Optional[str] = None,
|
|
52
|
+
**kwargs: Any,
|
|
53
|
+
) -> None:
|
|
18
54
|
self.max_rows = max_rows
|
|
19
55
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
56
|
+
if sql_database:
|
|
57
|
+
self.sql_database = sql_database
|
|
58
|
+
elif engine:
|
|
59
|
+
self.sql_database = SQLDatabase(engine, *args, **kwargs)
|
|
60
|
+
elif uri:
|
|
61
|
+
self.uri = uri
|
|
62
|
+
self.sql_database = SQLDatabase.from_uri(uri, *args, **kwargs)
|
|
63
|
+
elif (scheme and host and port and user and password and dbname):
|
|
64
|
+
uri = f"{scheme}://{user}:{password}@{host}:{port}/{dbname}"
|
|
65
|
+
self.uri = uri
|
|
66
|
+
self.sql_database = SQLDatabase.from_uri(uri, *args, **kwargs)
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
"You must provide either a SQLDatabase, "
|
|
70
|
+
"a SQL Alchemy Engine, a valid connection URI, or a valid "
|
|
71
|
+
"set of credentials."
|
|
72
|
+
)
|
|
73
|
+
self._uri = getattr(self, "uri", None) or str(self.sql_database.engine.url)
|
|
74
|
+
self._metadata = MetaData()
|
|
75
|
+
self._metadata.reflect(bind=self.sql_database.engine)
|
|
76
|
+
|
|
77
|
+
def _get_metadata_from_fn_name(
|
|
78
|
+
self, fn_name: Callable,
|
|
79
|
+
) -> Optional[ToolMetadata]:
|
|
80
|
+
"""Return map from function name.
|
|
81
|
+
|
|
82
|
+
Return type is Optional, meaning that the schema can be None.
|
|
83
|
+
In this case, it's up to the downstream tool implementation to infer the schema.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
func = getattr(self, fn_name)
|
|
87
|
+
except AttributeError:
|
|
88
|
+
return None
|
|
89
|
+
name = fn_name
|
|
90
|
+
docstring = func.__doc__ or ""
|
|
91
|
+
description = f"{name}{signature(func)}\n{docstring}"
|
|
92
|
+
fn_schema = create_schema_from_function(fn_name, getattr(self, fn_name))
|
|
93
|
+
return ToolMetadata(name=name, description=description, fn_schema=fn_schema)
|
|
94
|
+
|
|
95
|
+
def _load_data(self, query: str) -> List[Document]:
|
|
96
|
+
documents = []
|
|
97
|
+
with self.sql_database.engine.connect() as connection:
|
|
98
|
+
if query is None:
|
|
99
|
+
raise ValueError("A query parameter is necessary to filter the data")
|
|
100
|
+
result = connection.execute(text(query))
|
|
101
|
+
for item in result.fetchall():
|
|
102
|
+
doc_str = ", ".join([str(entry) for entry in item])
|
|
103
|
+
documents.append(Document(text=doc_str))
|
|
104
|
+
return documents
|
|
26
105
|
|
|
106
|
+
def load_data(self, *args: Any, **load_kwargs: Any) -> List[str]:
|
|
107
|
+
"""Query and load data from the Database, returning a list of Documents.
|
|
27
108
|
Args:
|
|
28
109
|
query (str): an SQL query to filter tables and rows.
|
|
29
|
-
|
|
30
110
|
Returns:
|
|
31
|
-
List[
|
|
111
|
+
List[Document]: a list of Document objects from the database.
|
|
32
112
|
"""
|
|
113
|
+
query = args[0] if args else load_kwargs.get("args",{}).get("query")
|
|
114
|
+
if query is None:
|
|
115
|
+
raise ValueError("A query parameter is necessary to filter the data")
|
|
116
|
+
|
|
33
117
|
count_query = f"SELECT COUNT(*) FROM ({query})"
|
|
34
118
|
try:
|
|
35
|
-
count_rows = self.
|
|
119
|
+
count_rows = self._load_data(count_query)
|
|
36
120
|
except Exception as e:
|
|
37
121
|
return [f"Error ({str(e)}) occurred while counting number of rows"]
|
|
38
122
|
num_rows = int(count_rows[0].text)
|
|
@@ -42,19 +126,12 @@ class DBLoadData(DBTool):
|
|
|
42
126
|
"Please refactor your query to make it return less rows. "
|
|
43
127
|
]
|
|
44
128
|
try:
|
|
45
|
-
res = self.
|
|
129
|
+
res = self._load_data(query)
|
|
46
130
|
except Exception as e:
|
|
47
131
|
return [f"Error ({str(e)}) occurred while executing the query {query}"]
|
|
48
132
|
return [d.text for d in res]
|
|
49
133
|
|
|
50
|
-
|
|
51
|
-
"""
|
|
52
|
-
A tool to load a sample of data from the specified database table.
|
|
53
|
-
|
|
54
|
-
This tool fetches the first num_rows (default 25) rows from the given table
|
|
55
|
-
using a provided database query function.
|
|
56
|
-
"""
|
|
57
|
-
def __call__(self, table_name: str, num_rows: int = 25) -> Any:
|
|
134
|
+
def load_sample_data(self, table_name: str, num_rows: int = 25) -> Any:
|
|
58
135
|
"""
|
|
59
136
|
Fetches the first num_rows rows from the specified database table.
|
|
60
137
|
|
|
@@ -65,16 +142,39 @@ class DBLoadSampleData(DBTool):
|
|
|
65
142
|
Any: The result of the database query.
|
|
66
143
|
"""
|
|
67
144
|
try:
|
|
68
|
-
res = self.
|
|
145
|
+
res = self._load_data(f"SELECT * FROM {table_name} LIMIT {num_rows}")
|
|
69
146
|
except Exception as e:
|
|
70
147
|
return [f"Error ({str(e)}) occurred while loading sample data for table {table_name}"]
|
|
71
|
-
return res
|
|
148
|
+
return [d.text for d in res]
|
|
72
149
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
150
|
+
def list_tables(self) -> List[str]:
|
|
151
|
+
"""List all tables in the database.
|
|
152
|
+
Returns:
|
|
153
|
+
List[str]: A list of table names in the database.
|
|
154
|
+
"""
|
|
155
|
+
return [x.name for x in self._metadata.sorted_tables]
|
|
156
|
+
|
|
157
|
+
def describe_tables(self, tables: Optional[List[str]] = None) -> str:
|
|
158
|
+
"""Describe the tables in the database.
|
|
159
|
+
Args:
|
|
160
|
+
tables (Optional[List[str]]): A list of table names to describe. If None, all tables are described.
|
|
161
|
+
Returns:
|
|
162
|
+
str: A string representation of the table schemas.
|
|
163
|
+
"""
|
|
164
|
+
table_names = tables or [table.name for table in self._metadata.sorted_tables]
|
|
165
|
+
table_schemas = []
|
|
166
|
+
for table_name in table_names:
|
|
167
|
+
table = next(
|
|
168
|
+
(table for table in self._metadata.sorted_tables if table.name == table_name),
|
|
169
|
+
None,
|
|
170
|
+
)
|
|
171
|
+
if table is None:
|
|
172
|
+
raise NoSuchTableError(f"Table '{table_name}' does not exist.")
|
|
173
|
+
schema = str(CreateTable(table).compile(self.sql_database.engine))
|
|
174
|
+
table_schemas.append(f"{schema}\n")
|
|
175
|
+
return "\n".join(table_schemas)
|
|
176
|
+
|
|
177
|
+
def load_unique_values(self, table_name: str, columns: list[str], num_vals: int = 200) -> Any:
|
|
78
178
|
"""
|
|
79
179
|
Fetches the first num_vals unique values from the specified columns of the database table.
|
|
80
180
|
|
|
@@ -89,8 +189,74 @@ class DBLoadUniqueValues(DBTool):
|
|
|
89
189
|
res = {}
|
|
90
190
|
try:
|
|
91
191
|
for column in columns:
|
|
92
|
-
unique_vals = self.
|
|
192
|
+
unique_vals = self._load_data(f'SELECT DISTINCT "{column}" FROM {table_name} LIMIT {num_vals}')
|
|
93
193
|
res[column] = [d.text for d in unique_vals]
|
|
94
194
|
except Exception as e:
|
|
95
195
|
return {f"Error ({str(e)}) occurred while loading unique values for table {table_name}"}
|
|
96
196
|
return res
|
|
197
|
+
|
|
198
|
+
def to_tool_list(self) -> List[FunctionTool]:
|
|
199
|
+
"""
|
|
200
|
+
Returns a list of tools available.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
tool_list = []
|
|
204
|
+
for tool_name in self.spec_functions:
|
|
205
|
+
func_sync = None
|
|
206
|
+
func_async = None
|
|
207
|
+
func = getattr(self, tool_name)
|
|
208
|
+
if asyncio.iscoroutinefunction(func):
|
|
209
|
+
func_async = func
|
|
210
|
+
else:
|
|
211
|
+
func_sync = func
|
|
212
|
+
metadata = self._get_metadata_from_fn_name(tool_name)
|
|
213
|
+
|
|
214
|
+
if func_sync is None:
|
|
215
|
+
if func_async is not None:
|
|
216
|
+
func_sync = patch_sync(func_async)
|
|
217
|
+
else:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"Could not retrieve a function for spec: {tool_name}"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
tool = FunctionTool.from_defaults(
|
|
223
|
+
fn=func_sync,
|
|
224
|
+
async_fn=func_async,
|
|
225
|
+
tool_metadata=metadata,
|
|
226
|
+
)
|
|
227
|
+
tool_list.append(tool)
|
|
228
|
+
return tool_list
|
|
229
|
+
|
|
230
|
+
# Custom pickling: exclude unpickleable objects
|
|
231
|
+
def __getstate__(self):
|
|
232
|
+
state = self.__dict__.copy()
|
|
233
|
+
if "sql_database" in state:
|
|
234
|
+
state["sql_database_state"] = {"uri": self._uri}
|
|
235
|
+
del state["sql_database"]
|
|
236
|
+
if "_metadata" in state:
|
|
237
|
+
del state["_metadata"]
|
|
238
|
+
return state
|
|
239
|
+
|
|
240
|
+
def __setstate__(self, state):
|
|
241
|
+
self.__dict__.update(state)
|
|
242
|
+
# Reconstruct the sql_database if it was removed
|
|
243
|
+
if "sql_database_state" in state:
|
|
244
|
+
uri = state["sql_database_state"].get("uri")
|
|
245
|
+
if uri:
|
|
246
|
+
self.sql_database = SQLDatabase.from_uri(uri)
|
|
247
|
+
self._uri = uri
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError("Cannot reconstruct SQLDatabase without URI")
|
|
250
|
+
# Rebuild metadata after restoring the engine
|
|
251
|
+
self._metadata = MetaData()
|
|
252
|
+
self._metadata.reflect(bind=self.sql_database.engine)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def patch_sync(func_async: AsyncCallable) -> Callable:
|
|
256
|
+
"""Patch sync function from async function."""
|
|
257
|
+
|
|
258
|
+
def patched_sync(*args: Any, **kwargs: Any) -> Any:
|
|
259
|
+
loop = asyncio.get_event_loop()
|
|
260
|
+
return loop.run_until_complete(func_async(*args, **kwargs))
|
|
261
|
+
|
|
262
|
+
return patched_sync
|
|
@@ -102,7 +102,7 @@ class SubQuestionQueryWorkflow(Workflow):
|
|
|
102
102
|
- What is the name of the mayor of San Jose?
|
|
103
103
|
Here is the user question: {await ctx.get('original_query')}.
|
|
104
104
|
Here are previous chat messages: {chat_history}.
|
|
105
|
-
And here is the list of tools: {
|
|
105
|
+
And here is the list of tools: {ev.tools}
|
|
106
106
|
""",
|
|
107
107
|
)
|
|
108
108
|
|
|
@@ -236,6 +236,7 @@ class SequentialSubQuestionsWorkflow(Workflow):
|
|
|
236
236
|
print(f"Query is {await ctx.get('original_query')}")
|
|
237
237
|
|
|
238
238
|
llm = await ctx.get("llm")
|
|
239
|
+
orig_query = await ctx.get("original_query")
|
|
239
240
|
response = llm.complete(
|
|
240
241
|
f"""
|
|
241
242
|
Given a user question, and a list of tools, output a list of
|
|
@@ -256,9 +257,9 @@ class SequentialSubQuestionsWorkflow(Workflow):
|
|
|
256
257
|
- Who is the mayor of this city?
|
|
257
258
|
The answer to the first question is San Jose, which is given as context to the second question.
|
|
258
259
|
The answer to the second question is Matt Mahan.
|
|
259
|
-
Here is the user question: {
|
|
260
|
+
Here is the user question: {orig_query}.
|
|
260
261
|
Here are previous chat messages: {chat_history}.
|
|
261
|
-
And here is the list of tools: {
|
|
262
|
+
And here is the list of tools: {ev.tools}
|
|
262
263
|
""",
|
|
263
264
|
)
|
|
264
265
|
|
vectara_agentic/tools.py
CHANGED
|
@@ -6,6 +6,7 @@ import inspect
|
|
|
6
6
|
import re
|
|
7
7
|
import importlib
|
|
8
8
|
import os
|
|
9
|
+
import asyncio
|
|
9
10
|
|
|
10
11
|
from typing import Callable, List, Dict, Any, Optional, Union, Type
|
|
11
12
|
from pydantic import BaseModel, Field, create_model
|
|
@@ -20,8 +21,8 @@ from llama_index.core.workflow.context import Context
|
|
|
20
21
|
|
|
21
22
|
from .types import ToolType
|
|
22
23
|
from .tools_catalog import ToolsCatalog, get_bad_topics
|
|
23
|
-
from .db_tools import
|
|
24
|
-
from .utils import is_float,
|
|
24
|
+
from .db_tools import DatabaseTools
|
|
25
|
+
from .utils import is_float, summarize_documents
|
|
25
26
|
from .agent_config import AgentConfig
|
|
26
27
|
|
|
27
28
|
LI_packages = {
|
|
@@ -31,7 +32,6 @@ LI_packages = {
|
|
|
31
32
|
"exa": ToolType.QUERY,
|
|
32
33
|
"neo4j": ToolType.QUERY,
|
|
33
34
|
"kuzu": ToolType.QUERY,
|
|
34
|
-
"database": ToolType.QUERY,
|
|
35
35
|
"google": {
|
|
36
36
|
"GmailToolSpec": {
|
|
37
37
|
"load_data": ToolType.QUERY,
|
|
@@ -109,9 +109,20 @@ class VectaraTool(FunctionTool):
|
|
|
109
109
|
fn, name, description, return_direct, fn_schema, async_fn, tool_metadata,
|
|
110
110
|
callback, async_callback
|
|
111
111
|
)
|
|
112
|
-
vectara_tool = cls(
|
|
112
|
+
vectara_tool = cls(
|
|
113
|
+
tool_type=tool_type, fn=tool.fn, metadata=tool.metadata, async_fn=tool.async_fn,
|
|
114
|
+
)
|
|
113
115
|
return vectara_tool
|
|
114
116
|
|
|
117
|
+
def __str__(self) -> str:
|
|
118
|
+
return (
|
|
119
|
+
f"Tool(name={self.metadata.name}, "
|
|
120
|
+
f"Tool metadata={self.metadata})"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def __repr__(self) -> str:
|
|
124
|
+
return str(self)
|
|
125
|
+
|
|
115
126
|
def __eq__(self, other):
|
|
116
127
|
if not isinstance(other, VectaraTool):
|
|
117
128
|
return False
|
|
@@ -508,17 +519,29 @@ class VectaraToolFactory:
|
|
|
508
519
|
raw_input={"args": args, "kwargs": kwargs},
|
|
509
520
|
raw_output={"response": msg},
|
|
510
521
|
)
|
|
511
|
-
tool_output = "Matching documents:\n"
|
|
512
522
|
unique_ids = set()
|
|
523
|
+
docs = []
|
|
513
524
|
for doc in response:
|
|
514
525
|
if doc.id_ in unique_ids:
|
|
515
526
|
continue
|
|
516
527
|
unique_ids.add(doc.id_)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
528
|
+
docs.append((doc.id_, doc.metadata))
|
|
529
|
+
tool_output = "Matching documents:\n"
|
|
530
|
+
if summarize:
|
|
531
|
+
summaries_dict = asyncio.run(
|
|
532
|
+
summarize_documents(
|
|
533
|
+
self.vectara_corpus_key,
|
|
534
|
+
self.vectara_api_key,
|
|
535
|
+
list(unique_ids)
|
|
536
|
+
)
|
|
537
|
+
)
|
|
538
|
+
for doc_id, metadata in docs:
|
|
539
|
+
summary = summaries_dict.get(doc_id, "")
|
|
540
|
+
tool_output += f"document_id: '{doc_id}'\nmetadata: '{metadata}'\nsummary: '{summary}'\n\n"
|
|
541
|
+
else:
|
|
542
|
+
for doc_id, metadata in docs:
|
|
543
|
+
tool_output += f"document_id: '{doc_id}'\nmetadata: '{metadata}'\n\n"
|
|
544
|
+
|
|
522
545
|
out = ToolOutput(
|
|
523
546
|
tool_name=search_function.__name__,
|
|
524
547
|
content=tool_output,
|
|
@@ -529,12 +552,14 @@ class VectaraToolFactory:
|
|
|
529
552
|
|
|
530
553
|
base_params = [
|
|
531
554
|
inspect.Parameter("query", inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
|
|
532
|
-
inspect.Parameter("top_k", inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
555
|
+
inspect.Parameter("top_k", inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
533
556
|
inspect.Parameter("summarize", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=True, annotation=bool),
|
|
534
557
|
]
|
|
535
558
|
search_tool_extra_desc = tool_description + "\n" + """
|
|
559
|
+
This tool is meant to perform a search for relevant documents, it is not meant for asking questions.
|
|
536
560
|
The response includes metadata about each relevant document.
|
|
537
|
-
If summarize=True, it also includes a summary of each document
|
|
561
|
+
If summarize=True, it also includes a summary of each document, but takes a lot longer to respond,
|
|
562
|
+
so avoid using it unless necessary.
|
|
538
563
|
"""
|
|
539
564
|
|
|
540
565
|
tool = _create_tool_from_dynamic_function(
|
|
@@ -905,7 +930,7 @@ class ToolsFactory:
|
|
|
905
930
|
user: str = "postgres",
|
|
906
931
|
password: str = "Password",
|
|
907
932
|
dbname: str = "postgres",
|
|
908
|
-
max_rows: int =
|
|
933
|
+
max_rows: int = 1000,
|
|
909
934
|
) -> List[VectaraTool]:
|
|
910
935
|
"""
|
|
911
936
|
Returns a list of database tools.
|
|
@@ -923,24 +948,16 @@ class ToolsFactory:
|
|
|
923
948
|
dbname (str, optional): The database name. Defaults to "postgres".
|
|
924
949
|
You must specify either the sql_database object or the scheme, host, port, user, password, and dbname.
|
|
925
950
|
max_rows (int, optional): if specified, instructs the load_data tool to never return more than max_rows
|
|
926
|
-
rows. Defaults to
|
|
951
|
+
rows. Defaults to 1000.
|
|
927
952
|
|
|
928
953
|
Returns:
|
|
929
954
|
List[VectaraTool]: A list of VectaraTool objects.
|
|
930
955
|
"""
|
|
931
956
|
if sql_database:
|
|
932
|
-
|
|
933
|
-
tool_package_name="database",
|
|
934
|
-
tool_spec_name="DatabaseToolSpec",
|
|
935
|
-
tool_name_prefix=tool_name_prefix,
|
|
936
|
-
sql_database=sql_database,
|
|
937
|
-
)
|
|
957
|
+
dbt = DatabaseTools(sql_database=sql_database)
|
|
938
958
|
else:
|
|
939
959
|
if scheme in ["postgresql", "mysql", "sqlite", "mssql", "oracle"]:
|
|
940
|
-
|
|
941
|
-
tool_package_name="database",
|
|
942
|
-
tool_spec_name="DatabaseToolSpec",
|
|
943
|
-
tool_name_prefix=tool_name_prefix,
|
|
960
|
+
dbt = DatabaseTools(
|
|
944
961
|
scheme=scheme,
|
|
945
962
|
host=host,
|
|
946
963
|
port=port,
|
|
@@ -955,28 +972,19 @@ class ToolsFactory:
|
|
|
955
972
|
)
|
|
956
973
|
|
|
957
974
|
# Update tools with description
|
|
975
|
+
tools = dbt.to_tool_list()
|
|
976
|
+
vtools = []
|
|
958
977
|
for tool in tools:
|
|
959
978
|
if content_description:
|
|
960
979
|
tool.metadata.description = (
|
|
961
980
|
tool.metadata.description + f"The database tables include data about {content_description}."
|
|
962
981
|
)
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
sample_data_fn = DBLoadSampleData(load_data_fn_original)
|
|
973
|
-
sample_data_fn.__name__ = f"{tool_name_prefix}_load_sample_data"
|
|
974
|
-
sample_data_tool = self.create_tool(sample_data_fn, ToolType.QUERY)
|
|
975
|
-
|
|
976
|
-
load_unique_values_fn = DBLoadUniqueValues(load_data_fn_original)
|
|
977
|
-
load_unique_values_fn.__name__ = f"{tool_name_prefix}_load_unique_values"
|
|
978
|
-
load_unique_values_tool = self.create_tool(load_unique_values_fn, ToolType.QUERY)
|
|
979
|
-
|
|
980
|
-
tools[load_data_tool_index] = load_data_tool
|
|
981
|
-
tools.extend([sample_data_tool, load_unique_values_tool])
|
|
982
|
-
return tools
|
|
982
|
+
if len(tool_name_prefix) > 0:
|
|
983
|
+
tool.metadata.name = tool_name_prefix + "_" + tool.metadata.name
|
|
984
|
+
vtool = VectaraTool(
|
|
985
|
+
tool_type=ToolType.QUERY,
|
|
986
|
+
fn=tool.fn, async_fn=tool.async_fn,
|
|
987
|
+
metadata=tool.metadata
|
|
988
|
+
)
|
|
989
|
+
vtools.append(vtool)
|
|
990
|
+
return vtools
|
vectara_agentic/utils.py
CHANGED
|
@@ -6,9 +6,9 @@ from typing import Tuple, Callable, Optional
|
|
|
6
6
|
from functools import lru_cache
|
|
7
7
|
from inspect import signature
|
|
8
8
|
import json
|
|
9
|
-
import
|
|
10
|
-
|
|
9
|
+
import asyncio
|
|
11
10
|
import tiktoken
|
|
11
|
+
import aiohttp
|
|
12
12
|
|
|
13
13
|
from llama_index.core.llms import LLM
|
|
14
14
|
from llama_index.llms.openai import OpenAI
|
|
@@ -43,7 +43,7 @@ def _get_llm_params_for_role(
|
|
|
43
43
|
config = config or AgentConfig() # fallback to default config
|
|
44
44
|
|
|
45
45
|
if role == LLMRole.TOOL:
|
|
46
|
-
model_provider = config.tool_llm_provider
|
|
46
|
+
model_provider = ModelProvider(config.tool_llm_provider)
|
|
47
47
|
# If the user hasn’t explicitly set a tool_llm_model_name,
|
|
48
48
|
# fallback to provider default from provider_to_default_model_name
|
|
49
49
|
model_name = (
|
|
@@ -51,7 +51,7 @@ def _get_llm_params_for_role(
|
|
|
51
51
|
or provider_to_default_model_name.get(model_provider)
|
|
52
52
|
)
|
|
53
53
|
else:
|
|
54
|
-
model_provider = config.main_llm_provider
|
|
54
|
+
model_provider = ModelProvider(config.main_llm_provider)
|
|
55
55
|
model_name = (
|
|
56
56
|
config.main_llm_model_name
|
|
57
57
|
or provider_to_default_model_name.get(model_provider)
|
|
@@ -101,13 +101,17 @@ def get_llm(
|
|
|
101
101
|
max_tokens=max_tokens
|
|
102
102
|
)
|
|
103
103
|
elif model_provider == ModelProvider.ANTHROPIC:
|
|
104
|
-
llm = Anthropic(
|
|
104
|
+
llm = Anthropic(
|
|
105
|
+
model=model_name, temperature=0,
|
|
106
|
+
max_tokens=max_tokens, cache_idx=2,
|
|
107
|
+
)
|
|
105
108
|
elif model_provider == ModelProvider.GEMINI:
|
|
106
109
|
from llama_index.llms.gemini import Gemini
|
|
107
110
|
llm = Gemini(
|
|
108
111
|
model=model_name, temperature=0,
|
|
109
112
|
is_function_calling_model=True,
|
|
110
|
-
|
|
113
|
+
allow_parallel_tool_calls=True,
|
|
114
|
+
max_tokens=max_tokens,
|
|
111
115
|
)
|
|
112
116
|
elif model_provider == ModelProvider.TOGETHER:
|
|
113
117
|
from llama_index.llms.together import TogetherLLM
|
|
@@ -159,7 +163,7 @@ def remove_self_from_signature(func):
|
|
|
159
163
|
func.__signature__ = new_sig
|
|
160
164
|
return func
|
|
161
165
|
|
|
162
|
-
def summarize_vectara_document(corpus_key: str, vectara_api_key, doc_id: str) -> str:
|
|
166
|
+
async def summarize_vectara_document(corpus_key: str, vectara_api_key: str, doc_id: str) -> str:
|
|
163
167
|
"""
|
|
164
168
|
Summarize a document in a Vectara corpus using the Vectara API.
|
|
165
169
|
"""
|
|
@@ -175,8 +179,32 @@ def summarize_vectara_document(corpus_key: str, vectara_api_key, doc_id: str) ->
|
|
|
175
179
|
'Accept': 'application/json',
|
|
176
180
|
'x-api-key': vectara_api_key
|
|
177
181
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
timeout = aiohttp.ClientTimeout(total=60)
|
|
183
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
184
|
+
async with session.post(url, headers=headers, data=payload) as response:
|
|
185
|
+
if response.status != 200:
|
|
186
|
+
error_json = await response.json()
|
|
187
|
+
return (
|
|
188
|
+
f"Vectara Summarization failed with error code {response.status}, "
|
|
189
|
+
f"error={error_json['messages'][0]}"
|
|
190
|
+
)
|
|
191
|
+
data = await response.json()
|
|
192
|
+
return data["summary"]
|
|
182
193
|
return json.loads(response.text)["summary"]
|
|
194
|
+
|
|
195
|
+
async def summarize_documents(
|
|
196
|
+
vectara_corpus_key: str,
|
|
197
|
+
vectara_api_key: str,
|
|
198
|
+
doc_ids: list[str]
|
|
199
|
+
) -> dict[str, str]:
|
|
200
|
+
"""
|
|
201
|
+
Summarize multiple documents in a Vectara corpus using the Vectara API.
|
|
202
|
+
"""
|
|
203
|
+
if not doc_ids:
|
|
204
|
+
return {}
|
|
205
|
+
tasks = [
|
|
206
|
+
summarize_vectara_document(vectara_corpus_key, vectara_api_key, doc_id)
|
|
207
|
+
for doc_id in doc_ids
|
|
208
|
+
]
|
|
209
|
+
summaries = await asyncio.gather(*tasks, return_exceptions=True)
|
|
210
|
+
return dict(zip(doc_ids, summaries))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vectara_agentic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: A Python package for creating AI Assistants and AI Agents with Vectara
|
|
5
5
|
Home-page: https://github.com/vectara/py-vectara-agentic
|
|
6
6
|
Author: Ofer Mendelevitch
|
|
@@ -16,7 +16,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: llama-index==0.12.
|
|
19
|
+
Requires-Dist: llama-index==0.12.26
|
|
20
20
|
Requires-Dist: llama-index-indices-managed-vectara==0.4.2
|
|
21
21
|
Requires-Dist: llama-index-agent-llm-compiler==0.3.0
|
|
22
22
|
Requires-Dist: llama-index-agent-lats==0.3.0
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
tests/endpoint.py,sha256=frnpdZQpnuQNNKNYgAn2rFTarNG8MCJaNA77Bw_W22A,1420
|
|
3
|
-
tests/test_agent.py,sha256=
|
|
3
|
+
tests/test_agent.py,sha256=t4omKBg9207hpT8b05v9TwuXJCM4knYSTdsXe740eho,4845
|
|
4
4
|
tests/test_agent_planning.py,sha256=_mj73TNP9yUjkUJ-X31r-cQYreJ4qatXOtMrRvVpF4Y,2411
|
|
5
5
|
tests/test_agent_type.py,sha256=JM0Q2GBGHSADoBacz_DW551zWSfbpf7qa8xXqtyWsc4,5671
|
|
6
6
|
tests/test_fallback.py,sha256=M5YD7NHZ0joVU1frYIr9_OiRAIje5mrXrYVcekzlyGs,2829
|
|
7
7
|
tests/test_private_llm.py,sha256=CY-_rCpxGUuxnZ3ypkodw5Jj-sJCNdh6rLbCvULwuJI,2247
|
|
8
|
+
tests/test_serialization.py,sha256=Ed23GN2zhSJNdPFrVK4aqLkOhJKviczR_o0t-r9TuRI,4762
|
|
8
9
|
tests/test_tools.py,sha256=IVKn0HoS2erTCr1mOEGzTkktiY0PCfKNvqnD_pizjOg,3977
|
|
9
10
|
tests/test_workflow.py,sha256=lVyrVHdRO5leYNbYtHTmKqMX0c8_xehCpUA7cXQKVsc,2175
|
|
10
11
|
vectara_agentic/__init__.py,sha256=2GLDS3U6KckK-dBRl9v_x1kSV507gEhjOfuMmmu0Qxg,850
|
|
11
|
-
vectara_agentic/_callback.py,sha256=
|
|
12
|
+
vectara_agentic/_callback.py,sha256=lU35-Pxp-fsMpOi4woY6oLECAhO1nSmLIy3b8fbgT54,13029
|
|
12
13
|
vectara_agentic/_observability.py,sha256=fTL3KW0jQU-_JSpFgjO6-XzgDut_oiq9kt4QR-FkSqU,3804
|
|
13
14
|
vectara_agentic/_prompts.py,sha256=LYyiOAiC8imz3U7MSJiuCYAP39afsp7ycXY7-9biyJI,9314
|
|
14
|
-
vectara_agentic/_version.py,sha256=
|
|
15
|
-
vectara_agentic/agent.py,sha256=
|
|
16
|
-
vectara_agentic/agent_config.py,sha256=
|
|
15
|
+
vectara_agentic/_version.py,sha256=H4T1Nr91mODgWHnEWr2XhUyVnkEh_ZuEEayYoJS0Iis,65
|
|
16
|
+
vectara_agentic/agent.py,sha256=ItNy6QRfgkk-zePK05k8yhsov_TVh5ScB-BbiQdUbTY,43956
|
|
17
|
+
vectara_agentic/agent_config.py,sha256=E-rtYMcpoGxnEAyy8231bizo2n0uGQ2qWxuSgTEfwdQ,4327
|
|
17
18
|
vectara_agentic/agent_endpoint.py,sha256=QIMejCLlpW2qzXxeDAxv3anF46XMDdVMdKGWhJh3azY,1996
|
|
18
|
-
vectara_agentic/db_tools.py,sha256=
|
|
19
|
-
vectara_agentic/sub_query_workflow.py,sha256=
|
|
20
|
-
vectara_agentic/tools.py,sha256=
|
|
19
|
+
vectara_agentic/db_tools.py,sha256=Go03bzma9m-qDH0CPP8hWhf1nu_4S6s7ke0jGqz58Pk,10296
|
|
20
|
+
vectara_agentic/sub_query_workflow.py,sha256=3WoVnryR2NXyYXbLDM1XVLd7DtbCG0jgrVqeDUN4YNQ,10943
|
|
21
|
+
vectara_agentic/tools.py,sha256=vOIevyGhApeZ46UelSTXmKXWE26Z2KtjLgsb4cHP49M,42579
|
|
21
22
|
vectara_agentic/tools_catalog.py,sha256=oiw3wAfbpFhh0_6rMvZsyPqWV6QIzHqhZCNzqRxuyV8,4818
|
|
22
23
|
vectara_agentic/types.py,sha256=HcS7vR8P2v2xQTlOc6ZFV2vvlr3OpzSNWhtcLMxqUZc,1792
|
|
23
|
-
vectara_agentic/utils.py,sha256=
|
|
24
|
-
vectara_agentic-0.2.
|
|
25
|
-
vectara_agentic-0.2.
|
|
26
|
-
vectara_agentic-0.2.
|
|
27
|
-
vectara_agentic-0.2.
|
|
28
|
-
vectara_agentic-0.2.
|
|
24
|
+
vectara_agentic/utils.py,sha256=4vA5MyNoG47_7eHuLFQByiG_FHWbrQ6ZJDsdqHUwiJA,7720
|
|
25
|
+
vectara_agentic-0.2.9.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
26
|
+
vectara_agentic-0.2.9.dist-info/METADATA,sha256=FfjEtidIWQlsUXY_cdVIxPc6h07M3JwXjQKEj3aCMLY,25046
|
|
27
|
+
vectara_agentic-0.2.9.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
28
|
+
vectara_agentic-0.2.9.dist-info/top_level.txt,sha256=Y7TQTFdOYGYodQRltUGRieZKIYuzeZj2kHqAUpfCUfg,22
|
|
29
|
+
vectara_agentic-0.2.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|