hyperforge-conditional 1.0.0.post19__tar.gz
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.
- hyperforge_conditional-1.0.0.post19/PKG-INFO +19 -0
- hyperforge_conditional-1.0.0.post19/README.md +1 -0
- hyperforge_conditional-1.0.0.post19/pyproject.toml +43 -0
- hyperforge_conditional-1.0.0.post19/setup.cfg +4 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional/__init__.py +11 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional/conditional.py +235 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional/context_agent.py +159 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional/generation_agent.py +48 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional/postprocess_agent.py +47 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional/preprocess_agent.py +47 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional.egg-info/PKG-INFO +19 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional.egg-info/SOURCES.txt +14 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional.egg-info/dependency_links.txt +1 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional.egg-info/requires.txt +1 -0
- hyperforge_conditional-1.0.0.post19/src/hyperforge_conditional.egg-info/top_level.txt +1 -0
- hyperforge_conditional-1.0.0.post19/tests/test_conditional.py +303 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyperforge_conditional
|
|
3
|
+
Version: 1.0.0.post19
|
|
4
|
+
Summary: Conditional Hyperforge agent
|
|
5
|
+
Author-email: Nuclia <nucliadb@nuclia.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://progress.com
|
|
8
|
+
Project-URL: Repository, https://github.com/nuclia/forge
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: <4,>=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: hyperforge
|
|
18
|
+
|
|
19
|
+
# Conditional Hyperforge agents
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Conditional Hyperforge agents
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hyperforge_conditional"
|
|
7
|
+
version = "1.0.0.post19"
|
|
8
|
+
license = "Apache-2.0"
|
|
9
|
+
description = "Conditional Hyperforge agent"
|
|
10
|
+
authors = [{ name = "Nuclia", email = "nucliadb@nuclia.com" }]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.10, <4"
|
|
21
|
+
dependencies = ["hyperforge"]
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"pytest",
|
|
26
|
+
"pytest-benchmark",
|
|
27
|
+
"pytest-docker-fixtures>=1.4.2",
|
|
28
|
+
"pytest-lazy-fixtures",
|
|
29
|
+
"pytest-recording",
|
|
30
|
+
"pytest-asyncio",
|
|
31
|
+
"pytest-cov",
|
|
32
|
+
"pytest-mock",
|
|
33
|
+
"respx",
|
|
34
|
+
"hyperforge_static",
|
|
35
|
+
"hyperforge_summarize",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://progress.com"
|
|
40
|
+
Repository = "https://github.com/nuclia/forge"
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .context_agent import ContextConditional
|
|
2
|
+
from .generation_agent import GenerationConditional
|
|
3
|
+
from .postprocess_agent import PostprocessConditional
|
|
4
|
+
from .preprocess_agent import PreprocessConditional
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"ContextConditional",
|
|
8
|
+
"GenerationConditional",
|
|
9
|
+
"PreprocessConditional",
|
|
10
|
+
"PostprocessConditional",
|
|
11
|
+
]
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from time import time
|
|
3
|
+
from typing import Any, Dict, List, Literal, Optional, cast
|
|
4
|
+
|
|
5
|
+
from hyperforge.agent import Agent, AgentConfig
|
|
6
|
+
from hyperforge.configure import get_agent_config_klass, get_agent_klass
|
|
7
|
+
from hyperforge.manager import Manager
|
|
8
|
+
from hyperforge.memory.memory import QuestionMemory
|
|
9
|
+
from hyperforge.trace import trace_agent
|
|
10
|
+
from hyperforge.utils import WidgetType
|
|
11
|
+
from pydantic import BaseModel, Field, field_serializer, field_validator
|
|
12
|
+
|
|
13
|
+
from hyperforge import PROMPT_ENVIRONMENT
|
|
14
|
+
|
|
15
|
+
CONDITIONAL_AGENT = """
|
|
16
|
+
Given a prompt that indicates a condition , assess whether a given text fulfills it or not.
|
|
17
|
+
# PROMPT:
|
|
18
|
+
{{prompt}}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# TEXT:
|
|
22
|
+
{{text}}
|
|
23
|
+
|
|
24
|
+
{% if similarity %}
|
|
25
|
+
# SIMILAR QUERIES:
|
|
26
|
+
Similar queries that also fulfill that condition
|
|
27
|
+
{% for similar in similarity_examples %}
|
|
28
|
+
- {{similar}}
|
|
29
|
+
{% endfor -%}
|
|
30
|
+
{% endif %}
|
|
31
|
+
|
|
32
|
+
# Important rules to follow
|
|
33
|
+
|
|
34
|
+
{% for rule in rules %}
|
|
35
|
+
{{rule}}
|
|
36
|
+
{% endfor -%}
|
|
37
|
+
|
|
38
|
+
# Output definition:
|
|
39
|
+
|
|
40
|
+
Your output should always follow the following schema in a JSON format (Not markdon, please, start with b...).
|
|
41
|
+
|
|
42
|
+
{{json_schema}}
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
CONDITIONAL_AGENT_TEMPLATE = PROMPT_ENVIRONMENT.from_string(CONDITIONAL_AGENT)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
CONDITIONAL_SCHEMA = {
|
|
49
|
+
"title": "yes_no",
|
|
50
|
+
"description": "Choose yes or no, depending on whether the condition is fulfilled",
|
|
51
|
+
"parameters": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"yes": {"type": "boolean"},
|
|
55
|
+
"reason": {"type": "string", "description": "reasoning behind the answer"},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ConditionalAgentConfig(AgentConfig):
|
|
62
|
+
then: List[AgentConfig] = Field(
|
|
63
|
+
default_factory=list,
|
|
64
|
+
title="Then",
|
|
65
|
+
description="List of agents to run in case the condition is valid",
|
|
66
|
+
)
|
|
67
|
+
else_: List[AgentConfig] = Field(
|
|
68
|
+
default_factory=list,
|
|
69
|
+
title="Else",
|
|
70
|
+
description="List of agents to run in case the condition is not valid",
|
|
71
|
+
)
|
|
72
|
+
prompt: Optional[str] = Field(
|
|
73
|
+
default=None,
|
|
74
|
+
title="Condition Prompt",
|
|
75
|
+
description="Prompt to evaluate the condition",
|
|
76
|
+
json_schema_extra={
|
|
77
|
+
"show_in_node": True,
|
|
78
|
+
"widget": WidgetType.EXPANDABLE_TEXTAREA,
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
has_keywords: Optional[List[str]] = Field(
|
|
82
|
+
default=None,
|
|
83
|
+
title="Keywords",
|
|
84
|
+
description="List of keywords to evaluate the condition",
|
|
85
|
+
)
|
|
86
|
+
similarity: Optional[List[str]] = Field(
|
|
87
|
+
default=None,
|
|
88
|
+
title="Similar Queries",
|
|
89
|
+
description="List of similar queries to evaluate the condition",
|
|
90
|
+
)
|
|
91
|
+
on: Literal["QUESTION", "ANSWER", "CONTEXT"] = Field(
|
|
92
|
+
default="QUESTION",
|
|
93
|
+
title="Evaluate condition on",
|
|
94
|
+
description="Source to evaluate the condition on. ",
|
|
95
|
+
)
|
|
96
|
+
model: str = Field(
|
|
97
|
+
default="chatgpt-o3-mini",
|
|
98
|
+
title="Generative model",
|
|
99
|
+
description="Model used to assess the condition",
|
|
100
|
+
json_schema_extra={"widget": WidgetType.MODEL_SELECT},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@field_serializer("then", "else_")
|
|
104
|
+
def serialize_conditional_agent(
|
|
105
|
+
self, field: list[BaseModel]
|
|
106
|
+
) -> Optional[List[Dict[str, Any]]]:
|
|
107
|
+
if field is None:
|
|
108
|
+
return field
|
|
109
|
+
return [agent.model_dump() for agent in field]
|
|
110
|
+
|
|
111
|
+
@field_validator("then", "else_", mode="before")
|
|
112
|
+
@classmethod
|
|
113
|
+
def is_conditional_agent(cls, value: list[Dict[str, Any]]) -> list[BaseModel]:
|
|
114
|
+
if value is None:
|
|
115
|
+
return value
|
|
116
|
+
result = []
|
|
117
|
+
for agent_cfg in value:
|
|
118
|
+
module = agent_cfg.get("module")
|
|
119
|
+
if module is None:
|
|
120
|
+
raise ValueError("Invalid agent config: missing 'module' field")
|
|
121
|
+
|
|
122
|
+
agent_config_klass = get_agent_config_klass(module)
|
|
123
|
+
agent_config_instance = agent_config_klass.model_validate(agent_cfg)
|
|
124
|
+
result.append(agent_config_instance)
|
|
125
|
+
return result # type: ignore
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class Conditional:
|
|
129
|
+
then: Optional[list[Agent]] = None
|
|
130
|
+
else_: Optional[list[Agent]] = None
|
|
131
|
+
arag_step: str
|
|
132
|
+
config: Any
|
|
133
|
+
agent_id: Any
|
|
134
|
+
|
|
135
|
+
async def conditional_from_config(self, config: ConditionalAgentConfig):
|
|
136
|
+
agent_module = None
|
|
137
|
+
then_agents_obj_list = []
|
|
138
|
+
for then_agent in config.then:
|
|
139
|
+
if then_agent:
|
|
140
|
+
agent_module = then_agent.module
|
|
141
|
+
if agent_module is None:
|
|
142
|
+
raise Exception("No agent found")
|
|
143
|
+
|
|
144
|
+
agent_klass = get_agent_klass(agent_module)
|
|
145
|
+
# We dump and load to create the agent via from_config
|
|
146
|
+
then_agents_obj_list.append((agent_klass, then_agent))
|
|
147
|
+
|
|
148
|
+
agent_id = None
|
|
149
|
+
else_agents_obj_list = []
|
|
150
|
+
for else_agent in config.else_:
|
|
151
|
+
if else_agent is not None:
|
|
152
|
+
agent_id = else_agent.module
|
|
153
|
+
if agent_id is None:
|
|
154
|
+
raise Exception("No agent found")
|
|
155
|
+
|
|
156
|
+
agent_class = get_agent_klass(agent_id)
|
|
157
|
+
# We dump and load to create the agent via from_config
|
|
158
|
+
else_agents_obj_list.append((agent_class, else_agent))
|
|
159
|
+
|
|
160
|
+
self.then = await asyncio.gather(
|
|
161
|
+
*[klass.from_config(cfg) for klass, cfg in then_agents_obj_list]
|
|
162
|
+
)
|
|
163
|
+
self.else_ = (
|
|
164
|
+
await asyncio.gather(
|
|
165
|
+
*[klass.from_config(cfg) for klass, cfg in else_agents_obj_list]
|
|
166
|
+
)
|
|
167
|
+
if else_agents_obj_list
|
|
168
|
+
else None
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
async def make_decision(
|
|
172
|
+
self,
|
|
173
|
+
question: str,
|
|
174
|
+
memory: QuestionMemory,
|
|
175
|
+
manager: Manager,
|
|
176
|
+
title: Optional[str] = None,
|
|
177
|
+
) -> bool:
|
|
178
|
+
t0 = time()
|
|
179
|
+
config: ConditionalAgentConfig = cast(ConditionalAgentConfig, self.config)
|
|
180
|
+
|
|
181
|
+
prompt = CONDITIONAL_AGENT_TEMPLATE.render(
|
|
182
|
+
prompt=config.prompt,
|
|
183
|
+
rules=memory.get_rules(),
|
|
184
|
+
text=question,
|
|
185
|
+
similarity_examples=config.similarity,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
sources, input, output = await manager.execute_json(
|
|
189
|
+
prompt=prompt,
|
|
190
|
+
schema=CONDITIONAL_SCHEMA,
|
|
191
|
+
user_id="conditional",
|
|
192
|
+
model=config.model,
|
|
193
|
+
tracking=memory.get_tracking_info(),
|
|
194
|
+
)
|
|
195
|
+
condition = sources.get("yes", False)
|
|
196
|
+
reason = sources.get("reason")
|
|
197
|
+
|
|
198
|
+
await memory.add_step(
|
|
199
|
+
step_module=config.module,
|
|
200
|
+
step_title=f"{title}: Condition check",
|
|
201
|
+
step_value=str(condition),
|
|
202
|
+
step_reason=reason,
|
|
203
|
+
timeit=time() - t0,
|
|
204
|
+
input_nuclia_tokens=input,
|
|
205
|
+
output_nuclia_tokens=output,
|
|
206
|
+
step_agent_path=f"/{self.arag_step}/{self.agent_id}",
|
|
207
|
+
)
|
|
208
|
+
return condition
|
|
209
|
+
|
|
210
|
+
@trace_agent
|
|
211
|
+
async def __call__(
|
|
212
|
+
self,
|
|
213
|
+
memory: QuestionMemory,
|
|
214
|
+
manager: Manager,
|
|
215
|
+
):
|
|
216
|
+
question = ""
|
|
217
|
+
if self.config.on == "QUESTION" and memory.actual_question:
|
|
218
|
+
question = memory.actual_question
|
|
219
|
+
elif self.config.on == "ANSWER" and memory.final_answer:
|
|
220
|
+
question = memory.final_answer
|
|
221
|
+
elif self.config.on == "CONTEXT":
|
|
222
|
+
question = "\n".join([x.summary for x in memory.contexts])
|
|
223
|
+
|
|
224
|
+
condition = await self.make_decision(
|
|
225
|
+
memory=memory,
|
|
226
|
+
manager=manager,
|
|
227
|
+
question=question,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if condition and self.then is not None:
|
|
231
|
+
for then_agent in self.then:
|
|
232
|
+
await then_agent(memory, manager) # type: ignore
|
|
233
|
+
elif condition is False and self.else_ is not None:
|
|
234
|
+
for else_agent in self.else_:
|
|
235
|
+
await else_agent(memory, manager) # type: ignore
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from typing import Any, Dict, Literal, Optional, cast
|
|
2
|
+
from uuid import uuid4
|
|
3
|
+
|
|
4
|
+
from hyperforge.agent import Agent
|
|
5
|
+
from hyperforge.configure import agent
|
|
6
|
+
from hyperforge.context.agent import ContextAgent, trace_agent
|
|
7
|
+
from hyperforge.context.config import ContextAgentConfig
|
|
8
|
+
from hyperforge.manager import Manager
|
|
9
|
+
from hyperforge.memory.memory import Chunk, Context, QuestionMemory
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
from pydantic.config import ConfigDict
|
|
12
|
+
|
|
13
|
+
from hyperforge_conditional.conditional import (
|
|
14
|
+
Conditional,
|
|
15
|
+
ConditionalAgentConfig,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ContextConditionalAgentConfig(ConditionalAgentConfig, ContextAgentConfig):
|
|
20
|
+
model_config = ConfigDict(title="Condition")
|
|
21
|
+
module: Literal["context_conditional"] = "context_conditional"
|
|
22
|
+
on: Literal["QUESTION", "CONTEXT"] = Field( # type: ignore
|
|
23
|
+
default="QUESTION",
|
|
24
|
+
title="Evaluate condition on",
|
|
25
|
+
description="Source to evaluate the condition on. CONTEXT is only valid when this agent is used as a next agent in a chain.",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@agent(
|
|
30
|
+
id="context_conditional",
|
|
31
|
+
agent_type="context",
|
|
32
|
+
title="Context Conditional",
|
|
33
|
+
description="Use Context Conditional to get information from the internet to answer questions.",
|
|
34
|
+
config_schema=ContextConditionalAgentConfig,
|
|
35
|
+
)
|
|
36
|
+
class ContextConditional(
|
|
37
|
+
ContextAgent, Conditional, Agent[ContextConditionalAgentConfig]
|
|
38
|
+
):
|
|
39
|
+
arag_step: str = "context"
|
|
40
|
+
|
|
41
|
+
async def inner_from_config(
|
|
42
|
+
self, config: ContextConditionalAgentConfig, agent_id: Optional[str] = None
|
|
43
|
+
):
|
|
44
|
+
# Build then and else branches
|
|
45
|
+
await self.context_from_config(config)
|
|
46
|
+
await self.conditional_from_config(config)
|
|
47
|
+
|
|
48
|
+
async def __call__(self, memory: QuestionMemory, manager: Manager):
|
|
49
|
+
await Conditional.__call__(self, memory, manager)
|
|
50
|
+
|
|
51
|
+
@trace_agent
|
|
52
|
+
async def get_question_context(
|
|
53
|
+
self,
|
|
54
|
+
memory: QuestionMemory,
|
|
55
|
+
manager: Manager,
|
|
56
|
+
question_uuid: str,
|
|
57
|
+
question: str,
|
|
58
|
+
flow_id: str,
|
|
59
|
+
extra_context: Optional[Dict[str, Any]] = None,
|
|
60
|
+
):
|
|
61
|
+
self.config: ContextConditionalAgentConfig # type: ignore
|
|
62
|
+
if extra_context is not None:
|
|
63
|
+
question, question_uuid = await self.rephrase(
|
|
64
|
+
memory=memory,
|
|
65
|
+
manager=manager,
|
|
66
|
+
question_uuid=question_uuid,
|
|
67
|
+
question=question,
|
|
68
|
+
contexts=extra_context,
|
|
69
|
+
model=self.config.rephrase_model,
|
|
70
|
+
module=self.config.module,
|
|
71
|
+
user_id="next_rephrase",
|
|
72
|
+
ident=self.agent_id,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
conditional_subject = question
|
|
76
|
+
# Previous context only used when selected and extra context is provided - that is, the conditional comes after a next agent in the flow
|
|
77
|
+
if self.config.on == "CONTEXT" and extra_context is not None:
|
|
78
|
+
conditional_subject = "\n".join(extra_context.values())
|
|
79
|
+
|
|
80
|
+
# condition checking and branching
|
|
81
|
+
condition = await self.make_decision(
|
|
82
|
+
memory=memory,
|
|
83
|
+
manager=manager,
|
|
84
|
+
question=conditional_subject,
|
|
85
|
+
)
|
|
86
|
+
selected_agents: list[Agent] = [
|
|
87
|
+
agent for agent in (self.then if condition else self.else_) or []
|
|
88
|
+
]
|
|
89
|
+
for selected_agent in selected_agents:
|
|
90
|
+
await cast(ContextAgent, selected_agent).get_question_context(
|
|
91
|
+
memory, manager, question_uuid, question, flow_id=flow_id
|
|
92
|
+
)
|
|
93
|
+
# If fallback is configured, we check if there are missing questions taking into account the executed agents contexts/summaries
|
|
94
|
+
if self.fallback is not None:
|
|
95
|
+
conditional_context = Context(
|
|
96
|
+
agent_id=self.agent_id,
|
|
97
|
+
original_question_uuid=memory.original_question_uuid,
|
|
98
|
+
actual_question_uuid=question_uuid,
|
|
99
|
+
question=question,
|
|
100
|
+
agent="Conditional",
|
|
101
|
+
title="Summarizing Conditional Agents Contexts",
|
|
102
|
+
source="conditional",
|
|
103
|
+
chunks=[],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
for agent in selected_agents:
|
|
107
|
+
conditional_context.chunks.extend(
|
|
108
|
+
[
|
|
109
|
+
Chunk(
|
|
110
|
+
chunk_id=agent.agent_id,
|
|
111
|
+
text=summary,
|
|
112
|
+
origin_agent=self.config.module,
|
|
113
|
+
)
|
|
114
|
+
for summary in memory.get_agent_answer_summaries(
|
|
115
|
+
flow_id=flow_id, agent_id=agent.agent_id
|
|
116
|
+
)
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
(
|
|
120
|
+
_,
|
|
121
|
+
missing_question,
|
|
122
|
+
_,
|
|
123
|
+
) = await self.validate_ctx_and_answer(
|
|
124
|
+
memory,
|
|
125
|
+
manager,
|
|
126
|
+
conditional_context,
|
|
127
|
+
question=question,
|
|
128
|
+
)
|
|
129
|
+
if missing_question is not None and missing_question.strip() != "":
|
|
130
|
+
await self.fallback.get_question_context(
|
|
131
|
+
memory,
|
|
132
|
+
manager,
|
|
133
|
+
question_uuid=uuid4().hex,
|
|
134
|
+
question=missing_question,
|
|
135
|
+
flow_id=flow_id,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if self.next_agent is not None:
|
|
139
|
+
extra_context = extra_context or {}
|
|
140
|
+
for agent in ( # type: ignore
|
|
141
|
+
selected_agents + [self.fallback]
|
|
142
|
+
if self.fallback is not None
|
|
143
|
+
else selected_agents
|
|
144
|
+
):
|
|
145
|
+
if agent:
|
|
146
|
+
answer_summaries = memory.get_agent_answer_summaries(
|
|
147
|
+
flow_id=flow_id, agent_id=agent.agent_id
|
|
148
|
+
)
|
|
149
|
+
if answer_summaries:
|
|
150
|
+
extra_context[agent.agent_id] = "\n".join(answer_summaries)
|
|
151
|
+
|
|
152
|
+
await self.next_agent.get_question_context(
|
|
153
|
+
memory,
|
|
154
|
+
manager,
|
|
155
|
+
question_uuid,
|
|
156
|
+
question,
|
|
157
|
+
extra_context=extra_context,
|
|
158
|
+
flow_id=flow_id,
|
|
159
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from hyperforge.agent import Agent, AgentConfig
|
|
4
|
+
from hyperforge.configure import agent
|
|
5
|
+
from hyperforge.manager import Manager
|
|
6
|
+
from hyperforge.memory.memory import QuestionMemory
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
from pydantic.config import ConfigDict
|
|
9
|
+
|
|
10
|
+
from hyperforge_conditional.conditional import (
|
|
11
|
+
Conditional,
|
|
12
|
+
ConditionalAgentConfig,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GenerationConditionalAgentConfig(ConditionalAgentConfig):
|
|
17
|
+
model_config = ConfigDict(title="Condition")
|
|
18
|
+
module: Literal["generation_conditional"] = "generation_conditional"
|
|
19
|
+
then: List["AgentConfig"] = Field(
|
|
20
|
+
default_factory=list,
|
|
21
|
+
title="Then",
|
|
22
|
+
description="List of agents to run in case the condition is valid",
|
|
23
|
+
)
|
|
24
|
+
else_: List["AgentConfig"] = Field(
|
|
25
|
+
default_factory=list,
|
|
26
|
+
title="Else",
|
|
27
|
+
description="List of agents to run in case the condition is not valid",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@agent(
|
|
32
|
+
id="generation_conditional",
|
|
33
|
+
agent_type="generation",
|
|
34
|
+
title="Generation Conditional",
|
|
35
|
+
description="Conditional generation agent that decides which generation strategy to use based on the context.",
|
|
36
|
+
config_schema=GenerationConditionalAgentConfig,
|
|
37
|
+
)
|
|
38
|
+
class GenerationConditional(Agent[GenerationConditionalAgentConfig], Conditional):
|
|
39
|
+
arag_step: str = "generation"
|
|
40
|
+
|
|
41
|
+
async def inner_from_config(
|
|
42
|
+
self, config: GenerationConditionalAgentConfig, agent_id: Optional[str] = None
|
|
43
|
+
):
|
|
44
|
+
# Build then and else branches
|
|
45
|
+
await self.conditional_from_config(config)
|
|
46
|
+
|
|
47
|
+
async def __call__(self, memory: QuestionMemory, manager: Manager):
|
|
48
|
+
await Conditional.__call__(self, memory, manager)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from hyperforge.agent import Agent, AgentConfig
|
|
4
|
+
from hyperforge.configure import agent
|
|
5
|
+
from hyperforge.manager import Manager
|
|
6
|
+
from hyperforge.memory.memory import QuestionMemory
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
from pydantic.config import ConfigDict
|
|
9
|
+
|
|
10
|
+
from hyperforge_conditional.conditional import (
|
|
11
|
+
Conditional,
|
|
12
|
+
ConditionalAgentConfig,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PostprocessConditionalAgentConfig(ConditionalAgentConfig):
|
|
17
|
+
model_config = ConfigDict(title="Condition")
|
|
18
|
+
module: Literal["post_conditional"] = "post_conditional"
|
|
19
|
+
then: List["AgentConfig"] = Field(
|
|
20
|
+
default_factory=list,
|
|
21
|
+
title="Then",
|
|
22
|
+
description="List of agents to run in case the condition is valid",
|
|
23
|
+
)
|
|
24
|
+
else_: List["AgentConfig"] = Field(
|
|
25
|
+
default_factory=list,
|
|
26
|
+
title="Else",
|
|
27
|
+
description="List of agents to run in case the condition is not valid",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@agent(
|
|
32
|
+
id="postprocess_conditional",
|
|
33
|
+
agent_type="postprocess",
|
|
34
|
+
title="Postprocess Conditional",
|
|
35
|
+
description="Agent that performs conditional postprocessing.",
|
|
36
|
+
config_schema=PostprocessConditionalAgentConfig,
|
|
37
|
+
)
|
|
38
|
+
class PostprocessConditional(Agent[PostprocessConditionalAgentConfig], Conditional):
|
|
39
|
+
arag_step: str = "postprocess"
|
|
40
|
+
|
|
41
|
+
async def inner_from_config(
|
|
42
|
+
self, config: PostprocessConditionalAgentConfig, agent_id: Optional[str] = None
|
|
43
|
+
):
|
|
44
|
+
await self.conditional_from_config(config)
|
|
45
|
+
|
|
46
|
+
async def __call__(self, memory: QuestionMemory, manager: Manager):
|
|
47
|
+
await Conditional.__call__(self, memory, manager)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from hyperforge.agent import Agent, AgentConfig
|
|
4
|
+
from hyperforge.configure import agent
|
|
5
|
+
from hyperforge.manager import Manager
|
|
6
|
+
from hyperforge.memory.memory import QuestionMemory
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
from pydantic.config import ConfigDict
|
|
9
|
+
|
|
10
|
+
from hyperforge_conditional.conditional import (
|
|
11
|
+
Conditional,
|
|
12
|
+
ConditionalAgentConfig,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PreprocessConditionalAgentConfig(ConditionalAgentConfig):
|
|
17
|
+
model_config = ConfigDict(title="Condition")
|
|
18
|
+
module: Literal["pre_conditional"] = "pre_conditional"
|
|
19
|
+
then: List[AgentConfig] = Field(
|
|
20
|
+
default_factory=list,
|
|
21
|
+
title="Then",
|
|
22
|
+
description="List of agents to run in case the condition is valid",
|
|
23
|
+
)
|
|
24
|
+
else_: List[AgentConfig] = Field(
|
|
25
|
+
default_factory=list,
|
|
26
|
+
title="Else",
|
|
27
|
+
description="List of agents to run in case the condition is not valid",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@agent(
|
|
32
|
+
id="preprocess_conditional",
|
|
33
|
+
agent_type="preprocess",
|
|
34
|
+
title="Preprocess Conditional",
|
|
35
|
+
description="Conditional preprocessing agent that decides which preprocessing strategy to use based on the context.",
|
|
36
|
+
config_schema=PreprocessConditionalAgentConfig,
|
|
37
|
+
)
|
|
38
|
+
class PreprocessConditional(Agent[PreprocessConditionalAgentConfig], Conditional):
|
|
39
|
+
arag_step: str = "preprocess"
|
|
40
|
+
|
|
41
|
+
async def inner_from_config(
|
|
42
|
+
self, config: PreprocessConditionalAgentConfig, agent_id: Optional[str] = None
|
|
43
|
+
):
|
|
44
|
+
await self.conditional_from_config(config)
|
|
45
|
+
|
|
46
|
+
async def __call__(self, memory: QuestionMemory, manager: Manager):
|
|
47
|
+
await Conditional.__call__(self, memory, manager)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyperforge_conditional
|
|
3
|
+
Version: 1.0.0.post19
|
|
4
|
+
Summary: Conditional Hyperforge agent
|
|
5
|
+
Author-email: Nuclia <nucliadb@nuclia.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://progress.com
|
|
8
|
+
Project-URL: Repository, https://github.com/nuclia/forge
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: <4,>=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: hyperforge
|
|
18
|
+
|
|
19
|
+
# Conditional Hyperforge agents
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/hyperforge_conditional/__init__.py
|
|
4
|
+
src/hyperforge_conditional/conditional.py
|
|
5
|
+
src/hyperforge_conditional/context_agent.py
|
|
6
|
+
src/hyperforge_conditional/generation_agent.py
|
|
7
|
+
src/hyperforge_conditional/postprocess_agent.py
|
|
8
|
+
src/hyperforge_conditional/preprocess_agent.py
|
|
9
|
+
src/hyperforge_conditional.egg-info/PKG-INFO
|
|
10
|
+
src/hyperforge_conditional.egg-info/SOURCES.txt
|
|
11
|
+
src/hyperforge_conditional.egg-info/dependency_links.txt
|
|
12
|
+
src/hyperforge_conditional.egg-info/requires.txt
|
|
13
|
+
src/hyperforge_conditional.egg-info/top_level.txt
|
|
14
|
+
tests/test_conditional.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hyperforge
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hyperforge_conditional
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from hyperforge.engine import main as arag_main
|
|
6
|
+
from hyperforge.minimal_fixtures import cassette_nua_key
|
|
7
|
+
|
|
8
|
+
NUA_KEY = os.environ.get(
|
|
9
|
+
"NUA_KEY",
|
|
10
|
+
) or cassette_nua_key("https://europe-1.nuclia.cloud/")
|
|
11
|
+
|
|
12
|
+
CONFIG = {
|
|
13
|
+
"drivers": [],
|
|
14
|
+
"rules": {
|
|
15
|
+
"rules": [
|
|
16
|
+
{"prompt": "Be polite"},
|
|
17
|
+
{
|
|
18
|
+
"prompt": "The documentation of Nuclia is hosted at https://docs.nuclia.dev"
|
|
19
|
+
},
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"memory": {},
|
|
23
|
+
"workflow": {
|
|
24
|
+
"id": "default",
|
|
25
|
+
"name": "Default workflow",
|
|
26
|
+
"description": "Default workflow for testing",
|
|
27
|
+
"parameters": {},
|
|
28
|
+
},
|
|
29
|
+
"preprocess": [],
|
|
30
|
+
"context": [
|
|
31
|
+
{
|
|
32
|
+
"module": "context_conditional",
|
|
33
|
+
"title": "",
|
|
34
|
+
"prompt": "The question is about Atlas",
|
|
35
|
+
"then": [
|
|
36
|
+
{
|
|
37
|
+
"module": "static",
|
|
38
|
+
"title": "Static Agent",
|
|
39
|
+
"context": "Atlas is Carmen's dog. He is small and very cute but a bit moody. He was a rescue dog.",
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"else_": [
|
|
43
|
+
{
|
|
44
|
+
"module": "static",
|
|
45
|
+
"title": "Static Agent",
|
|
46
|
+
"context": "RAO is a product of Progress agentic RAG, it is a powerful agent orchestrator that you are using right now.",
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
"generation": [
|
|
52
|
+
{"module": "summarize"},
|
|
53
|
+
],
|
|
54
|
+
"postprocess": [],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
@pytest.mark.vcr(ignore_localhost=True)
|
|
60
|
+
async def test_conditional_static():
|
|
61
|
+
question_memory = await arag_main(
|
|
62
|
+
agent_id="default",
|
|
63
|
+
internal_nua=False,
|
|
64
|
+
external_nua_api_key=NUA_KEY,
|
|
65
|
+
question="Who is Atlas?",
|
|
66
|
+
config=CONFIG,
|
|
67
|
+
user_metadata={"fullname": "Carmen", "age": "99"},
|
|
68
|
+
loaded_modules=[
|
|
69
|
+
"hyperforge_conditional",
|
|
70
|
+
"hyperforge_static",
|
|
71
|
+
"hyperforge_summarize",
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
keywords = ["Atlas", "Carmen", "dog"]
|
|
76
|
+
|
|
77
|
+
assert question_memory.final_answer
|
|
78
|
+
assert all(
|
|
79
|
+
keyword.lower() in question_memory.final_answer.lower() for keyword in keywords
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
question_memory = await arag_main(
|
|
83
|
+
agent_id="default",
|
|
84
|
+
internal_nua=False,
|
|
85
|
+
external_nua_api_key=NUA_KEY,
|
|
86
|
+
question="What is RAO?",
|
|
87
|
+
config=CONFIG,
|
|
88
|
+
user_metadata={"fullname": "Carmen", "age": "99"},
|
|
89
|
+
loaded_modules=[
|
|
90
|
+
"hyperforge_conditional",
|
|
91
|
+
"hyperforge_static",
|
|
92
|
+
"hyperforge_summarize",
|
|
93
|
+
],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
keywords = ["RAO", "powerful", "agent", "orchestrator"]
|
|
97
|
+
|
|
98
|
+
assert question_memory.final_answer
|
|
99
|
+
assert all(
|
|
100
|
+
keyword.lower() in question_memory.final_answer.lower() for keyword in keywords
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
AGENTS_CONTEXT_CONDITIONAL = {
|
|
105
|
+
"context": [
|
|
106
|
+
{
|
|
107
|
+
"module": "static",
|
|
108
|
+
"title": "Static Agent",
|
|
109
|
+
"context": "The best cardamom buns are in Denmark, the most famous place is a bakery called Juno.",
|
|
110
|
+
"next_agent": {
|
|
111
|
+
"module": "context_conditional",
|
|
112
|
+
"title": "",
|
|
113
|
+
"prompt": "The context mentions Juno",
|
|
114
|
+
"on": "CONTEXT",
|
|
115
|
+
"then": [
|
|
116
|
+
{
|
|
117
|
+
"module": "static",
|
|
118
|
+
"title": "Static Agent",
|
|
119
|
+
"context": "Juno is a bakery in Denmark famous for its cardamom buns. They are the best in the world. The also have a wide variety of other delicious pastries. And great coffee.",
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"else_": [
|
|
123
|
+
{
|
|
124
|
+
"module": "static",
|
|
125
|
+
"title": "Static Agent",
|
|
126
|
+
"context": "You can find good cardamom buns in many places. Some people say the best ones are in Fabrique in New York, but that is subjective. The best way to find good cardamom buns is to try them in different bakeries and see which one you like the most.",
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
"generation": [
|
|
133
|
+
{"module": "summarize"},
|
|
134
|
+
],
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.asyncio
|
|
139
|
+
@pytest.mark.vcr(ignore_localhost=True)
|
|
140
|
+
async def test_conditional_context():
|
|
141
|
+
config = deepcopy(CONFIG)
|
|
142
|
+
config["context"] = AGENTS_CONTEXT_CONDITIONAL["context"]
|
|
143
|
+
config["generation"] = AGENTS_CONTEXT_CONDITIONAL["generation"]
|
|
144
|
+
question_memory = await arag_main(
|
|
145
|
+
agent_id="default",
|
|
146
|
+
internal_nua=False,
|
|
147
|
+
external_nua_api_key=NUA_KEY,
|
|
148
|
+
question="Where can I find good cardamom buns? give me as much info as possible",
|
|
149
|
+
config=config,
|
|
150
|
+
user_metadata={"fullname": "Carmen", "age": "99"},
|
|
151
|
+
loaded_modules=[
|
|
152
|
+
"hyperforge_conditional",
|
|
153
|
+
"hyperforge_static",
|
|
154
|
+
"hyperforge_summarize",
|
|
155
|
+
],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
assert (
|
|
159
|
+
question_memory.final_answer and "juno" in question_memory.final_answer.lower()
|
|
160
|
+
)
|
|
161
|
+
config["context"][0]["context"] = (
|
|
162
|
+
"Cardamom buns are a type of sweet roll that is flavored with cardamom. They are popular in many countries, including Sweden, Denmark, and Finland. They are often enjoyed with coffee or tea."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
question_memory = await arag_main(
|
|
166
|
+
agent_id="default",
|
|
167
|
+
internal_nua=False,
|
|
168
|
+
external_nua_api_key=NUA_KEY,
|
|
169
|
+
question="Where can I find good cardamom buns?",
|
|
170
|
+
config=config,
|
|
171
|
+
user_metadata={"fullname": "Carmen", "age": "99"},
|
|
172
|
+
loaded_modules=[
|
|
173
|
+
"hyperforge_conditional",
|
|
174
|
+
"hyperforge_static",
|
|
175
|
+
"hyperforge_summarize",
|
|
176
|
+
],
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
assert question_memory.final_answer
|
|
180
|
+
|
|
181
|
+
assert "juno" not in question_memory.final_answer.lower()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
AGENTS_FALLBACK = {
|
|
185
|
+
"context": [
|
|
186
|
+
{
|
|
187
|
+
"module": "context_conditional",
|
|
188
|
+
"title": "",
|
|
189
|
+
"prompt": "The question is about Atlas",
|
|
190
|
+
"then": [
|
|
191
|
+
{
|
|
192
|
+
"module": "static",
|
|
193
|
+
"title": "Static Agent",
|
|
194
|
+
"context": "Atlas is Carmen's dog. He is small and very cute but a bit moody. He was a rescue dog.",
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"else_": [
|
|
198
|
+
{
|
|
199
|
+
"module": "static",
|
|
200
|
+
"title": "Static Agent",
|
|
201
|
+
"context": "RAO is a product of Progress agentic RAG, it is a powerful agent orchestrator that you are using right now.",
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"fallback": {
|
|
205
|
+
"module": "static",
|
|
206
|
+
"title": "Fallback Agent",
|
|
207
|
+
"context": "You are probably asking about where to find the best cookies in the world. The best cookies are the ones made at home with love, but if you want to buy them, you can find great cookies in many bakeries around the world. Some famous ones are Levain Bakery in New York, Lune Croissanterie in Melbourne, and Maison Pichard in Paris.",
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
"generation": [
|
|
212
|
+
{"module": "summarize"},
|
|
213
|
+
],
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.asyncio
|
|
218
|
+
@pytest.mark.vcr(ignore_localhost=True)
|
|
219
|
+
async def test_conditional_fallback():
|
|
220
|
+
config = deepcopy(CONFIG)
|
|
221
|
+
config["context"] = AGENTS_FALLBACK["context"]
|
|
222
|
+
config["generation"] = AGENTS_FALLBACK["generation"]
|
|
223
|
+
question_memory = await arag_main(
|
|
224
|
+
agent_id="default",
|
|
225
|
+
internal_nua=False,
|
|
226
|
+
external_nua_api_key=NUA_KEY,
|
|
227
|
+
question="Where to have the best cookies?",
|
|
228
|
+
config=config,
|
|
229
|
+
user_metadata={"fullname": "Carmen", "age": "99"},
|
|
230
|
+
loaded_modules=[
|
|
231
|
+
"hyperforge_conditional",
|
|
232
|
+
"hyperforge_static",
|
|
233
|
+
"hyperforge_summarize",
|
|
234
|
+
],
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
keywords = ["cookies", "Levain Bakery", "Lune Croissanterie", "Maison Pichard"]
|
|
238
|
+
|
|
239
|
+
assert question_memory.final_answer
|
|
240
|
+
assert all(
|
|
241
|
+
keyword.lower() in question_memory.final_answer.lower() for keyword in keywords
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
AGENTS_FALLBACK_2 = {
|
|
246
|
+
"context": [
|
|
247
|
+
{
|
|
248
|
+
"module": "context_conditional",
|
|
249
|
+
"title": "",
|
|
250
|
+
"prompt": "The question is about Atlas",
|
|
251
|
+
"then": [
|
|
252
|
+
{
|
|
253
|
+
"module": "static",
|
|
254
|
+
"title": "Static Agent",
|
|
255
|
+
"context": "Atlas is Carmen's dog. He is small and very cute but a bit moody. He was a rescue dog.",
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
"else_": [
|
|
259
|
+
{
|
|
260
|
+
"module": "static",
|
|
261
|
+
"title": "Static Agent",
|
|
262
|
+
"context": "RAO is a product of Progress agentic RAG, it is a powerful agent orchestrator that you are using right now.",
|
|
263
|
+
"fallback": {
|
|
264
|
+
"module": "static",
|
|
265
|
+
"title": "Fallback Agent",
|
|
266
|
+
"context": "You are probably asking about where to find the best cookies in the world. The best cookies are the ones made at home with love, but if you want to buy them, you can find great cookies in many bakeries around the world. Some famous ones are Levain Bakery in New York, Lune Croissanterie in Melbourne, and Maison Pichard in Paris.",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
"generation": [
|
|
273
|
+
{"module": "summarize"},
|
|
274
|
+
],
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@pytest.mark.asyncio
|
|
279
|
+
@pytest.mark.vcr(ignore_localhost=True)
|
|
280
|
+
async def test_conditional_fallback_second_level():
|
|
281
|
+
config = deepcopy(CONFIG)
|
|
282
|
+
config["context"] = AGENTS_FALLBACK["context"]
|
|
283
|
+
config["generation"] = AGENTS_FALLBACK["generation"]
|
|
284
|
+
question_memory = await arag_main(
|
|
285
|
+
agent_id="default",
|
|
286
|
+
internal_nua=False,
|
|
287
|
+
external_nua_api_key=NUA_KEY,
|
|
288
|
+
question="Where to have the best cookies?",
|
|
289
|
+
config=config,
|
|
290
|
+
user_metadata={"fullname": "Carmen", "age": "99"},
|
|
291
|
+
loaded_modules=[
|
|
292
|
+
"hyperforge_conditional",
|
|
293
|
+
"hyperforge_static",
|
|
294
|
+
"hyperforge_summarize",
|
|
295
|
+
],
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
keywords = ["cookies", "Levain Bakery", "Lune Croissanterie", "Maison Pichard"]
|
|
299
|
+
|
|
300
|
+
assert question_memory.final_answer
|
|
301
|
+
assert all(
|
|
302
|
+
keyword.lower() in question_memory.final_answer.lower() for keyword in keywords
|
|
303
|
+
)
|