vectara-agentic 0.2.15__py3-none-any.whl → 0.2.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vectara-agentic might be problematic. Click here for more details.
- tests/test_agent.py +2 -2
- tests/test_agent_planning.py +1 -1
- tests/test_groq.py +3 -1
- tests/test_tools.py +119 -17
- tests/test_workflow.py +67 -0
- vectara_agentic/_observability.py +1 -1
- vectara_agentic/_prompts.py +4 -2
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +147 -69
- vectara_agentic/db_tools.py +2 -2
- vectara_agentic/llm_utils.py +10 -6
- vectara_agentic/sub_query_workflow.py +5 -2
- vectara_agentic/tool_utils.py +112 -84
- vectara_agentic/tools.py +3 -1
- vectara_agentic/tools_catalog.py +1 -0
- vectara_agentic/utils.py +1 -1
- {vectara_agentic-0.2.15.dist-info → vectara_agentic-0.2.17.dist-info}/METADATA +62 -19
- vectara_agentic-0.2.17.dist-info/RECORD +34 -0
- {vectara_agentic-0.2.15.dist-info → vectara_agentic-0.2.17.dist-info}/WHEEL +1 -1
- vectara_agentic-0.2.15.dist-info/RECORD +0 -34
- {vectara_agentic-0.2.15.dist-info → vectara_agentic-0.2.17.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.15.dist-info → vectara_agentic-0.2.17.dist-info}/top_level.txt +0 -0
tests/test_agent.py
CHANGED
|
@@ -124,7 +124,7 @@ class TestAgentPackage(unittest.TestCase):
|
|
|
124
124
|
self.assertEqual(res.response, "1050")
|
|
125
125
|
|
|
126
126
|
def test_custom_general_instruction(self):
|
|
127
|
-
general_instructions = "Always respond with
|
|
127
|
+
general_instructions = "Always respond with: I DIDN'T DO IT"
|
|
128
128
|
agent = Agent.from_corpus(
|
|
129
129
|
tool_name="RAG Tool",
|
|
130
130
|
vectara_corpus_key="corpus_key",
|
|
@@ -135,7 +135,7 @@ class TestAgentPackage(unittest.TestCase):
|
|
|
135
135
|
)
|
|
136
136
|
|
|
137
137
|
res = agent.chat("What is the meaning of the universe?")
|
|
138
|
-
self.assertEqual(res.response, "I
|
|
138
|
+
self.assertEqual(res.response, "I DIDN'T DO IT")
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
if __name__ == "__main__":
|
tests/test_agent_planning.py
CHANGED
|
@@ -4,7 +4,7 @@ from vectara_agentic.agent_config import AgentConfig
|
|
|
4
4
|
from vectara_agentic.agent import Agent
|
|
5
5
|
from vectara_agentic.tools import VectaraToolFactory
|
|
6
6
|
|
|
7
|
-
# SETUP
|
|
7
|
+
# SETUP special test account credentials for vectara
|
|
8
8
|
# It's okay to expose these credentials in the test code
|
|
9
9
|
vectara_corpus_key = "vectara-docs_1"
|
|
10
10
|
vectara_api_key = 'zqt_UXrBcnI2UXINZkrv4g1tQPhzj02vfdtqYJIDiA'
|
tests/test_groq.py
CHANGED
|
@@ -113,7 +113,9 @@ class TestGROQ(unittest.TestCase):
|
|
|
113
113
|
agent_config=fc_config_groq,
|
|
114
114
|
)
|
|
115
115
|
res = agent.chat("What is the stock price?")
|
|
116
|
-
self.
|
|
116
|
+
self.assertTrue(
|
|
117
|
+
any(sub in str(res) for sub in ["I don't know", "I do not have"])
|
|
118
|
+
)
|
|
117
119
|
|
|
118
120
|
|
|
119
121
|
if __name__ == "__main__":
|
tests/test_tools.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
from pydantic import Field, BaseModel
|
|
3
|
-
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
|
+
import requests
|
|
4
5
|
from vectara_agentic.tools import (
|
|
5
6
|
VectaraTool,
|
|
6
7
|
VectaraToolFactory,
|
|
@@ -17,6 +18,7 @@ from llama_index.core.tools import FunctionTool
|
|
|
17
18
|
vectara_corpus_key = "vectara-docs_1"
|
|
18
19
|
vectara_api_key = "zqt_UXrBcnI2UXINZkrv4g1tQPhzj02vfdtqYJIDiA"
|
|
19
20
|
|
|
21
|
+
from typing import Optional
|
|
20
22
|
|
|
21
23
|
class TestToolsPackage(unittest.TestCase):
|
|
22
24
|
|
|
@@ -89,7 +91,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
89
91
|
description="The ticker symbol for the company",
|
|
90
92
|
examples=["AAPL", "GOOG"],
|
|
91
93
|
)
|
|
92
|
-
year: int | str = Field(
|
|
94
|
+
year: Optional[int | str] = Field(
|
|
93
95
|
default=None,
|
|
94
96
|
description="The year this query relates to. An integer between 2015 and 2024 or a string specifying a condition on the year",
|
|
95
97
|
examples=[
|
|
@@ -109,6 +111,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
109
111
|
tool_args_schema=QueryToolArgs,
|
|
110
112
|
)
|
|
111
113
|
|
|
114
|
+
# test an invalid argument name
|
|
112
115
|
res = query_tool(
|
|
113
116
|
query="What is the stock price?",
|
|
114
117
|
the_year=2023,
|
|
@@ -126,6 +129,86 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
126
129
|
)
|
|
127
130
|
self.assertIn("got an unexpected keyword argument 'the_year'", str(res))
|
|
128
131
|
|
|
132
|
+
@patch.object(requests.Session, "post")
|
|
133
|
+
def test_vectara_tool_ranges(self, mock_post):
|
|
134
|
+
# Configure the mock to return a dummy response.
|
|
135
|
+
response_text = "ALL GOOD"
|
|
136
|
+
mock_response = MagicMock()
|
|
137
|
+
mock_response.status_code = 200
|
|
138
|
+
mock_response.json.return_value = {
|
|
139
|
+
'summary': response_text,
|
|
140
|
+
'search_results': [
|
|
141
|
+
{'text': 'ALL GOOD', 'document_id': '12345', 'score': 0.9},
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
mock_post.return_value = mock_response
|
|
145
|
+
|
|
146
|
+
vec_factory = VectaraToolFactory(vectara_corpus_key, vectara_api_key)
|
|
147
|
+
|
|
148
|
+
class QueryToolArgs(BaseModel):
|
|
149
|
+
ticker: str = Field(
|
|
150
|
+
description="The ticker symbol for the company",
|
|
151
|
+
examples=["AAPL", "GOOG"],
|
|
152
|
+
)
|
|
153
|
+
year: int | str = Field(
|
|
154
|
+
default=None,
|
|
155
|
+
description="The year this query relates to. An integer between 2015 and 2024 or a string specifying a condition on the year",
|
|
156
|
+
examples=[
|
|
157
|
+
2020,
|
|
158
|
+
">2021",
|
|
159
|
+
"<2023",
|
|
160
|
+
">=2021",
|
|
161
|
+
"<=2023",
|
|
162
|
+
"[2021, 2023]",
|
|
163
|
+
"[2021, 2023)",
|
|
164
|
+
],
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
query_tool = vec_factory.create_rag_tool(
|
|
168
|
+
tool_name="rag_tool",
|
|
169
|
+
tool_description="Returns a response (str) to the user query based on the data in this corpus.",
|
|
170
|
+
tool_args_schema=QueryToolArgs,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# test an invalid argument name
|
|
174
|
+
res = query_tool(
|
|
175
|
+
query="What is the stock price?",
|
|
176
|
+
year=">2023"
|
|
177
|
+
)
|
|
178
|
+
self.assertIn(response_text, str(res))
|
|
179
|
+
|
|
180
|
+
# Test a valid range
|
|
181
|
+
res = query_tool(
|
|
182
|
+
query="What is the stock price?",
|
|
183
|
+
year="[2021, 2023]",
|
|
184
|
+
)
|
|
185
|
+
self.assertIn(response_text, str(res))
|
|
186
|
+
|
|
187
|
+
# Test a valid half closed range
|
|
188
|
+
res = query_tool(
|
|
189
|
+
query="What is the stock price?",
|
|
190
|
+
year="[2020, 2023)",
|
|
191
|
+
)
|
|
192
|
+
self.assertIn(response_text, str(res))
|
|
193
|
+
|
|
194
|
+
# Test an operator
|
|
195
|
+
res = query_tool(
|
|
196
|
+
query="What is the stock price?",
|
|
197
|
+
year=">2022",
|
|
198
|
+
)
|
|
199
|
+
self.assertIn(response_text, str(res))
|
|
200
|
+
|
|
201
|
+
search_tool = vec_factory.create_search_tool(
|
|
202
|
+
tool_name="search_tool",
|
|
203
|
+
tool_description="Returns a list of documents (str) that match the user query.",
|
|
204
|
+
tool_args_schema=QueryToolArgs,
|
|
205
|
+
)
|
|
206
|
+
res = search_tool(
|
|
207
|
+
query="What is the stock price?",
|
|
208
|
+
the_year=2023,
|
|
209
|
+
)
|
|
210
|
+
self.assertIn("got an unexpected keyword argument 'the_year'", str(res))
|
|
211
|
+
|
|
129
212
|
def test_tool_factory(self):
|
|
130
213
|
def mult(x: float, y: float) -> float:
|
|
131
214
|
return x * y
|
|
@@ -152,8 +235,6 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
152
235
|
self.assertEqual(tool.metadata.tool_type, ToolType.QUERY)
|
|
153
236
|
|
|
154
237
|
def test_tool_with_many_arguments(self):
|
|
155
|
-
vectara_corpus_key = "corpus_key"
|
|
156
|
-
vectara_api_key = "api_key"
|
|
157
238
|
vec_factory = VectaraToolFactory(vectara_corpus_key, vectara_api_key)
|
|
158
239
|
|
|
159
240
|
class QueryToolArgs(BaseModel):
|
|
@@ -169,24 +250,18 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
169
250
|
arg10: str = Field(description="the tenth argument", examples=["val10"])
|
|
170
251
|
arg11: str = Field(description="the eleventh argument", examples=["val11"])
|
|
171
252
|
arg12: str = Field(description="the twelfth argument", examples=["val12"])
|
|
172
|
-
arg13: str = Field(
|
|
173
|
-
description="the thirteenth argument", examples=["val13"]
|
|
174
|
-
)
|
|
175
|
-
arg14: str = Field(
|
|
176
|
-
description="the fourteenth argument", examples=["val14"]
|
|
177
|
-
)
|
|
178
|
-
arg15: str = Field(description="the fifteenth argument", examples=["val15"])
|
|
253
|
+
arg13: str = Field(description="the thirteenth argument", examples=["val13"])
|
|
179
254
|
|
|
180
255
|
query_tool_1 = vec_factory.create_rag_tool(
|
|
181
256
|
tool_name="rag_tool",
|
|
182
257
|
tool_description="""
|
|
183
|
-
A dummy tool that takes
|
|
258
|
+
A dummy tool that takes 13 arguments and returns a response (str) to the user query based on the data in this corpus.
|
|
184
259
|
We are using this tool to test the tool factory works and does not crash with OpenAI.
|
|
185
260
|
""",
|
|
186
261
|
tool_args_schema=QueryToolArgs,
|
|
187
262
|
)
|
|
188
263
|
|
|
189
|
-
# Test with
|
|
264
|
+
# Test with 13 arguments which go over the 1024 limit.
|
|
190
265
|
config = AgentConfig(
|
|
191
266
|
agent_type=AgentType.OPENAI
|
|
192
267
|
)
|
|
@@ -208,7 +283,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
208
283
|
agent = Agent(
|
|
209
284
|
tools=[query_tool_1],
|
|
210
285
|
topic="Sample topic",
|
|
211
|
-
custom_instructions="Call the tool with
|
|
286
|
+
custom_instructions="Call the tool with 13 arguments for GROQ",
|
|
212
287
|
agent_config=config,
|
|
213
288
|
)
|
|
214
289
|
res = agent.chat("What is the stock price?")
|
|
@@ -223,14 +298,14 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
223
298
|
agent = Agent(
|
|
224
299
|
tools=[query_tool_1],
|
|
225
300
|
topic="Sample topic",
|
|
226
|
-
custom_instructions="Call the tool with
|
|
301
|
+
custom_instructions="Call the tool with 13 arguments for ANTHROPIC",
|
|
227
302
|
agent_config=config,
|
|
228
303
|
)
|
|
229
304
|
res = agent.chat("What is the stock price?")
|
|
230
305
|
# ANTHROPIC does not have that 1024 limit
|
|
231
306
|
self.assertIn("stock price", str(res))
|
|
232
307
|
|
|
233
|
-
# But using Compact_docstring=True, we can pass
|
|
308
|
+
# But using Compact_docstring=True, we can pass 13 arguments successfully.
|
|
234
309
|
vec_factory = VectaraToolFactory(
|
|
235
310
|
vectara_corpus_key, vectara_api_key, compact_docstring=True
|
|
236
311
|
)
|
|
@@ -251,7 +326,9 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
251
326
|
agent_config=config,
|
|
252
327
|
)
|
|
253
328
|
res = agent.chat("What is the stock price?")
|
|
254
|
-
self.
|
|
329
|
+
self.assertTrue(
|
|
330
|
+
any(sub in str(res) for sub in ["I don't know", "stock price"])
|
|
331
|
+
)
|
|
255
332
|
|
|
256
333
|
def test_public_repo(self):
|
|
257
334
|
vectara_corpus_key = "vectara-docs_1"
|
|
@@ -297,6 +374,31 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
297
374
|
"50",
|
|
298
375
|
)
|
|
299
376
|
|
|
377
|
+
def test_vectara_tool_docstring(self):
|
|
378
|
+
class DummyArgs(BaseModel):
|
|
379
|
+
foo: int = Field(..., description="how many foos", examples=[1, 2, 3])
|
|
380
|
+
bar: str = Field(
|
|
381
|
+
"baz",
|
|
382
|
+
description="what bar to use",
|
|
383
|
+
examples=["x", "y"],
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
vec_factory = VectaraToolFactory(vectara_corpus_key, vectara_api_key)
|
|
387
|
+
dummy_tool = vec_factory.create_rag_tool(
|
|
388
|
+
tool_name="dummy_tool",
|
|
389
|
+
tool_description="A dummy tool.",
|
|
390
|
+
tool_args_schema=DummyArgs,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
doc = dummy_tool.metadata.description
|
|
394
|
+
self.assertTrue(doc.startswith("dummy_tool(query: str, foo: int, bar: str) -> dict[str, Any]"))
|
|
395
|
+
self.assertIn("Args:", doc)
|
|
396
|
+
self.assertIn("query (str): The search query to perform, in the form of a question", doc)
|
|
397
|
+
self.assertIn("foo (int): how many foos (e.g., 1, 2, 3)", doc)
|
|
398
|
+
self.assertIn("bar (str, default='baz'): what bar to use (e.g., 'x', 'y')", doc)
|
|
399
|
+
self.assertIn("Returns:", doc)
|
|
400
|
+
self.assertIn("dict[str, Any]: A dictionary containing the result data.", doc)
|
|
401
|
+
|
|
300
402
|
|
|
301
403
|
if __name__ == "__main__":
|
|
302
404
|
unittest.main()
|
tests/test_workflow.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from llama_index.core.workflow import WorkflowTimeoutError
|
|
6
|
+
|
|
3
7
|
from vectara_agentic.agent import Agent
|
|
4
8
|
from vectara_agentic.agent_config import AgentConfig
|
|
5
9
|
from vectara_agentic.tools import ToolsFactory
|
|
@@ -62,6 +66,69 @@ class TestWorkflowPackage(unittest.IsolatedAsyncioTestCase):
|
|
|
62
66
|
res = await agent.run(inputs=inputs, verbose=True)
|
|
63
67
|
self.assertIn("22", res.response)
|
|
64
68
|
|
|
69
|
+
class TestWorkflowFailure(unittest.IsolatedAsyncioTestCase):
|
|
70
|
+
|
|
71
|
+
async def test_workflow_failure(self):
|
|
72
|
+
tools = [ToolsFactory().create_tool(mult)] + [ToolsFactory().create_tool(add)]
|
|
73
|
+
topic = "AI topic"
|
|
74
|
+
instructions = "You are a helpful AI assistant."
|
|
75
|
+
agent = Agent(
|
|
76
|
+
tools=tools,
|
|
77
|
+
topic=topic,
|
|
78
|
+
custom_instructions=instructions,
|
|
79
|
+
agent_config = AgentConfig(),
|
|
80
|
+
workflow_cls = SubQuestionQueryWorkflow,
|
|
81
|
+
workflow_timeout = 1
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
inputs = SubQuestionQueryWorkflow.InputsModel(
|
|
85
|
+
query="Compute 5 times 3, then add 7 to the result."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
res = None
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
res = await agent.run(inputs=inputs)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
self.assertIsInstance(e, WorkflowTimeoutError)
|
|
94
|
+
|
|
95
|
+
self.assertIsNone(res)
|
|
96
|
+
|
|
97
|
+
async def test_workflow_with_fail_class(self):
|
|
98
|
+
tools = [ToolsFactory().create_tool(mult)] + [ToolsFactory().create_tool(add)]
|
|
99
|
+
topic = "AI topic"
|
|
100
|
+
instructions = "You are a helpful AI assistant."
|
|
101
|
+
|
|
102
|
+
class SubQuestionQueryWorkflowWithFailClass(SubQuestionQueryWorkflow):
|
|
103
|
+
class OutputModelOnFail(BaseModel):
|
|
104
|
+
"""
|
|
105
|
+
In case of failure, returns the user's original query
|
|
106
|
+
"""
|
|
107
|
+
original_query: str
|
|
108
|
+
|
|
109
|
+
agent = Agent(
|
|
110
|
+
tools=tools,
|
|
111
|
+
topic=topic,
|
|
112
|
+
custom_instructions=instructions,
|
|
113
|
+
agent_config = AgentConfig(),
|
|
114
|
+
workflow_cls = SubQuestionQueryWorkflowWithFailClass,
|
|
115
|
+
workflow_timeout = 1
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
inputs = SubQuestionQueryWorkflow.InputsModel(
|
|
119
|
+
query="Compute 5 times 3, then add 7 to the result."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
res = None
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
res = await agent.run(inputs=inputs)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
assert isinstance(e, WorkflowTimeoutError)
|
|
128
|
+
|
|
129
|
+
self.assertIsInstance(res, SubQuestionQueryWorkflowWithFailClass.OutputModelOnFail)
|
|
130
|
+
self.assertEqual(res.original_query, "Compute 5 times 3, then add 7 to the result.")
|
|
131
|
+
|
|
65
132
|
|
|
66
133
|
if __name__ == "__main__":
|
|
67
134
|
unittest.main()
|
|
@@ -48,7 +48,7 @@ def setup_observer(config: AgentConfig, verbose: bool) -> bool:
|
|
|
48
48
|
reg_kwargs = {
|
|
49
49
|
"endpoint": phoenix_endpoint or 'http://localhost:6006/v1/traces',
|
|
50
50
|
"project_name": "vectara-agentic",
|
|
51
|
-
"batch":
|
|
51
|
+
"batch": False,
|
|
52
52
|
"set_global_tracer_provider": False,
|
|
53
53
|
}
|
|
54
54
|
tracer_provider = register(**reg_kwargs)
|
vectara_agentic/_prompts.py
CHANGED
|
@@ -4,11 +4,13 @@ This file contains the prompt templates for the different types of agents.
|
|
|
4
4
|
|
|
5
5
|
# General (shared) instructions
|
|
6
6
|
GENERAL_INSTRUCTIONS = """
|
|
7
|
-
- Use tools as your main source of information, do not respond without using a tool
|
|
7
|
+
- Use tools as your main source of information, do not respond without using a tool at least once.
|
|
8
|
+
- Do not respond based on pre-trained knowledge, unless repeated calls to the tools fail or do not provide the information needed.
|
|
8
9
|
- Use the 'get_bad_topics' (if it exists) tool to determine the topics you are not allowed to discuss or respond to.
|
|
9
10
|
- Before responding to a user query that requires knowledge of the current date, call the 'get_current_date' tool to get the current date.
|
|
10
11
|
Never rely on previous knowledge of the current date.
|
|
11
12
|
Example queries that require the current date: "What is the revenue of Apple last october?" or "What was the stock price 5 days ago?".
|
|
13
|
+
Never call 'get_current_date' more than once for the same user query.
|
|
12
14
|
- When using a tool with arguments, simplify the query as much as possible if you use the tool with arguments.
|
|
13
15
|
For example, if the original query is "revenue for apple in 2021", you can use the tool with a query "revenue" with arguments year=2021 and company=apple.
|
|
14
16
|
- If a tool responds with "I do not have enough information", try one or more of the following strategies:
|
|
@@ -24,7 +26,7 @@ GENERAL_INSTRUCTIONS = """
|
|
|
24
26
|
- If a tool provides citations or references in markdown as part of its response, include the references in your response.
|
|
25
27
|
- Ensure that every URL in your response includes descriptive anchor text that clearly explains what the user can expect from the linked content.
|
|
26
28
|
Avoid using generic terms like “source” or “reference”, or the full URL, as the anchor text.
|
|
27
|
-
- If a tool returns in the metadata a valid URL pointing to a PDF file, along with page number - then combine the URL and page number in
|
|
29
|
+
- If a tool returns in the metadata a valid URL pointing to a PDF file, along with page number - then combine the URL and page number in your response.
|
|
28
30
|
For example, if the URL returned from the tool is "https://example.com/doc.pdf" and "page=5", then the combined URL would be "https://example.com/doc.pdf#page=5".
|
|
29
31
|
If a tool returns in the metadata invalid URLs or an URL empty (e.g. "[[1]()]"), ignore it and do not include that citation or reference in your response.
|
|
30
32
|
- All URLs provided in your response must be obtained from tool output, and cannot be "https://example.com" or empty strings, and should open in a new tab.
|
vectara_agentic/_version.py
CHANGED