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.
Files changed (77) hide show
  1. iointel-1.3.2/.github/workflows/linting.yaml +16 -0
  2. {iointel-1.3.0 → iointel-1.3.2}/PKG-INFO +1 -5
  3. {iointel-1.3.0 → iointel-1.3.2}/iointel/__init__.py +4 -4
  4. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/agents/agents_factory.py +10 -4
  5. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/agents/tool_factory.py +3 -1
  6. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/data_models/datamodels.py +1 -1
  7. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/duckduckgo.py +2 -0
  8. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/utils.py +1 -0
  9. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agents.py +6 -4
  10. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/chainables.py +2 -5
  11. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/memory.py +2 -2
  12. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/task.py +3 -7
  13. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/graph_nodes.py +26 -26
  14. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/helpers.py +6 -4
  15. iointel-1.3.2/iointel/src/utilities/rich.py +39 -0
  16. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/runners.py +10 -5
  17. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/stages.py +13 -5
  18. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/workflow.py +27 -12
  19. {iointel-1.3.0 → iointel-1.3.2}/pyproject.toml +0 -4
  20. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_firecrawl_tool.py +1 -0
  21. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_r2r.py +3 -3
  22. iointel-1.3.2/tests/test_utilities.py +18 -0
  23. iointel-1.3.0/iointel/src/utilities/magic.py +0 -9
  24. iointel-1.3.0/iointel/src/utilities/rich.py +0 -6
  25. {iointel-1.3.0 → iointel-1.3.2}/.gitattributes +0 -0
  26. {iointel-1.3.0 → iointel-1.3.2}/.github/workflows/codeql.yaml +0 -0
  27. {iointel-1.3.0 → iointel-1.3.2}/.github/workflows/release.yaml +0 -0
  28. {iointel-1.3.0 → iointel-1.3.2}/.github/workflows/tests.yaml +0 -0
  29. {iointel-1.3.0 → iointel-1.3.2}/.gitignore +0 -0
  30. {iointel-1.3.0 → iointel-1.3.2}/.pre-commit-config.yaml +0 -0
  31. {iointel-1.3.0 → iointel-1.3.2}/LICENSE +0 -0
  32. {iointel-1.3.0 → iointel-1.3.2}/Makefile +0 -0
  33. {iointel-1.3.0 → iointel-1.3.2}/README.md +0 -0
  34. {iointel-1.3.0 → iointel-1.3.2}/creds.env.example +0 -0
  35. {iointel-1.3.0 → iointel-1.3.2}/iointel/client/__init__.py +0 -0
  36. {iointel-1.3.0 → iointel-1.3.2}/iointel/client/client.py +0 -0
  37. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/__init__.py +0 -0
  38. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/__init__.py +0 -0
  39. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/agents/__init__.py +0 -0
  40. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/data_models/__init__.py +0 -0
  41. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/prompts/__init__.py +0 -0
  42. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/prompts/instructions.py +0 -0
  43. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/__init__.py +0 -0
  44. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/coinmarketcap.py +0 -0
  45. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/firecrawl.py +0 -0
  46. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/r2r.py +0 -0
  47. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/searxng.py +0 -0
  48. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/solscan.py +0 -0
  49. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/tools.py +0 -0
  50. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/agent_methods/tools/wolfram.py +0 -0
  51. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/code_parsers/__init__.py +0 -0
  52. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/code_parsers/jscode_parser.py +0 -0
  53. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/code_parsers/pycode_parser.py +0 -0
  54. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/constants.py +0 -0
  55. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/decorators.py +0 -0
  56. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/exceptions.py +0 -0
  57. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/func_metadata.py +0 -0
  58. {iointel-1.3.0 → iointel-1.3.2}/iointel/src/utilities/registries.py +0 -0
  59. {iointel-1.3.0 → iointel-1.3.2}/test-runner +0 -0
  60. {iointel-1.3.0 → iointel-1.3.2}/tests/conftest.py +0 -0
  61. {iointel-1.3.0 → iointel-1.3.2}/tests/test_agents.py +0 -0
  62. {iointel-1.3.0 → iointel-1.3.2}/tests/test_client.py +0 -0
  63. {iointel-1.3.0 → iointel-1.3.2}/tests/test_stages.py +0 -0
  64. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tasks.py +0 -0
  65. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_coinmarketcap.py +0 -0
  66. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_duckduckgo.py +0 -0
  67. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_searxng.py +0 -0
  68. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_solscan.py +0 -0
  69. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_isolated/test_wolfram.py +0 -0
  70. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_basic_agent_tools.py +0 -0
  71. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_coinmarketcap_agent.py +0 -0
  72. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_crawl_agent.py +0 -0
  73. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_duckduckgo_agent.py +0 -0
  74. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_searxng_agent.py +0 -0
  75. {iointel-1.3.0 → iointel-1.3.2}/tests/test_tools_with_agents/test_wolframalpha_agent.py +0 -0
  76. {iointel-1.3.0 → iointel-1.3.2}/tests/test_workflows.py +0 -0
  77. {iointel-1.3.0 → iointel-1.3.2}/uv.lock +0 -0
@@ -0,0 +1,16 @@
1
+ name: Linting
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main, dev]
7
+
8
+ jobs:
9
+ linting:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout
13
+ uses: actions/checkout@v3
14
+
15
+ - name: Lint
16
+ uses: pre-commit/action@v3.0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iointel
3
- Version: 1.3.0
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.0"
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(__builtins__, output_type, output_type)
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(agent_obj: Agent|Sequence[Agent], store_creds: bool) -> list[AgentParams]|AgentSwarm:
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), "Nested swarms not allowed"
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(registered_tool.model_copy(update={"fn_self": tool_obj.fn_self}))
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] = Field(default_factory=list)
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
  """
@@ -2,6 +2,7 @@ from datetime import datetime
2
2
 
3
3
  from iointel.src.utilities.decorators import register_tool
4
4
 
5
+
5
6
  @register_tool
6
7
  def what_time_is_it():
7
8
  """
@@ -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(PatchedValidatorTool(registered_tool.get_wrapped_fn()))
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=True,
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: {', '.join(classify_by)}.
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 create_engine, Column, String, Text, DateTime, select
17
- from sqlalchemy.orm import sessionmaker, declarative_base
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, Optional
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['TaskNode'] = None
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(self, context: GraphRunContext[WorkflowState]) -> "TaskNode" | End[WorkflowState]:
53
- from ..workflow import Workflow, _get_task_key # import must happen here, or circular issue occurs
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] = result.get("data", result) if isinstance(result, dict) else result
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(task: dict,
78
- default_text: str,
79
- default_agents: list,
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 = task.get("task_id") or task.get("name") or str(uuid.uuid4())
87
- cls_name = f'{task.get("name","task")}_{uid}'.replace('-', '_')
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
- [], # <‑‑ empty ``fields`` list satisfies the API
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" : task,
103
- "default_text" : 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" : cls_name, # used by the custom ``get_id`` below
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-") or
86
- model_name.startswith("openai/") or
87
- "gpt" in model_name or
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, agents=None, conversation_id=None, name=None, task_id="some_default", context=None, **kwargs
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
- definition=definition, output_type=output_type
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(await execute_stage(substage, agents, task_metadata, default_text))
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(stage: ParallelStage, agents, task_metadata, default_text) -> list:
61
- futures = [execute_stage(substage, agents, task_metadata, default_text) for substage in stage.stages]
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(stage: FallbackStage, agents, task_metadata, default_text) -> any:
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 agent_or_swarm, create_agent, create_swarm
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(self, graph, initial_state):
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(self, graph: Graph, initial_state: WorkflowState):
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 = tuple(node_classes),
348
- state_type = WorkflowState,
349
- run_end_type = WorkflowState,
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",
@@ -2,5 +2,6 @@ from iointel.src.agent_methods.tools.firecrawl import Crawler
2
2
 
3
3
  crawler = Crawler()
4
4
 
5
+
5
6
  async def test_firecrawl():
6
7
  assert crawler.scrape_url(url="https://firecrawl.dev/")
@@ -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
- "database" in result["answer"]
48
- ), "Sample document has to be referenced in RAG answer"
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
@@ -1,6 +0,0 @@
1
- from rich.console import Console
2
- from rich.theme import Theme
3
-
4
- custom_theme = Theme({"electric_blue": "#7DF9FF"})
5
-
6
- console = Console(theme=custom_theme)
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