iointel 1.3.0__tar.gz → 1.3.2__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.
- iointel-1.3.2/.github/workflows/linting.yaml +16 -0
- {iointel-1.3.0 → iointel-1.3.2}/PKG-INFO +1 -5
- {iointel-1.3.0 → iointel-1.3.2}/iointel/__init__.py +4 -4
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/agents/agents_factory.py +10 -4
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/agents/tool_factory.py +3 -1
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/data_models/datamodels.py +1 -1
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/duckduckgo.py +2 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/utils.py +1 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agents.py +6 -4
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/chainables.py +2 -5
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/memory.py +2 -2
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/task.py +3 -7
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/graph_nodes.py +26 -26
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/helpers.py +6 -4
- iointel-1.3.2/iointel/src/utilities/rich.py +39 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/runners.py +10 -5
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/stages.py +13 -5
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/workflow.py +27 -12
- {iointel-1.3.0 → iointel-1.3.2}/pyproject.toml +0 -4
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_firecrawl_tool.py +1 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_r2r.py +3 -3
- iointel-1.3.2/tests/test_utilities.py +18 -0
- iointel-1.3.0/iointel/src/utilities/magic.py +0 -9
- iointel-1.3.0/iointel/src/utilities/rich.py +0 -6
- {iointel-1.3.0 → iointel-1.3.2}/.gitattributes +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/.github/workflows/codeql.yaml +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/.github/workflows/release.yaml +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/.github/workflows/tests.yaml +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/.gitignore +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/.pre-commit-config.yaml +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/LICENSE +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/Makefile +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/README.md +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/creds.env.example +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/client/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/client/client.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/agents/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/data_models/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/prompts/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/prompts/instructions.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/coinmarketcap.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/firecrawl.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/r2r.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/searxng.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/solscan.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/tools.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/wolfram.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/code_parsers/__init__.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/code_parsers/jscode_parser.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/code_parsers/pycode_parser.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/constants.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/decorators.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/exceptions.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/func_metadata.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/registries.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/test-runner +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/conftest.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_agents.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_client.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_stages.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tasks.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_coinmarketcap.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_duckduckgo.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_searxng.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_solscan.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_wolfram.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_basic_agent_tools.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_coinmarketcap_agent.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_crawl_agent.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_duckduckgo_agent.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_searxng_agent.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_wolframalpha_agent.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/tests/test_workflows.py +0 -0
- {iointel-1.3.0 → iointel-1.3.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iointel
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: A framework to create agents, tasks, and workflows.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: AI,Agents,LLM,framework,tools
|
|
@@ -10,19 +10,15 @@ Requires-Dist: duckduckgo-search==8.0.0
|
|
|
10
10
|
Requires-Dist: firecrawl-py>=2.2.0
|
|
11
11
|
Requires-Dist: firecrawl>=2.2.0
|
|
12
12
|
Requires-Dist: httpx
|
|
13
|
-
Requires-Dist: langchain-core>=0.3.28
|
|
14
|
-
Requires-Dist: langchain-openai>=0.2.14
|
|
15
13
|
Requires-Dist: pgvector>=0.4.0
|
|
16
14
|
Requires-Dist: pydantic-ai>=0.1.0
|
|
17
15
|
Requires-Dist: pydantic>=2.10.3
|
|
18
16
|
Requires-Dist: python-dotenv>=1.0.1
|
|
19
17
|
Requires-Dist: python-multipart>=0.0.20
|
|
20
18
|
Requires-Dist: pyyaml>=6.0.2
|
|
21
|
-
Requires-Dist: r2r>=3.4.4
|
|
22
19
|
Requires-Dist: rich>=13.9.4
|
|
23
20
|
Requires-Dist: sqlalchemy-utils>=0.41.2
|
|
24
21
|
Requires-Dist: sqlalchemy>=2.0.36
|
|
25
|
-
Requires-Dist: wolframalpha>=5.1.3
|
|
26
22
|
Provides-Extra: dev
|
|
27
23
|
Requires-Dist: pre-commit>=4.1.0; extra == 'dev'
|
|
28
24
|
Requires-Dist: pytest-asyncio>=0.21.2; extra == 'dev'
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
from .src.utilities.magic import (
|
|
2
|
-
UNUSED as UNUSED,
|
|
3
|
-
) # this performs some magic to stop prefect mucking with logging setup
|
|
4
1
|
from .src.agents import Agent
|
|
5
2
|
from .src.memory import AsyncMemory
|
|
6
3
|
from .src.workflow import Workflow
|
|
@@ -8,6 +5,7 @@ from .src.utilities.runners import run_agents, run_agents_stream
|
|
|
8
5
|
from .src.agent_methods.tools.wolfram import query_wolfram
|
|
9
6
|
from .src.agent_methods.data_models.datamodels import PersonaConfig
|
|
10
7
|
from .src.utilities.decorators import register_custom_task, register_tool
|
|
8
|
+
from .src.utilities.rich import pretty_output
|
|
11
9
|
|
|
12
10
|
from .src.code_parsers.pycode_parser import (
|
|
13
11
|
PythonModule,
|
|
@@ -42,7 +40,9 @@ __all__ = [
|
|
|
42
40
|
"PythonCodeGenerator",
|
|
43
41
|
###tools###
|
|
44
42
|
"query_wolfram",
|
|
43
|
+
###configuration###
|
|
44
|
+
"pretty_output",
|
|
45
45
|
]
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
__version__ = "1.3.
|
|
48
|
+
__version__ = "1.3.2"
|
|
@@ -18,15 +18,19 @@ def create_agent(params: AgentParams) -> Agent:
|
|
|
18
18
|
agent_data["tools"] = resolve_tools(params)
|
|
19
19
|
if output_type := agent_data.get("output_type"):
|
|
20
20
|
if isinstance(output_type, str):
|
|
21
|
-
agent_data["output_type"] = globals().get(output_type) or getattr(
|
|
21
|
+
agent_data["output_type"] = globals().get(output_type) or getattr(
|
|
22
|
+
__builtins__, output_type, output_type
|
|
23
|
+
)
|
|
22
24
|
return Agent(**agent_data)
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
def create_swarm(agents: list[AgentParams]|AgentSwarm):
|
|
27
|
+
def create_swarm(agents: list[AgentParams] | AgentSwarm):
|
|
26
28
|
raise NotImplementedError()
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
def agent_or_swarm(
|
|
31
|
+
def agent_or_swarm(
|
|
32
|
+
agent_obj: Agent | Sequence[Agent], store_creds: bool
|
|
33
|
+
) -> list[AgentParams] | AgentSwarm:
|
|
30
34
|
"""
|
|
31
35
|
Serializes an agent object into a list of AgentParams.
|
|
32
36
|
|
|
@@ -63,7 +67,9 @@ def agent_or_swarm(agent_obj: Agent|Sequence[Agent], store_creds: bool) -> list[
|
|
|
63
67
|
|
|
64
68
|
if isinstance(agent_obj, Sequence):
|
|
65
69
|
# group of agents not packed as a swarm
|
|
66
|
-
assert all(not hasattr(ag, "members") for ag in agent_obj),
|
|
70
|
+
assert all(not hasattr(ag, "members") for ag in agent_obj), (
|
|
71
|
+
"Nested swarms not allowed"
|
|
72
|
+
)
|
|
67
73
|
return [make_params(ag) for ag in agent_obj]
|
|
68
74
|
if hasattr(agent_obj, "api_key"):
|
|
69
75
|
# Individual agent.
|
|
@@ -97,7 +97,9 @@ def resolve_tools(params: AgentParams) -> List[Optional[Callable]]:
|
|
|
97
97
|
logger.debug(
|
|
98
98
|
f"Tool '{tool_obj.name}' found in TOOLS_REGISTRY under the custom name '{registered_tool_name}'."
|
|
99
99
|
)
|
|
100
|
-
resolved_tools.append(
|
|
100
|
+
resolved_tools.append(
|
|
101
|
+
registered_tool.model_copy(update={"fn_self": tool_obj.fn_self})
|
|
102
|
+
)
|
|
101
103
|
continue
|
|
102
104
|
else:
|
|
103
105
|
logger.warning(
|
|
@@ -406,7 +406,7 @@ class AgentParams(BaseModel):
|
|
|
406
406
|
description="Context to be passed to the agent.",
|
|
407
407
|
)
|
|
408
408
|
# memories: Optional[list[Memory]] = Field(default_factory=list)
|
|
409
|
-
memory: Optional[AsyncMemory] =
|
|
409
|
+
memory: Optional[AsyncMemory] = None
|
|
410
410
|
|
|
411
411
|
model_settings: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
|
412
412
|
output_type: Optional[Any] = str
|
|
@@ -90,6 +90,7 @@ class DuckDuckGoSearchAPIWrapper(BaseModel):
|
|
|
90
90
|
|
|
91
91
|
return results
|
|
92
92
|
|
|
93
|
+
|
|
93
94
|
@register_tool
|
|
94
95
|
def search_the_web(text: str, max_results: int = 10):
|
|
95
96
|
"""
|
|
@@ -99,6 +100,7 @@ def search_the_web(text: str, max_results: int = 10):
|
|
|
99
100
|
"""
|
|
100
101
|
return DuckDuckGoSearchAPIWrapper().results(text, max_results=max_results)
|
|
101
102
|
|
|
103
|
+
|
|
102
104
|
@register_tool
|
|
103
105
|
async def search_the_web_async(text: str, max_results: int = 10):
|
|
104
106
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .memory import AsyncMemory
|
|
2
2
|
from .agent_methods.data_models.datamodels import PersonaConfig, Tool
|
|
3
|
-
from .utilities.rich import console
|
|
3
|
+
from .utilities.rich import console, pretty_output
|
|
4
4
|
from .utilities.constants import get_api_url, get_base_model, get_api_key
|
|
5
5
|
from .utilities.registries import TOOLS_REGISTRY
|
|
6
6
|
from .utilities.helpers import supports_tool_choice_required
|
|
@@ -222,13 +222,15 @@ class Agent(BaseModel):
|
|
|
222
222
|
def add_tool(self, tool):
|
|
223
223
|
registered_tool = self._get_registered_tool(tool)
|
|
224
224
|
self.tools += [registered_tool]
|
|
225
|
-
self._runner._register_tool(
|
|
225
|
+
self._runner._register_tool(
|
|
226
|
+
PatchedValidatorTool(registered_tool.get_wrapped_fn())
|
|
227
|
+
)
|
|
226
228
|
|
|
227
229
|
async def run(
|
|
228
230
|
self,
|
|
229
231
|
query: str,
|
|
230
232
|
conversation_id: Optional[str] = None,
|
|
231
|
-
pretty=
|
|
233
|
+
pretty=None,
|
|
232
234
|
message_history_limit=100,
|
|
233
235
|
**kwargs,
|
|
234
236
|
):
|
|
@@ -279,7 +281,7 @@ class Agent(BaseModel):
|
|
|
279
281
|
await self.memory.store_run_history(conversation_id, result)
|
|
280
282
|
except Exception as e:
|
|
281
283
|
print("Error storing run history:", e)
|
|
282
|
-
if pretty:
|
|
284
|
+
if pretty or (pretty is None and pretty_output.is_enabled):
|
|
283
285
|
task_header = Text(
|
|
284
286
|
f" Objective: {query} ", style="bold white on dark_green"
|
|
285
287
|
)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from typing import List, Optional, Dict
|
|
2
2
|
|
|
3
3
|
# from .task import CUSTOM_WORKFLOW_REGISTRY
|
|
4
|
-
from .agent_methods.tools.tools import between
|
|
5
4
|
from .utilities.runners import run_agents
|
|
6
5
|
from .utilities.decorators import register_custom_task
|
|
7
6
|
from .utilities.registries import CHAINABLE_METHODS, CUSTOM_WORKFLOW_REGISTRY
|
|
@@ -97,6 +96,7 @@ def execute_summarize_text(
|
|
|
97
96
|
)
|
|
98
97
|
return summary.execute()
|
|
99
98
|
|
|
99
|
+
|
|
100
100
|
@register_custom_task("sentiment")
|
|
101
101
|
async def execute_sentiment(
|
|
102
102
|
task_metadata: dict, objective: str, agents: List[Agent], execution_metadata: dict
|
|
@@ -152,14 +152,12 @@ def execute_extract_entities(
|
|
|
152
152
|
return extracted.execute()
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
|
|
156
155
|
@register_custom_task("translate_text")
|
|
157
156
|
def execute_translate_text(
|
|
158
157
|
task_metadata: dict, objective: str, agents: List[Agent], execution_metadata: dict
|
|
159
158
|
):
|
|
160
159
|
target_lang = task_metadata["target_language"]
|
|
161
160
|
from ..client.client import translate_text_task
|
|
162
|
-
from .agent_methods.data_models.datamodels import TranslationResult
|
|
163
161
|
|
|
164
162
|
client_mode = execution_metadata.get("client_mode", False)
|
|
165
163
|
if client_mode:
|
|
@@ -176,7 +174,6 @@ def execute_translate_text(
|
|
|
176
174
|
return result
|
|
177
175
|
|
|
178
176
|
|
|
179
|
-
|
|
180
177
|
@register_custom_task("classify")
|
|
181
178
|
def execute_classify(
|
|
182
179
|
task_metadata: dict, objective: str, agents: List[Agent], execution_metadata: dict
|
|
@@ -193,7 +190,7 @@ def execute_classify(
|
|
|
193
190
|
objective=f"""Take this text: {objective}
|
|
194
191
|
|
|
195
192
|
Classify it into the appropriate category.
|
|
196
|
-
Category must be one of: {
|
|
193
|
+
Category must be one of: {", ".join(classify_by)}.
|
|
197
194
|
Return only the determined category, omit the thoughts.""",
|
|
198
195
|
agents=agents,
|
|
199
196
|
output_type=str,
|
|
@@ -13,8 +13,8 @@ from pydantic_ai.messages import (
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
from datetime import datetime, timezone
|
|
16
|
-
from sqlalchemy import
|
|
17
|
-
from sqlalchemy.orm import
|
|
16
|
+
from sqlalchemy import Column, String, Text, DateTime, select
|
|
17
|
+
from sqlalchemy.orm import declarative_base
|
|
18
18
|
|
|
19
19
|
# For async support
|
|
20
20
|
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
from typing import List, Dict, Any
|
|
2
|
-
import asyncio
|
|
1
|
+
from typing import List, Dict, Any
|
|
3
2
|
|
|
4
3
|
from .agents import Agent
|
|
5
4
|
from .utilities.helpers import LazyCaller
|
|
@@ -86,9 +85,7 @@ class Task:
|
|
|
86
85
|
)
|
|
87
86
|
)
|
|
88
87
|
|
|
89
|
-
async def chain_runs(
|
|
90
|
-
self, run_specs: List[Dict[str, Any]]
|
|
91
|
-
) -> List[Any]:
|
|
88
|
+
async def chain_runs(self, run_specs: List[Dict[str, Any]]) -> List[Any]:
|
|
92
89
|
"""
|
|
93
90
|
Execute multiple runs in sequence. Each element in run_specs is a dict containing parameters for `self.run`.
|
|
94
91
|
The output of one run can be fed into the context of the next run if desired.
|
|
@@ -134,8 +131,7 @@ class Task:
|
|
|
134
131
|
**{
|
|
135
132
|
k: v
|
|
136
133
|
for k, v in spec.items()
|
|
137
|
-
if k
|
|
138
|
-
not in ["objective", "agents", "context", "conversation_id"]
|
|
134
|
+
if k not in ["objective", "agents", "context", "conversation_id"]
|
|
139
135
|
},
|
|
140
136
|
)
|
|
141
137
|
results.append(result)
|
|
@@ -9,7 +9,7 @@ from dataclasses import dataclass, field, make_dataclass
|
|
|
9
9
|
class WorkflowState:
|
|
10
10
|
initial_text: str = ""
|
|
11
11
|
conversation_id: str = ""
|
|
12
|
-
results: Dict[str, Any] = field(default_factory=dict)
|
|
12
|
+
results: Dict[str, Any] = field(default_factory=dict)
|
|
13
13
|
|
|
14
14
|
@classmethod
|
|
15
15
|
def get_id(cls) -> str:
|
|
@@ -23,17 +23,15 @@ class TaskNode(BaseNode[WorkflowState]):
|
|
|
23
23
|
default_agents: list
|
|
24
24
|
conversation_id: str
|
|
25
25
|
unique_id: str = field(init=False)
|
|
26
|
-
next_task: Optional[
|
|
26
|
+
next_task: Optional["TaskNode"] = None
|
|
27
27
|
|
|
28
28
|
def __post_init__(self):
|
|
29
29
|
self.unique_id = (
|
|
30
|
-
self.task.get("task_id")
|
|
31
|
-
or self.task.get("name")
|
|
32
|
-
or str(uuid.uuid4())
|
|
30
|
+
self.task.get("task_id") or self.task.get("name") or str(uuid.uuid4())
|
|
33
31
|
)
|
|
34
32
|
|
|
35
33
|
self.conversation_id = self.conversation_id
|
|
36
|
-
|
|
34
|
+
|
|
37
35
|
@classmethod
|
|
38
36
|
def get_node_def(cls, local_ns: Optional[Dict[str, Any]] = None):
|
|
39
37
|
next_edges: dict[str, Any] = {}
|
|
@@ -49,11 +47,16 @@ class TaskNode(BaseNode[WorkflowState]):
|
|
|
49
47
|
returns_base_node=True,
|
|
50
48
|
)
|
|
51
49
|
|
|
52
|
-
async def run(
|
|
53
|
-
|
|
50
|
+
async def run(
|
|
51
|
+
self, context: GraphRunContext[WorkflowState]
|
|
52
|
+
) -> "TaskNode" | End[WorkflowState]:
|
|
53
|
+
from ..workflow import (
|
|
54
|
+
Workflow,
|
|
55
|
+
_get_task_key,
|
|
56
|
+
) # import must happen here, or circular issue occurs
|
|
54
57
|
|
|
55
58
|
wf = Workflow()
|
|
56
|
-
state = context.state
|
|
59
|
+
state = context.state
|
|
57
60
|
|
|
58
61
|
if not state.conversation_id:
|
|
59
62
|
state.conversation_id = self.conversation_id or str(uuid.uuid4())
|
|
@@ -64,27 +67,25 @@ class TaskNode(BaseNode[WorkflowState]):
|
|
|
64
67
|
task_key = _get_task_key(self.task)
|
|
65
68
|
|
|
66
69
|
result = await wf.run_task(
|
|
67
|
-
self.task,
|
|
68
|
-
self.default_text,
|
|
69
|
-
self.default_agents,
|
|
70
|
-
self.conversation_id
|
|
70
|
+
self.task, self.default_text, self.default_agents, self.conversation_id
|
|
71
71
|
)
|
|
72
72
|
|
|
73
|
-
state.results[task_key] =
|
|
73
|
+
state.results[task_key] = (
|
|
74
|
+
result.get("data", result) if isinstance(result, dict) else result
|
|
75
|
+
)
|
|
74
76
|
return self.next_task if self.next_task else End(state)
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
def make_task_node(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
conv_id: str) -> type[BaseNode]:
|
|
79
|
+
def make_task_node(
|
|
80
|
+
task: dict, default_text: str, default_agents: list, conv_id: str
|
|
81
|
+
) -> type[BaseNode]:
|
|
81
82
|
"""
|
|
82
83
|
Return a brand‑new subclass of TaskNode whose `get_id()` is unique.
|
|
83
84
|
The task parameters are stored on the *class* so the graph can later
|
|
84
85
|
instantiate it.
|
|
85
86
|
"""
|
|
86
|
-
uid
|
|
87
|
-
cls_name
|
|
87
|
+
uid = task.get("task_id") or task.get("name") or str(uuid.uuid4())
|
|
88
|
+
cls_name = f"{task.get('name', 'task')}_{uid}".replace("-", "_")
|
|
88
89
|
|
|
89
90
|
# Every field you’d pass to TaskNode’s __init__ becomes a *class*
|
|
90
91
|
# attribute; pydantic‑graph will pass them to the generated __init__.
|
|
@@ -92,19 +93,18 @@ def make_task_node(task: dict,
|
|
|
92
93
|
# data‑carrying fields are already inherited from the base ``TaskNode``
|
|
93
94
|
# we can simply pass an **empty list**. (Leaving it out raises the
|
|
94
95
|
# ``TypeError: make_dataclass() missing 1 required positional argument: 'fields'``)
|
|
95
|
-
|
|
96
|
+
|
|
96
97
|
NewNode = make_dataclass(
|
|
97
98
|
cls_name,
|
|
98
|
-
[],
|
|
99
|
+
[], # <‑‑ empty ``fields`` list satisfies the API
|
|
99
100
|
bases=(TaskNode,),
|
|
100
101
|
namespace={
|
|
101
102
|
# class‑level constants → become default values for the subclass
|
|
102
|
-
"task"
|
|
103
|
-
"default_text"
|
|
103
|
+
"task": task,
|
|
104
|
+
"default_text": default_text,
|
|
104
105
|
"default_agents": default_agents,
|
|
105
106
|
"conversation_id": conv_id,
|
|
106
|
-
"_node_id"
|
|
107
|
-
|
|
107
|
+
"_node_id": cls_name, # used by the custom ``get_id`` below
|
|
108
108
|
# Provide a per‑class ``get_id`` so every generated subclass
|
|
109
109
|
# advertises a unique node‑ID to pydantic‑graph
|
|
110
110
|
"get_id": classmethod(lambda cls: cls._node_id),
|
|
@@ -17,6 +17,7 @@ def make_logger(name: str, level: str = "INFO"):
|
|
|
17
17
|
|
|
18
18
|
logger = make_logger(__name__)
|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
class LazyCaller(BaseModel):
|
|
21
22
|
func: Callable
|
|
22
23
|
args: tuple[Any, ...] = ()
|
|
@@ -77,13 +78,14 @@ class LazyCaller(BaseModel):
|
|
|
77
78
|
"""Only serialize the name, not the problematic object"""
|
|
78
79
|
return {"name": self.name}
|
|
79
80
|
|
|
81
|
+
|
|
80
82
|
def supports_tool_choice_required(model_name: str) -> bool:
|
|
81
83
|
"""Temp hack fix to check if the model supports tool choice required."""
|
|
82
84
|
# TODO: Remove this once we have a better way to check if the model supports tool choice required.
|
|
83
85
|
model_name = model_name.lower()
|
|
84
86
|
return (
|
|
85
|
-
model_name.startswith("gpt-")
|
|
86
|
-
model_name.startswith("openai/")
|
|
87
|
-
"gpt" in model_name
|
|
88
|
-
model_name == "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
|
|
87
|
+
model_name.startswith("gpt-")
|
|
88
|
+
or model_name.startswith("openai/")
|
|
89
|
+
or "gpt" in model_name
|
|
90
|
+
or model_name == "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
|
|
89
91
|
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.theme import Theme
|
|
4
|
+
|
|
5
|
+
custom_theme = Theme({"electric_blue": "#7DF9FF"})
|
|
6
|
+
|
|
7
|
+
console = Console(theme=custom_theme)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PrettyController:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self._enabled = True
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def is_enabled(self):
|
|
16
|
+
return self._enabled
|
|
17
|
+
|
|
18
|
+
def _onoff(self, enabled: bool):
|
|
19
|
+
@contextmanager
|
|
20
|
+
def inner(this=self):
|
|
21
|
+
old_value, this._enabled = this._enabled, enabled
|
|
22
|
+
try:
|
|
23
|
+
yield this
|
|
24
|
+
finally:
|
|
25
|
+
this._enabled = old_value
|
|
26
|
+
|
|
27
|
+
return inner()
|
|
28
|
+
|
|
29
|
+
def enabled(self):
|
|
30
|
+
return self._onoff(True)
|
|
31
|
+
|
|
32
|
+
def disabled(self):
|
|
33
|
+
return self._onoff(False)
|
|
34
|
+
|
|
35
|
+
def global_onoff(self, enabled: bool):
|
|
36
|
+
self._enabled = enabled
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
pretty_output = PrettyController()
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
from typing import Sequence
|
|
2
2
|
|
|
3
|
-
from ..agents import Agent
|
|
4
3
|
from .helpers import LazyCaller
|
|
5
4
|
from ..task import Task
|
|
6
5
|
from ..agent_methods.data_models.datamodels import TaskDefinition
|
|
7
6
|
from ..agent_methods.agents.agents_factory import agent_or_swarm
|
|
8
7
|
|
|
8
|
+
|
|
9
9
|
def _to_task_definition(
|
|
10
|
-
objective: str,
|
|
10
|
+
objective: str,
|
|
11
|
+
agents=None,
|
|
12
|
+
conversation_id=None,
|
|
13
|
+
name=None,
|
|
14
|
+
task_id="some_default",
|
|
15
|
+
context=None,
|
|
16
|
+
**kwargs,
|
|
11
17
|
) -> TaskDefinition:
|
|
12
18
|
"""
|
|
13
19
|
Helper that merges the user’s provided fields into a TaskDefinition.
|
|
@@ -42,9 +48,8 @@ async def _run_stream(objective: str, output_type=None, **all_kwargs):
|
|
|
42
48
|
async def _run(objective: str, output_type=None, **all_kwargs):
|
|
43
49
|
definition = _to_task_definition(objective, **all_kwargs)
|
|
44
50
|
agents = definition.agents or []
|
|
45
|
-
return await Task(agents=agents).run(
|
|
46
|
-
|
|
47
|
-
)
|
|
51
|
+
return await Task(agents=agents).run(definition=definition, output_type=output_type)
|
|
52
|
+
|
|
48
53
|
|
|
49
54
|
async def _unpack(func, *args, **kwargs):
|
|
50
55
|
result = await (await func(*args, **kwargs)).execute()
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import concurrent.futures
|
|
3
2
|
from .runners import run_agents
|
|
4
3
|
from typing import Any, get_type_hints
|
|
5
4
|
|
|
@@ -52,13 +51,20 @@ async def _run_sequential(
|
|
|
52
51
|
) -> list:
|
|
53
52
|
results = []
|
|
54
53
|
for substage in stage.stages:
|
|
55
|
-
results.append(
|
|
54
|
+
results.append(
|
|
55
|
+
await execute_stage(substage, agents, task_metadata, default_text)
|
|
56
|
+
)
|
|
56
57
|
return results
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
@register_stage_runner
|
|
60
|
-
async def _run_parallel(
|
|
61
|
-
|
|
61
|
+
async def _run_parallel(
|
|
62
|
+
stage: ParallelStage, agents, task_metadata, default_text
|
|
63
|
+
) -> list:
|
|
64
|
+
futures = [
|
|
65
|
+
execute_stage(substage, agents, task_metadata, default_text)
|
|
66
|
+
for substage in stage.stages
|
|
67
|
+
]
|
|
62
68
|
return await asyncio.gather(*futures)
|
|
63
69
|
|
|
64
70
|
|
|
@@ -74,7 +80,9 @@ async def _run_while(stage: WhileStage, agents, task_metadata, default_text) ->
|
|
|
74
80
|
|
|
75
81
|
|
|
76
82
|
@register_stage_runner
|
|
77
|
-
async def _run_fallback(
|
|
83
|
+
async def _run_fallback(
|
|
84
|
+
stage: FallbackStage, agents, task_metadata, default_text
|
|
85
|
+
) -> any:
|
|
78
86
|
try:
|
|
79
87
|
return await execute_stage(stage.primary, agents, task_metadata, default_text)
|
|
80
88
|
except Exception as e:
|
|
@@ -5,7 +5,11 @@ import yaml
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
|
|
8
|
-
from .agent_methods.agents.agents_factory import
|
|
8
|
+
from .agent_methods.agents.agents_factory import (
|
|
9
|
+
agent_or_swarm,
|
|
10
|
+
create_agent,
|
|
11
|
+
create_swarm,
|
|
12
|
+
)
|
|
9
13
|
from .agent_methods.data_models.datamodels import (
|
|
10
14
|
WorkflowDefinition,
|
|
11
15
|
TaskDefinition,
|
|
@@ -24,6 +28,7 @@ from .utilities.stages import (
|
|
|
24
28
|
WhileStage,
|
|
25
29
|
FallbackStage,
|
|
26
30
|
)
|
|
31
|
+
from .utilities.rich import pretty_output
|
|
27
32
|
|
|
28
33
|
from .utilities.graph_nodes import WorkflowState, TaskNode, make_task_node
|
|
29
34
|
from pydantic_graph import Graph, End, GraphRunContext
|
|
@@ -39,6 +44,7 @@ from rich.progress import (
|
|
|
39
44
|
|
|
40
45
|
logger = make_logger(__name__)
|
|
41
46
|
|
|
47
|
+
|
|
42
48
|
def _get_task_key(task: dict) -> str:
|
|
43
49
|
return (
|
|
44
50
|
task.get("task_id")
|
|
@@ -47,6 +53,7 @@ def _get_task_key(task: dict) -> str:
|
|
|
47
53
|
or "task"
|
|
48
54
|
)
|
|
49
55
|
|
|
56
|
+
|
|
50
57
|
class Workflow:
|
|
51
58
|
"""
|
|
52
59
|
Manages a chain of tasks and runs them sequentially.
|
|
@@ -120,7 +127,7 @@ class Workflow:
|
|
|
120
127
|
execution_metadata["conversation_id"] = task["conversation_id"]
|
|
121
128
|
if conversation_id:
|
|
122
129
|
execution_metadata["conversation_id"] = conversation_id
|
|
123
|
-
client_mode = execution_metadata.get("client_mode", self.client_mode)
|
|
130
|
+
# client_mode = execution_metadata.get("client_mode", self.client_mode)
|
|
124
131
|
|
|
125
132
|
if stage_defs := execution_metadata.get("stages"):
|
|
126
133
|
stage_objects = []
|
|
@@ -219,7 +226,11 @@ class Workflow:
|
|
|
219
226
|
result = await result.execute()
|
|
220
227
|
return result
|
|
221
228
|
|
|
222
|
-
async def execute_graph_streaming(
|
|
229
|
+
async def execute_graph_streaming(
|
|
230
|
+
self, graph, initial_state, pretty: Optional[bool] = None
|
|
231
|
+
):
|
|
232
|
+
if pretty is None:
|
|
233
|
+
pretty = pretty_output.is_enabled
|
|
223
234
|
nodes = list(graph.node_defs.values())
|
|
224
235
|
total_tasks = len(nodes)
|
|
225
236
|
|
|
@@ -234,6 +245,7 @@ class Workflow:
|
|
|
234
245
|
TaskProgressColumn(),
|
|
235
246
|
TimeElapsedColumn(),
|
|
236
247
|
transient=True,
|
|
248
|
+
disable=not pretty,
|
|
237
249
|
) as progress:
|
|
238
250
|
task_progress = progress.add_task(
|
|
239
251
|
"[cyan]Executing Tasks...", total=total_tasks
|
|
@@ -289,7 +301,11 @@ class Workflow:
|
|
|
289
301
|
|
|
290
302
|
return state
|
|
291
303
|
|
|
292
|
-
async def execute_graph(
|
|
304
|
+
async def execute_graph(
|
|
305
|
+
self, graph: Graph, initial_state: WorkflowState, pretty: Optional[bool] = None
|
|
306
|
+
):
|
|
307
|
+
if pretty is None:
|
|
308
|
+
pretty = pretty_output.is_enabled
|
|
293
309
|
nodes = list(graph.node_defs.values())
|
|
294
310
|
total_tasks = len(nodes)
|
|
295
311
|
|
|
@@ -300,6 +316,7 @@ class Workflow:
|
|
|
300
316
|
TaskProgressColumn(),
|
|
301
317
|
TimeElapsedColumn(),
|
|
302
318
|
transient=True,
|
|
319
|
+
disable=not pretty,
|
|
303
320
|
) as progress:
|
|
304
321
|
task_progress = progress.add_task(
|
|
305
322
|
"[cyan]Executing Tasks...", total=total_tasks
|
|
@@ -344,14 +361,12 @@ class Workflow:
|
|
|
344
361
|
|
|
345
362
|
# pass the class objects to Graph
|
|
346
363
|
return Graph(
|
|
347
|
-
nodes
|
|
348
|
-
state_type
|
|
349
|
-
run_end_type
|
|
364
|
+
nodes=tuple(node_classes),
|
|
365
|
+
state_type=WorkflowState,
|
|
366
|
+
run_end_type=WorkflowState,
|
|
350
367
|
)
|
|
351
368
|
|
|
352
|
-
async def run_tasks(
|
|
353
|
-
self, conversation_id: Optional[str] = None, **kwargs
|
|
354
|
-
) -> dict:
|
|
369
|
+
async def run_tasks(self, conversation_id: Optional[str] = None, **kwargs) -> dict:
|
|
355
370
|
if not conversation_id:
|
|
356
371
|
conversation_id = str(uuid.uuid4())
|
|
357
372
|
|
|
@@ -426,8 +441,8 @@ class Workflow:
|
|
|
426
441
|
"memory": True,
|
|
427
442
|
"memories": True,
|
|
428
443
|
"agents": {"__all__": {"memory": True}},
|
|
429
|
-
"tasks": {"__all__": {"agents": {"__all__": {"memory": True}}}}
|
|
430
|
-
}
|
|
444
|
+
"tasks": {"__all__": {"agents": {"__all__": {"memory": True}}}},
|
|
445
|
+
},
|
|
431
446
|
)
|
|
432
447
|
yaml_str = yaml.safe_dump(wf_dict, sort_keys=False)
|
|
433
448
|
if file_path:
|
|
@@ -11,10 +11,6 @@ dependencies = [
|
|
|
11
11
|
"pydantic-ai>=0.1.0",
|
|
12
12
|
"pyyaml>=6.0.2",
|
|
13
13
|
"duckduckgo-search==8.0.0",
|
|
14
|
-
"langchain-core>=0.3.28",
|
|
15
|
-
"langchain-openai>=0.2.14",
|
|
16
|
-
"r2r>=3.4.4",
|
|
17
|
-
"wolframalpha>=5.1.3",
|
|
18
14
|
"pgvector>=0.4.0",
|
|
19
15
|
"sqlalchemy-utils>=0.41.2",
|
|
20
16
|
"sqlalchemy>=2.0.36",
|
|
@@ -43,7 +43,7 @@ async def test_rag_search(r2r_client: R2RClient, sample_document: uuid.UUID):
|
|
|
43
43
|
result = await r2r_client.rag_search("What do you know about samples?")
|
|
44
44
|
assert result, "Expected non-empty RAG result"
|
|
45
45
|
assert result["answer"], "Expected non-empty RAG answer"
|
|
46
|
-
assert (
|
|
47
|
-
"
|
|
48
|
-
)
|
|
46
|
+
assert "database" in result["answer"], (
|
|
47
|
+
"Sample document has to be referenced in RAG answer"
|
|
48
|
+
)
|
|
49
49
|
assert result["citations"], "Expected to see at least one citation"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from iointel import pretty_output
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_pretty_context():
|
|
5
|
+
assert pretty_output.is_enabled, "Pretty output not enabled"
|
|
6
|
+
with pretty_output.disabled() as ctx:
|
|
7
|
+
assert not pretty_output.is_enabled, "Pretty output not disabled"
|
|
8
|
+
assert ctx is pretty_output, "Wrong context manager instance"
|
|
9
|
+
assert pretty_output.is_enabled, "Pretty output not enabled"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_pretty_global():
|
|
13
|
+
assert pretty_output.is_enabled, "Pretty output not enabled"
|
|
14
|
+
try:
|
|
15
|
+
pretty_output.global_onoff(False)
|
|
16
|
+
assert not pretty_output.is_enabled, "Pretty output not disabled"
|
|
17
|
+
finally:
|
|
18
|
+
pretty_output.global_onoff(True)
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
# turn off most prefect log messages, as they aren't useful
|
|
5
|
-
# to end user, but might hurt UX for inexperienced ones
|
|
6
|
-
for env_name in ("PREFECT_LOGGING_SERVER_LEVEL", "PREFECT_LOGGING_LEVEL"):
|
|
7
|
-
os.environ[env_name] = os.environ.get(env_name, "CRITICAL")
|
|
8
|
-
|
|
9
|
-
UNUSED = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|