vectara-agentic 0.2.4__py3-none-any.whl → 0.2.6__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 +18 -0
- tests/test_agent_planning.py +46 -0
- tests/test_agent_type.py +83 -0
- tests/test_fallback.py +83 -0
- tests/test_private_llm.py +10 -9
- tests/test_workflow.py +67 -0
- vectara_agentic/__init__.py +12 -2
- vectara_agentic/_callback.py +12 -4
- vectara_agentic/_observability.py +1 -1
- vectara_agentic/_prompts.py +48 -7
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +331 -134
- vectara_agentic/db_tools.py +2 -2
- vectara_agentic/sub_query_workflow.py +292 -0
- vectara_agentic/tools.py +34 -21
- vectara_agentic/types.py +5 -0
- vectara_agentic/utils.py +5 -3
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info}/METADATA +101 -23
- vectara_agentic-0.2.6.dist-info/RECORD +28 -0
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info}/WHEEL +1 -1
- vectara_agentic-0.2.4.dist-info/RECORD +0 -23
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info/licenses}/LICENSE +0 -0
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info}/top_level.txt +0 -0
vectara_agentic/db_tools.py
CHANGED
|
@@ -13,7 +13,7 @@ class DBTool(ABC):
|
|
|
13
13
|
"""
|
|
14
14
|
A base class for vectara-agentic database tools extensions
|
|
15
15
|
"""
|
|
16
|
-
def __init__(self, load_data_fn: Callable, max_rows: int =
|
|
16
|
+
def __init__(self, load_data_fn: Callable, max_rows: int = 1000):
|
|
17
17
|
self.load_data_fn = load_data_fn
|
|
18
18
|
self.max_rows = max_rows
|
|
19
19
|
|
|
@@ -39,7 +39,7 @@ class DBLoadData(DBTool):
|
|
|
39
39
|
if num_rows > self.max_rows:
|
|
40
40
|
return [
|
|
41
41
|
f"The query is expected to return more than {self.max_rows} rows. "
|
|
42
|
-
"Please
|
|
42
|
+
"Please refactor your query to make it return less rows. "
|
|
43
43
|
]
|
|
44
44
|
try:
|
|
45
45
|
res = self.load_data_fn(query)
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the SubQuestionQueryEngine workflow, which is a workflow
|
|
3
|
+
that takes a user question and a list of tools, and outputs a list of sub-questions.
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from llama_index.core.workflow import (
|
|
9
|
+
step,
|
|
10
|
+
Context,
|
|
11
|
+
Workflow,
|
|
12
|
+
Event,
|
|
13
|
+
StartEvent,
|
|
14
|
+
StopEvent,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
class SubQuestionQueryWorkflow(Workflow):
|
|
18
|
+
"""
|
|
19
|
+
Workflow for sub-question query engine.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Workflow inputs/outputs
|
|
23
|
+
class InputsModel(BaseModel):
|
|
24
|
+
"""
|
|
25
|
+
Inputs for the workflow.
|
|
26
|
+
"""
|
|
27
|
+
query: str
|
|
28
|
+
|
|
29
|
+
class OutputsModel(BaseModel):
|
|
30
|
+
"""
|
|
31
|
+
Outputs for the workflow.
|
|
32
|
+
"""
|
|
33
|
+
response: str
|
|
34
|
+
|
|
35
|
+
# Workflow Event types
|
|
36
|
+
class QueryEvent(Event):
|
|
37
|
+
"""Event for a query."""
|
|
38
|
+
question: str
|
|
39
|
+
|
|
40
|
+
class AnswerEvent(Event):
|
|
41
|
+
"""Event for an answer."""
|
|
42
|
+
question: str
|
|
43
|
+
answer: str
|
|
44
|
+
|
|
45
|
+
@step
|
|
46
|
+
async def query(self, ctx: Context, ev: StartEvent) -> QueryEvent:
|
|
47
|
+
"""
|
|
48
|
+
Given a user question, and a list of tools, output a list of relevant
|
|
49
|
+
sub-questions, such that the answers to all the sub-questions put together
|
|
50
|
+
will answer the question.
|
|
51
|
+
"""
|
|
52
|
+
if not hasattr(ev, "inputs"):
|
|
53
|
+
raise ValueError("No inputs provided to workflow Start Event.")
|
|
54
|
+
if hasattr(ev, "inputs") and not isinstance(ev.inputs, self.InputsModel):
|
|
55
|
+
raise ValueError(f"Expected inputs to be of type {self.InputsModel}")
|
|
56
|
+
if hasattr(ev, "inputs"):
|
|
57
|
+
query = ev.inputs.query
|
|
58
|
+
await ctx.set("original_query", query)
|
|
59
|
+
print(f"Query is {await ctx.get('original_query')}")
|
|
60
|
+
|
|
61
|
+
if hasattr(ev, "agent"):
|
|
62
|
+
await ctx.set("agent", ev.agent)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("Agent not provided to workflow Start Event.")
|
|
65
|
+
chat_history = [str(msg) for msg in ev.agent.memory.get()]
|
|
66
|
+
|
|
67
|
+
if hasattr(ev, "llm"):
|
|
68
|
+
await ctx.set("llm", ev.llm)
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError("LLM not provided to workflow Start Event.")
|
|
71
|
+
|
|
72
|
+
if hasattr(ev, "tools"):
|
|
73
|
+
await ctx.set("tools", ev.tools)
|
|
74
|
+
else:
|
|
75
|
+
raise ValueError("Tools not provided to workflow Start Event.")
|
|
76
|
+
|
|
77
|
+
if hasattr(ev, "verbose"):
|
|
78
|
+
await ctx.set("verbose", ev.verbose)
|
|
79
|
+
else:
|
|
80
|
+
await ctx.set("verbose", False)
|
|
81
|
+
|
|
82
|
+
llm = await ctx.get("llm")
|
|
83
|
+
response = llm.complete(
|
|
84
|
+
f"""
|
|
85
|
+
Given a user question, and a list of tools, output a list of
|
|
86
|
+
relevant sub-questions, such that the answers to all the
|
|
87
|
+
sub-questions put together will answer the question.
|
|
88
|
+
Order the sub-questions in the right order if there are dependencies.
|
|
89
|
+
Make sure sub-questions do not result in duplicate tool calling.
|
|
90
|
+
Respond in pure JSON without any markdown, like this:
|
|
91
|
+
{{
|
|
92
|
+
"sub_questions": [
|
|
93
|
+
"What is the population of San Francisco?",
|
|
94
|
+
"What is the budget of San Francisco?",
|
|
95
|
+
"What is the GDP of San Francisco?"
|
|
96
|
+
]
|
|
97
|
+
}}
|
|
98
|
+
As an example, for the question
|
|
99
|
+
"what is the name of the mayor of the largest city within 50 miles of San Francisco?",
|
|
100
|
+
the sub-questions could be:
|
|
101
|
+
- What is the largest city within 50 miles of San Francisco? (answer is San Jose)
|
|
102
|
+
- What is the name of the mayor of San Jose?
|
|
103
|
+
Here is the user question: {await ctx.get('original_query')}.
|
|
104
|
+
Here are previous chat messages: {chat_history}.
|
|
105
|
+
And here is the list of tools: {await ctx.get('tools')}
|
|
106
|
+
""",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if await ctx.get("verbose"):
|
|
110
|
+
print(f"Sub-questions are {response}")
|
|
111
|
+
|
|
112
|
+
response_obj = json.loads(str(response))
|
|
113
|
+
sub_questions = response_obj["sub_questions"]
|
|
114
|
+
|
|
115
|
+
await ctx.set("sub_question_count", len(sub_questions))
|
|
116
|
+
|
|
117
|
+
for question in sub_questions:
|
|
118
|
+
ctx.send_event(self.QueryEvent(question=question))
|
|
119
|
+
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
@step(num_workers=3)
|
|
123
|
+
async def sub_question(self, ctx: Context, ev: QueryEvent) -> AnswerEvent:
|
|
124
|
+
"""
|
|
125
|
+
Given a sub-question, return the answer to the sub-question, using the agent.
|
|
126
|
+
"""
|
|
127
|
+
if await ctx.get("verbose"):
|
|
128
|
+
print(f"Sub-question is {ev.question}")
|
|
129
|
+
agent = await ctx.get("agent")
|
|
130
|
+
response = await agent.achat(ev.question)
|
|
131
|
+
return self.AnswerEvent(question=ev.question, answer=str(response))
|
|
132
|
+
|
|
133
|
+
@step
|
|
134
|
+
async def combine_answers(
|
|
135
|
+
self, ctx: Context, ev: AnswerEvent
|
|
136
|
+
) -> StopEvent | None:
|
|
137
|
+
"""
|
|
138
|
+
Given a list of answers to sub-questions, combine them into a single answer.
|
|
139
|
+
"""
|
|
140
|
+
ready = ctx.collect_events(
|
|
141
|
+
ev, [self.AnswerEvent] * await ctx.get("sub_question_count")
|
|
142
|
+
)
|
|
143
|
+
if ready is None:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
answers = "\n\n".join(
|
|
147
|
+
[
|
|
148
|
+
f"Question: {event.question}: \n Answer: {event.answer}"
|
|
149
|
+
for event in ready
|
|
150
|
+
]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
prompt = f"""
|
|
154
|
+
You are given an overall question that has been split into sub-questions,
|
|
155
|
+
each of which has been answered. Combine the answers to all the sub-questions
|
|
156
|
+
into a single answer to the original question.
|
|
157
|
+
|
|
158
|
+
Original question: {await ctx.get('original_query')}
|
|
159
|
+
|
|
160
|
+
Sub-questions and answers:
|
|
161
|
+
{answers}
|
|
162
|
+
"""
|
|
163
|
+
if await ctx.get("verbose"):
|
|
164
|
+
print(f"Final prompt is {prompt}")
|
|
165
|
+
|
|
166
|
+
llm = await ctx.get("llm")
|
|
167
|
+
response = llm.complete(prompt)
|
|
168
|
+
|
|
169
|
+
if await ctx.get("verbose"):
|
|
170
|
+
print("Final response is", response)
|
|
171
|
+
|
|
172
|
+
output = self.OutputsModel(response=str(response))
|
|
173
|
+
return StopEvent(result=output)
|
|
174
|
+
|
|
175
|
+
class SequentialSubQuestionsWorkflow(Workflow):
|
|
176
|
+
"""
|
|
177
|
+
Workflow for breaking a query into sequential sub-questions
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
# Workflow inputs/outputs
|
|
181
|
+
class InputsModel(BaseModel):
|
|
182
|
+
"""
|
|
183
|
+
Inputs for the workflow.
|
|
184
|
+
"""
|
|
185
|
+
query: str
|
|
186
|
+
|
|
187
|
+
class OutputsModel(BaseModel):
|
|
188
|
+
"""
|
|
189
|
+
Outputs for the workflow.
|
|
190
|
+
"""
|
|
191
|
+
response: str
|
|
192
|
+
|
|
193
|
+
# Workflow Event types
|
|
194
|
+
class QueryEvent(Event):
|
|
195
|
+
"""Event for a query."""
|
|
196
|
+
question: str
|
|
197
|
+
prev_answer: str
|
|
198
|
+
num: int
|
|
199
|
+
|
|
200
|
+
@step
|
|
201
|
+
async def query(self, ctx: Context, ev: StartEvent) -> QueryEvent:
|
|
202
|
+
"""
|
|
203
|
+
Given a user question, and a list of tools, output a list of relevant
|
|
204
|
+
sub-questions, such that each question depends on the response of the
|
|
205
|
+
previous question, to answer the original user question.
|
|
206
|
+
"""
|
|
207
|
+
if not hasattr(ev, "inputs"):
|
|
208
|
+
raise ValueError("No inputs provided to workflow Start Event.")
|
|
209
|
+
if hasattr(ev, "inputs") and not isinstance(ev.inputs, self.InputsModel):
|
|
210
|
+
raise ValueError(f"Expected inputs to be of type {self.InputsModel}")
|
|
211
|
+
if hasattr(ev, "inputs"):
|
|
212
|
+
query = ev.inputs.query
|
|
213
|
+
await ctx.set("original_query", query)
|
|
214
|
+
|
|
215
|
+
if hasattr(ev, "agent"):
|
|
216
|
+
await ctx.set("agent", ev.agent)
|
|
217
|
+
else:
|
|
218
|
+
raise ValueError("Agent not provided to workflow Start Event.")
|
|
219
|
+
chat_history = [str(msg) for msg in ev.agent.memory.get()]
|
|
220
|
+
|
|
221
|
+
if hasattr(ev, "llm"):
|
|
222
|
+
await ctx.set("llm", ev.llm)
|
|
223
|
+
else:
|
|
224
|
+
raise ValueError("LLM not provided to workflow Start Event.")
|
|
225
|
+
|
|
226
|
+
if hasattr(ev, "tools"):
|
|
227
|
+
await ctx.set("tools", ev.tools)
|
|
228
|
+
else:
|
|
229
|
+
raise ValueError("Tools not provided to workflow Start Event.")
|
|
230
|
+
|
|
231
|
+
if hasattr(ev, "verbose"):
|
|
232
|
+
await ctx.set("verbose", ev.verbose)
|
|
233
|
+
else:
|
|
234
|
+
await ctx.set("verbose", False)
|
|
235
|
+
if ev.verbose:
|
|
236
|
+
print(f"Query is {await ctx.get('original_query')}")
|
|
237
|
+
|
|
238
|
+
llm = await ctx.get("llm")
|
|
239
|
+
response = llm.complete(
|
|
240
|
+
f"""
|
|
241
|
+
Given a user question, and a list of tools, output a list of
|
|
242
|
+
relevant sequential sub-questions, such that the answers to all the
|
|
243
|
+
sub-questions in sequence will answer the question, and the output
|
|
244
|
+
of each question can be used as input to the subsequent question.
|
|
245
|
+
Respond in pure JSON without any markdown, like this:
|
|
246
|
+
{{
|
|
247
|
+
"sub_questions": [
|
|
248
|
+
"What is the population of San Francisco?",
|
|
249
|
+
"Is that population larger than the population of San Jose?",
|
|
250
|
+
]
|
|
251
|
+
}}
|
|
252
|
+
As an example, for the question
|
|
253
|
+
"what is the name of the mayor of the largest city within 50 miles of San Francisco?",
|
|
254
|
+
the sub-questions could be:
|
|
255
|
+
- What is the largest city within 50 miles of San Francisco? (answer is San Jose)
|
|
256
|
+
- What is the name of the mayor of San Jose?
|
|
257
|
+
Here is the user question: {await ctx.get('original_query')}.
|
|
258
|
+
Here are previous chat messages: {chat_history}.
|
|
259
|
+
And here is the list of tools: {await ctx.get('tools')}
|
|
260
|
+
""",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
response_obj = json.loads(str(response))
|
|
264
|
+
sub_questions = response_obj["sub_questions"]
|
|
265
|
+
|
|
266
|
+
await ctx.set("sub_questions", sub_questions)
|
|
267
|
+
if await ctx.get("verbose"):
|
|
268
|
+
print(f"Sub-questions are {sub_questions}")
|
|
269
|
+
|
|
270
|
+
return self.QueryEvent(question=sub_questions[0], prev_answer="", num=0)
|
|
271
|
+
|
|
272
|
+
@step
|
|
273
|
+
async def sub_question(self, ctx: Context, ev: QueryEvent) -> StopEvent | QueryEvent:
|
|
274
|
+
"""
|
|
275
|
+
Given a sub-question, return the answer to the sub-question, using the agent.
|
|
276
|
+
"""
|
|
277
|
+
if await ctx.get("verbose"):
|
|
278
|
+
print(f"Sub-question is {ev.question}")
|
|
279
|
+
agent = await ctx.get("agent")
|
|
280
|
+
response = await agent.achat(ev.question)
|
|
281
|
+
if await ctx.get("verbose"):
|
|
282
|
+
print(f"Answer is {response}")
|
|
283
|
+
|
|
284
|
+
sub_questions = await ctx.get("sub_questions")
|
|
285
|
+
if ev.num + 1 < len(sub_questions):
|
|
286
|
+
return self.QueryEvent(
|
|
287
|
+
question=sub_questions[ev.num + 1],
|
|
288
|
+
prev_answer = response.response,
|
|
289
|
+
num=ev.num + 1)
|
|
290
|
+
|
|
291
|
+
output = self.OutputsModel(response=response.response)
|
|
292
|
+
return StopEvent(result=output)
|
vectara_agentic/tools.py
CHANGED
|
@@ -112,28 +112,36 @@ class VectaraTool(FunctionTool):
|
|
|
112
112
|
return vectara_tool
|
|
113
113
|
|
|
114
114
|
def __eq__(self, other):
|
|
115
|
+
if not isinstance(other, VectaraTool):
|
|
116
|
+
return False
|
|
117
|
+
|
|
115
118
|
if self.metadata.tool_type != other.metadata.tool_type:
|
|
116
119
|
return False
|
|
117
120
|
|
|
118
|
-
if self.metadata.name != other.metadata.name
|
|
121
|
+
if self.metadata.name != other.metadata.name:
|
|
119
122
|
return False
|
|
120
123
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
124
|
+
# If schema is a dict-like object, compare the dict representation
|
|
125
|
+
try:
|
|
126
|
+
# Try to get schema as dict if possible
|
|
127
|
+
if hasattr(self.metadata.fn_schema, 'schema'):
|
|
128
|
+
self_schema = self.metadata.fn_schema.schema
|
|
129
|
+
other_schema = other.metadata.fn_schema.schema
|
|
130
|
+
|
|
131
|
+
# Compare only properties and required fields
|
|
132
|
+
self_props = self_schema.get('properties', {})
|
|
133
|
+
other_props = other_schema.get('properties', {})
|
|
134
|
+
|
|
135
|
+
self_required = self_schema.get('required', [])
|
|
136
|
+
other_required = other_schema.get('required', [])
|
|
137
|
+
|
|
138
|
+
return (self_props.keys() == other_props.keys() and
|
|
139
|
+
set(self_required) == set(other_required))
|
|
140
|
+
except Exception:
|
|
141
|
+
# If any exception occurs during schema comparison, fall back to name comparison
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
return True
|
|
137
145
|
|
|
138
146
|
def call(
|
|
139
147
|
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
@@ -312,6 +320,8 @@ class VectaraToolFactory:
|
|
|
312
320
|
self.vectara_corpus_key = vectara_corpus_key
|
|
313
321
|
self.vectara_api_key = vectara_api_key
|
|
314
322
|
self.num_corpora = len(vectara_corpus_key.split(","))
|
|
323
|
+
self.cache_expiry = 60 * 60 # 1 hour
|
|
324
|
+
self.max_cache_size = 128
|
|
315
325
|
|
|
316
326
|
def create_search_tool(
|
|
317
327
|
self,
|
|
@@ -443,7 +453,7 @@ class VectaraToolFactory:
|
|
|
443
453
|
if doc.id_ in unique_ids:
|
|
444
454
|
continue
|
|
445
455
|
unique_ids.add(doc.id_)
|
|
446
|
-
tool_output += f"
|
|
456
|
+
tool_output += f"document_id: '{doc.id_}'\nmetadata: '{doc.metadata}'\n"
|
|
447
457
|
out = ToolOutput(
|
|
448
458
|
tool_name=search_function.__name__,
|
|
449
459
|
content=tool_output,
|
|
@@ -479,10 +489,13 @@ class VectaraToolFactory:
|
|
|
479
489
|
function_str = f"{tool_name}({args_str}) -> str"
|
|
480
490
|
|
|
481
491
|
# Create the tool
|
|
492
|
+
search_tool_extra_desc = """
|
|
493
|
+
The response includes metadata about each relevant document, but NOT the text itself.
|
|
494
|
+
"""
|
|
482
495
|
tool = VectaraTool.from_defaults(
|
|
483
496
|
fn=search_function,
|
|
484
497
|
name=tool_name,
|
|
485
|
-
description=function_str + "
|
|
498
|
+
description=function_str + "\n" + tool_description + '\n' + search_tool_extra_desc,
|
|
486
499
|
fn_schema=tool_args_schema,
|
|
487
500
|
tool_type=ToolType.QUERY,
|
|
488
501
|
)
|
|
@@ -623,8 +636,8 @@ class VectaraToolFactory:
|
|
|
623
636
|
mmr_diversity_bias=mmr_diversity_bias,
|
|
624
637
|
udf_expression=udf_expression,
|
|
625
638
|
rerank_chain=rerank_chain,
|
|
626
|
-
|
|
627
|
-
|
|
639
|
+
n_sentences_before=n_sentences_before,
|
|
640
|
+
n_sentences_after=n_sentences_after,
|
|
628
641
|
offset=offset,
|
|
629
642
|
lambda_val=lambda_val,
|
|
630
643
|
semantics=semantics,
|
vectara_agentic/types.py
CHANGED
|
@@ -57,6 +57,11 @@ class ToolType(Enum):
|
|
|
57
57
|
QUERY = "query"
|
|
58
58
|
ACTION = "action"
|
|
59
59
|
|
|
60
|
+
class AgentConfigType(Enum):
|
|
61
|
+
"""Enumeration for different types of agent configurations."""
|
|
62
|
+
DEFAULT = "default"
|
|
63
|
+
FALLBACK = "fallback"
|
|
64
|
+
|
|
60
65
|
|
|
61
66
|
# classes for Agent responses
|
|
62
67
|
ToolOutput = LI_ToolOutput
|
vectara_agentic/utils.py
CHANGED
|
@@ -17,13 +17,13 @@ from .agent_config import AgentConfig
|
|
|
17
17
|
|
|
18
18
|
provider_to_default_model_name = {
|
|
19
19
|
ModelProvider.OPENAI: "gpt-4o",
|
|
20
|
-
ModelProvider.ANTHROPIC: "claude-3-
|
|
20
|
+
ModelProvider.ANTHROPIC: "claude-3-7-sonnet-20250219",
|
|
21
21
|
ModelProvider.TOGETHER: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
22
22
|
ModelProvider.GROQ: "llama-3.3-70b-versatile",
|
|
23
23
|
ModelProvider.FIREWORKS: "accounts/fireworks/models/firefunction-v2",
|
|
24
24
|
ModelProvider.BEDROCK: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
25
25
|
ModelProvider.COHERE: "command-r-plus",
|
|
26
|
-
ModelProvider.GEMINI: "models/gemini-
|
|
26
|
+
ModelProvider.GEMINI: "models/gemini-2.0-flash",
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
DEFAULT_MODEL_PROVIDER = ModelProvider.OPENAI
|
|
@@ -92,7 +92,9 @@ def get_llm(
|
|
|
92
92
|
"""
|
|
93
93
|
model_provider, model_name = _get_llm_params_for_role(role, config)
|
|
94
94
|
if model_provider == ModelProvider.OPENAI:
|
|
95
|
-
llm = OpenAI(model=model_name, temperature=0,
|
|
95
|
+
llm = OpenAI(model=model_name, temperature=0,
|
|
96
|
+
is_function_calling_model=True,
|
|
97
|
+
strict=True)
|
|
96
98
|
elif model_provider == ModelProvider.ANTHROPIC:
|
|
97
99
|
llm = Anthropic(model=model_name, temperature=0)
|
|
98
100
|
elif model_provider == ModelProvider.GEMINI:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: vectara_agentic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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,13 +16,13 @@ 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.
|
|
20
|
-
Requires-Dist: llama-index-indices-managed-vectara==0.4.
|
|
19
|
+
Requires-Dist: llama-index==0.12.25
|
|
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
|
|
23
23
|
Requires-Dist: llama-index-agent-openai==0.4.6
|
|
24
24
|
Requires-Dist: llama-index-llms-openai==0.3.25
|
|
25
|
-
Requires-Dist: llama-index-llms-anthropic==0.6.
|
|
25
|
+
Requires-Dist: llama-index-llms-anthropic==0.6.10
|
|
26
26
|
Requires-Dist: llama-index-llms-together==0.3.1
|
|
27
27
|
Requires-Dist: llama-index-llms-groq==0.3.1
|
|
28
28
|
Requires-Dist: llama-index-llms-fireworks==0.3.2
|
|
@@ -41,18 +41,18 @@ Requires-Dist: llama-index-tools-exa==0.3.0
|
|
|
41
41
|
Requires-Dist: tavily-python==0.5.1
|
|
42
42
|
Requires-Dist: exa-py==1.8.9
|
|
43
43
|
Requires-Dist: yahoo-finance==1.4.0
|
|
44
|
-
Requires-Dist: openinference-instrumentation-llama-index==3.
|
|
45
|
-
Requires-Dist: opentelemetry-proto==1.
|
|
46
|
-
Requires-Dist: arize-phoenix==
|
|
47
|
-
Requires-Dist: arize-phoenix-otel==0.
|
|
48
|
-
Requires-Dist: protobuf==
|
|
44
|
+
Requires-Dist: openinference-instrumentation-llama-index==3.3.3
|
|
45
|
+
Requires-Dist: opentelemetry-proto==1.31.0
|
|
46
|
+
Requires-Dist: arize-phoenix==8.14.1
|
|
47
|
+
Requires-Dist: arize-phoenix-otel==0.8.0
|
|
48
|
+
Requires-Dist: protobuf==5.29.3
|
|
49
49
|
Requires-Dist: tokenizers>=0.20
|
|
50
|
-
Requires-Dist: pydantic==2.10.
|
|
50
|
+
Requires-Dist: pydantic==2.10.6
|
|
51
51
|
Requires-Dist: retrying==1.3.4
|
|
52
52
|
Requires-Dist: python-dotenv==1.0.1
|
|
53
53
|
Requires-Dist: tiktoken==0.9.0
|
|
54
54
|
Requires-Dist: cloudpickle>=3.1.1
|
|
55
|
-
Requires-Dist: httpx==0.
|
|
55
|
+
Requires-Dist: httpx==0.28.1
|
|
56
56
|
Dynamic: author
|
|
57
57
|
Dynamic: author-email
|
|
58
58
|
Dynamic: classifier
|
|
@@ -60,6 +60,7 @@ Dynamic: description
|
|
|
60
60
|
Dynamic: description-content-type
|
|
61
61
|
Dynamic: home-page
|
|
62
62
|
Dynamic: keywords
|
|
63
|
+
Dynamic: license-file
|
|
63
64
|
Dynamic: project-url
|
|
64
65
|
Dynamic: requires-dist
|
|
65
66
|
Dynamic: requires-python
|
|
@@ -93,14 +94,20 @@ Dynamic: summary
|
|
|
93
94
|
<img src="https://raw.githubusercontent.com/vectara/py-vectara-agentic/main/.github/assets/diagram1.png" alt="Agentic RAG diagram" width="100%" style="vertical-align: middle;">
|
|
94
95
|
</p>
|
|
95
96
|
|
|
96
|
-
###
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
97
|
+
### Key Features
|
|
98
|
+
|
|
99
|
+
- **Rapid Tool Creation:**
|
|
100
|
+
Build Vectara RAG tools or search tools with a single line of code.
|
|
101
|
+
- **Agent Flexibility:**
|
|
102
|
+
Supports multiple agent types including `ReAct`, `OpenAIAgent`, `LATS`, and `LLMCompiler`.
|
|
103
|
+
- **Pre-Built Domain Tools:**
|
|
104
|
+
Tools tailored for finance, legal, and other verticals.
|
|
105
|
+
- **Multi-LLM Integration:**
|
|
106
|
+
Seamless integration with OpenAI, Anthropic, Gemini, GROQ, Together.AI, Cohere, Bedrock, and Fireworks.
|
|
107
|
+
- **Observability:**
|
|
108
|
+
Built-in support with Arize Phoenix for monitoring and feedback.
|
|
109
|
+
- **Workflow Support:**
|
|
110
|
+
Extend your agent’s capabilities by defining custom workflows using the `run()` method.
|
|
104
111
|
|
|
105
112
|
### 📚 Example AI Assistants
|
|
106
113
|
|
|
@@ -200,7 +207,7 @@ agent = Agent(
|
|
|
200
207
|
|
|
201
208
|
See the [docs](https://vectara.github.io/vectara-agentic-docs/) for additional arguments, including `agent_progress_callback` and `query_logging_callback`.
|
|
202
209
|
|
|
203
|
-
### 5. Run
|
|
210
|
+
### 5. Run a chat interaction
|
|
204
211
|
|
|
205
212
|
```python
|
|
206
213
|
res = agent.chat("What was the revenue for Apple in 2021?")
|
|
@@ -213,6 +220,77 @@ Note that:
|
|
|
213
220
|
response it's available as the `response` variable, or just use `str()`. For advanced use-cases you can look
|
|
214
221
|
at other `AgentResponse` variables [such as `sources`](https://github.com/run-llama/llama_index/blob/659f9faaafbecebb6e6c65f42143c0bf19274a37/llama-index-core/llama_index/core/chat_engine/types.py#L53).
|
|
215
222
|
|
|
223
|
+
## Advanced Usage: Workflows
|
|
224
|
+
|
|
225
|
+
In addition to standard chat interactions, `vectara-agentic` supports custom workflows via the `run()` method.
|
|
226
|
+
Workflows allow you to structure multi-step interactions where inputs and outputs are validated using Pydantic models.
|
|
227
|
+
To learn more about workflows read [the documentation](https://docs.llamaindex.ai/en/stable/understanding/workflows/basic_flow/)
|
|
228
|
+
|
|
229
|
+
### Defining a Custom Workflow
|
|
230
|
+
|
|
231
|
+
Create a workflow by subclassing `llama_index.core.workflow.Workflow` and defining the input/output models:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from pydantic import BaseModel
|
|
235
|
+
from llama_index.core.workflow import (
|
|
236
|
+
StartEvent,StopEvent, Workflow, step,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
class MyWorkflow(Workflow):
|
|
240
|
+
class InputsModel(BaseModel):
|
|
241
|
+
query: str
|
|
242
|
+
|
|
243
|
+
class OutputsModel(BaseModel):
|
|
244
|
+
answer: str
|
|
245
|
+
|
|
246
|
+
@step
|
|
247
|
+
async def my_step(self, ev: StartEvent) -> StopEvent:
|
|
248
|
+
# do something here
|
|
249
|
+
return StopEvent(result="Hello, world!")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
When the `run()` method in vectara-agentic is invoked, it calls the workflow with the following variables in the StartEvent:
|
|
253
|
+
* `agent`: the agent object used to call `run()` (self)
|
|
254
|
+
* `tools`: the tools provided to the agent. Those can be used as needed in the flow.
|
|
255
|
+
* `llm`: a pointer to a LlamaIndex llm, so it can be used in the workflow. For example, one of the steps may call `llm.acomplete(prompt)`
|
|
256
|
+
* `verbose`: controls whether extra debug information is displayed
|
|
257
|
+
* `inputs`: this is the actual inputs to the workflow provided by the call to `run()` and must be of type `InputsModel`
|
|
258
|
+
|
|
259
|
+
### Using the Workflow with Your Agent
|
|
260
|
+
|
|
261
|
+
When initializing your agent, pass the workflow class using the `workflow_cls` parameter:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
agent = Agent(
|
|
265
|
+
tools=[query_financial_reports_tool],
|
|
266
|
+
topic="10-K financial reports",
|
|
267
|
+
custom_instructions="You are a helpful financial assistant.",
|
|
268
|
+
workflow_cls=MyWorkflow, # Provide your custom workflow here
|
|
269
|
+
workflow_timeout=120 # Optional: Set a timeout (default is 120 seconds)
|
|
270
|
+
)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Running the Workflow
|
|
274
|
+
|
|
275
|
+
Prepare the inputs using your workflow’s `InputsModel` and execute the workflow using `run()`:
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
# Create an instance of the workflow's input model
|
|
279
|
+
inputs = MyWorkflow.InputsModel(query="What is Vectara?", extra_param=42)
|
|
280
|
+
|
|
281
|
+
# Run the workflow (ensure you're in an async context or use asyncio.run)
|
|
282
|
+
workflow_result = asyncio.run(agent.run(inputs))
|
|
283
|
+
|
|
284
|
+
# Access the output from the workflow's OutputsModel
|
|
285
|
+
print(workflow_result.answer)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Using SubQuestionQueryWorkflow
|
|
289
|
+
|
|
290
|
+
vectara-agentic already includes one useful workflow you can use right away (it is also useful as an advanced example)
|
|
291
|
+
This workflow is called `SubQuestionQueryWorkflow` and it works by breaking a complex query into sub-queries and then
|
|
292
|
+
executing each sub-query with the agent until it reaches a good response.
|
|
293
|
+
|
|
216
294
|
## 🧰 Vectara tools
|
|
217
295
|
|
|
218
296
|
`vectara-agentic` provides two helper functions to connect with Vectara RAG
|
|
@@ -353,9 +431,9 @@ The `AgentConfig` object may include the following items:
|
|
|
353
431
|
|
|
354
432
|
If any of these are not provided, `AgentConfig` first tries to read the values from the OS environment.
|
|
355
433
|
|
|
356
|
-
## Configuring Vectara
|
|
434
|
+
## Configuring Vectara tools: rag_tool, or search_tool
|
|
357
435
|
|
|
358
|
-
When creating a `VectaraToolFactory`, you can pass in a `vectara_api_key`,
|
|
436
|
+
When creating a `VectaraToolFactory`, you can pass in a `vectara_api_key`, and `vectara_corpus_key` to the factory.
|
|
359
437
|
|
|
360
438
|
If not passed in, it will be taken from the environment variables (`VECTARA_API_KEY` and `VECTARA_CORPUS_KEY`). Note that `VECTARA_CORPUS_KEY` can be a single KEY or a comma-separated list of KEYs (if you want to query multiple corpora).
|
|
361
439
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
tests/endpoint.py,sha256=frnpdZQpnuQNNKNYgAn2rFTarNG8MCJaNA77Bw_W22A,1420
|
|
3
|
+
tests/test_agent.py,sha256=5iYlROsGQV_fPF9IR1JZ_ByhQ3EoaLG_40ntrCgugWo,6461
|
|
4
|
+
tests/test_agent_planning.py,sha256=0GEI-b7g5tV8xP_FbTfIu-a8J9s_EhDXC_9T6HS6DsU,1457
|
|
5
|
+
tests/test_agent_type.py,sha256=-14Y6vwYTaRJuj8VZ-c6d1vIiWpV31k2zs_frdoxR5s,2920
|
|
6
|
+
tests/test_fallback.py,sha256=4ZqP_7XsabhzaVgXa599PDbwp38t_XY5fMzQwr8F6Z8,2793
|
|
7
|
+
tests/test_private_llm.py,sha256=rPXQ-NKL2MnrMcGNEG1Zz3U8uK9pjxUfjvIl2gH9gnw,2224
|
|
8
|
+
tests/test_tools.py,sha256=0-2oWX8DW0WIjViNFl0xj_6JOhIdyx6zV0IlTuMzxjk,3954
|
|
9
|
+
tests/test_workflow.py,sha256=lVyrVHdRO5leYNbYtHTmKqMX0c8_xehCpUA7cXQKVsc,2175
|
|
10
|
+
vectara_agentic/__init__.py,sha256=2GLDS3U6KckK-dBRl9v_x1kSV507gEhjOfuMmmu0Qxg,850
|
|
11
|
+
vectara_agentic/_callback.py,sha256=5PfqjLmuaZIR6dnqmhniTD_zwCgfi7kOu-nexb6Kss4,9688
|
|
12
|
+
vectara_agentic/_observability.py,sha256=fTL3KW0jQU-_JSpFgjO6-XzgDut_oiq9kt4QR-FkSqU,3804
|
|
13
|
+
vectara_agentic/_prompts.py,sha256=LYyiOAiC8imz3U7MSJiuCYAP39afsp7ycXY7-9biyJI,9314
|
|
14
|
+
vectara_agentic/_version.py,sha256=EFHZPv0y0xF__sBHhCA8j-o21yOSHXl15GJEp-lZLy4,65
|
|
15
|
+
vectara_agentic/agent.py,sha256=74_2XzBvl5jPyAqqYhoxsS7PXITWBdJpxs4L_XeyZio,42561
|
|
16
|
+
vectara_agentic/agent_config.py,sha256=y1hSvU5ns0cE2R7BqF65LFstixF1ytJcoVgicGXo7w0,3691
|
|
17
|
+
vectara_agentic/agent_endpoint.py,sha256=QIMejCLlpW2qzXxeDAxv3anF46XMDdVMdKGWhJh3azY,1996
|
|
18
|
+
vectara_agentic/db_tools.py,sha256=VUdcjDFPwauFd2A92mXNYZnCjeMiTzcTka7S5At_3oQ,3595
|
|
19
|
+
vectara_agentic/sub_query_workflow.py,sha256=KcIfUaDcv25n8iLQmZ9ZhNlKyZAKAu-3otXADukBios,10394
|
|
20
|
+
vectara_agentic/tools.py,sha256=xWxl1ixSCsBPjZ-GNpkjN_nXRBxvH_vr8oDauAYrIW0,41763
|
|
21
|
+
vectara_agentic/tools_catalog.py,sha256=oiw3wAfbpFhh0_6rMvZsyPqWV6QIzHqhZCNzqRxuyV8,4818
|
|
22
|
+
vectara_agentic/types.py,sha256=tLpyDY-UbFN2Iqk_fgWoOxlGexh_AQ5BaXQ593sCkRc,1750
|
|
23
|
+
vectara_agentic/utils.py,sha256=AUyWrL8aY67AGx6j9m00k75JRHTI44EAKtal73aMczM,5504
|
|
24
|
+
vectara_agentic-0.2.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
25
|
+
vectara_agentic-0.2.6.dist-info/METADATA,sha256=u9gIGxK3XEPeSItrUevqwJVOWWzRJ3Mqdo55-l3o098,25046
|
|
26
|
+
vectara_agentic-0.2.6.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
27
|
+
vectara_agentic-0.2.6.dist-info/top_level.txt,sha256=Y7TQTFdOYGYodQRltUGRieZKIYuzeZj2kHqAUpfCUfg,22
|
|
28
|
+
vectara_agentic-0.2.6.dist-info/RECORD,,
|