Skip to content

Commit 49c9647

Browse files
committed
refactor: updated docs to new wrapper API
2 parents 53986c2 + 1fb6eff commit 49c9647

17 files changed

Lines changed: 1543 additions & 77 deletions

File tree

.github/workflows/continuous_integration.yml

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ on: [push, pull_request]
33

44
jobs:
55
lint_and_format:
6-
runs-on: ubuntu-latest
6+
runs-on: ubuntu-latest
77
steps:
88
- name: Fetch code
99
uses: actions/checkout@v3
10-
# - name: Check linting
10+
# - name: Check linting
1111
# uses: astral-sh/ruff-action@v1
1212
# with:
1313
# args: "check"
@@ -19,27 +19,27 @@ jobs:
1919
needs: lint_and_format
2020
steps:
2121
- name: Fetch code
22-
uses: actions/checkout@v3
22+
uses: actions/checkout@v3
2323
- name: "Install dependecies"
2424
run: pip install bandit
2525
- name: "Check with bandit"
2626
run: bandit -r src/
2727
tests:
28-
runs-on: ubuntu-latest
28+
runs-on: ubuntu-latest
2929
needs: lint_and_format
3030
strategy:
3131
matrix:
3232
python-version: ['3.10', '3.11']
3333
steps:
3434
- name: Fetch code
35-
uses: actions/checkout@v3
36-
35+
uses: actions/checkout@v3
36+
3737
- name: Set up python ${{ matrix.python-version }}
3838
uses: actions/setup-python@v4
3939
with:
4040
python-version: ${{ matrix.python-version }}
4141
cache: 'pip'
42-
42+
4343
- name: Cache pip dependencies
4444
uses: actions/cache@v3
4545
with:
@@ -48,27 +48,20 @@ jobs:
4848
restore-keys: |
4949
${{ runner.os }}-pip-${{ matrix.python-version }}-
5050
${{ runner.os }}-pip-
51-
52-
- name: "Install graphviz"
53-
run: |
54-
sudo apt-get update
55-
sudo apt-get install -y graphviz graphviz-dev
56-
shell: bash
57-
51+
5852
- name: "Create virtual environment"
5953
run: |
6054
python -m venv .venv
6155
shell: bash
62-
56+
6357
- name: "Install dependencies"
6458
run: |
6559
source .venv/bin/activate
6660
pip install -e ".[dev]"
67-
pip install pygraphviz
6861
shell: bash
69-
62+
7063
- name: Run python tests with coverage
71-
env:
64+
env:
7265
OPEN_ROUTER_API_KEY: ${{ secrets.OPEN_ROUTER_API_KEY }}
7366
run: |
7467
source .venv/bin/activate
@@ -79,13 +72,13 @@ jobs:
7972
--cov-report=term:skip-covered \
8073
tests/
8174
shell: bash
82-
75+
8376
- name: "Show coverage summary"
8477
if: always()
8578
run: |
8679
echo "=== Coverage Summary ==="
8780
coverage report || echo "Coverage report not available"
88-
81+
8982
- name: Upload results of test coverage
9083
uses: actions/upload-artifact@v4
9184
if: always()

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,11 @@ logs/
4141
# Temporary files
4242
*.tmp
4343

44-
uv.lock
44+
uv.lock
45+
46+
#Ignore vscode AI rules
47+
.github/instructions/codacy.instructions.md
48+
.venv/
49+
50+
51+
tests/benchmark/results/

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@ VENV_ACTIVATE = source $(VENV_PATH)/bin/activate
2020
help: ## Show this help message
2121
@printf "$(BLUE) Available commands: $(RESET)\n"
2222
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(GREEN)%-15s$(RESET) %s\n", $$1, $$2}' $(MAKEFILE_LIST)
23-
install: ## Installation
23+
install: ## Install core dev dependencies
2424
uv venv $(VENV_PATH) --python 3.10
2525
uv pip install -e ".[dev]"
26+
install-benchmark: ## Install dev + benchmark dependencies (langgraph + asyncflow)
27+
uv venv $(VENV_PATH) --python 3.10
28+
uv pip install -e ".[langgraph,asyncflow,dev]"
29+
install-all: ## Install all dependencies (frameworks + runtimes + dev)
30+
uv venv $(VENV_PATH) --python 3.10
31+
uv pip install -e ".[all,dev]"
2632

2733
# 2) Examples
2834
examples-lg-asyncflow: ## Langgraph + Asyncflow
@@ -40,5 +46,5 @@ examples-ag-parsl: ## AutoGen + Parsl
4046
benchmark: ## Run the experiments in the benchmarking
4147
$(VENV_ACTIVATE) && python3 -m tests.benchmark.data_generation.run_experiments
4248

43-
benchmark-suite: ## Run the full benchmark suite with multiple configs
49+
benchmark-multiple-workloads: ## Run the full benchmark suite with multiple configs
4450
$(VENV_ACTIVATE) && python3 -m tests.benchmark.run_benchmark_suite

pyproject.toml

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,48 @@ authors = [
1010
]
1111
requires-python = ">=3.10"
1212
dependencies = [
13+
"pydantic>=2.0.0",
14+
"python-json-logger>=2.0.0",
15+
"python-dotenv",
16+
"cloudpickle>=3.1.2",
17+
]
18+
19+
[project.optional-dependencies]
20+
# --- Per-component extras (pick only what you need) ---
21+
langgraph = [
1322
"langgraph>=0.6.6",
14-
"radical-asyncflow",
1523
"langchain-openai",
1624
"langchain",
1725
"langchain-ollama",
1826
"langchain-core",
1927
"langchain-community",
2028
"langchain-mcp-adapters",
21-
"python-dotenv",
22-
"academy-py",
23-
"pydantic>=2.0.0",
24-
"python-json-logger>=2.0.0",
29+
]
30+
crewai = [
2531
"crewai[google-genai]",
32+
]
33+
autogen = [
2634
"autogen>=0.10.4",
35+
]
36+
asyncflow = [
37+
"radical-asyncflow",
38+
]
39+
parsl = [
2740
"parsl>=2026.1.19",
28-
"cloudpickle>=3.1.2",
2941
]
30-
31-
[project.optional-dependencies]
42+
academy = [
43+
"academy-py",
44+
]
45+
# --- Convenience bundles ---
46+
frameworks = [
47+
"flowgentic[langgraph,crewai,autogen]",
48+
]
49+
runtime = [
50+
"flowgentic[asyncflow,parsl,academy]",
51+
]
52+
all = [
53+
"flowgentic[frameworks,runtime]",
54+
]
3255
dev = [
3356
"ruff>=0.12.11",
3457
"pre-commit",
@@ -41,10 +64,9 @@ dev = [
4164
"pytest>=7.0.0",
4265
"pytest-asyncio>=0.21.0",
4366
"uv>=0.1.0",
44-
"pytest-asyncio>=0.21.0",
4567
"pytest-cov",
4668
"matplotlib",
47-
"cloudpickle"
69+
"cloudpickle",
4870
]
4971

5072
[project.scripts]
@@ -102,3 +124,10 @@ radical-asyncflow = { git = "https://github.com/radical-cybertools/radical.async
102124

103125
[tool.pytest.ini_options]
104126
asyncio_mode = "auto"
127+
# Exclude tests whose modules no longer exist on this branch
128+
addopts = [
129+
"--ignore=tests/integration",
130+
"--ignore=tests/unit/test_generator.py",
131+
"--ignore=tests/unit/test_introspection.py",
132+
"--ignore=tests/unit/test_memory.py",
133+
]

src/flowgentic/agent_orchestration_frameworks/langgraph.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
from functools import wraps
21
import time
32
import uuid
3+
from functools import wraps
44
from typing import Any, Callable, Optional
5+
56
from flowgentic.agent_orchestration_frameworks.base import AgentOrchestrator
67
from flowgentic.backend_engines.base import BaseEngine
7-
from langchain_core.tools import tool as langchain_tool
8+
9+
10+
def _langchain_tool(func: Callable) -> Callable:
11+
"""Lazy wrapper so langchain-core is only imported when actually used."""
12+
try:
13+
from langchain_core.tools import tool as _tool
14+
except ModuleNotFoundError as exc:
15+
raise ImportError(
16+
"langchain-core is required for LanGraphOrchestrator. "
17+
'Install it with: pip install "flowgentic[langgraph]"'
18+
) from exc
19+
return _tool(func)
820

921

1022
class LanGraphOrchestrator(AgentOrchestrator):
@@ -21,27 +33,55 @@ def deco(f: Callable):
2133
# Emit setup start event
2234
self.engine.emit(
2335
{
24-
"event": "task_wrap_start",
36+
"event": "tool_wrap_start",
2537
"ts": time.perf_counter(),
26-
"task_name": task_name,
38+
"tool_name": task_name,
2739
"wrap_id": wrap_id,
2840
}
2941
)
3042

3143
@wraps(f)
3244
async def wrapper(*args, **kwargs):
33-
return await self.engine.execute_tool(
34-
f, *args, task_kwargs=task_kwargs, **kwargs
45+
invocation_id = str(uuid.uuid4())
46+
47+
# Ts_invoke_start: LangGraph calls tool, FlowGentic intercepts
48+
self.engine.emit(
49+
{
50+
"event": "tool_invoke_start",
51+
"ts": time.perf_counter(),
52+
"tool_name": task_name,
53+
"invocation_id": invocation_id,
54+
}
3555
)
3656

37-
wrapped_tool = langchain_tool(wrapper)
57+
result = await self.engine.execute_tool(
58+
f,
59+
*args,
60+
task_kwargs=task_kwargs,
61+
invocation_id=invocation_id,
62+
**kwargs,
63+
)
64+
65+
# Ts_collect_end: Result returned to LangGraph
66+
self.engine.emit(
67+
{
68+
"event": "tool_invoke_end",
69+
"ts": time.perf_counter(),
70+
"tool_name": task_name,
71+
"invocation_id": invocation_id,
72+
}
73+
)
74+
75+
return result
76+
77+
wrapped_tool = _langchain_tool(wrapper)
3878

3979
# Emit setup end event
4080
self.engine.emit(
4181
{
42-
"event": "task_wrap_end",
82+
"event": "tool_wrap_end",
4383
"ts": time.perf_counter(),
44-
"task_name": task_name,
84+
"tool_name": task_name,
4585
"wrap_id": wrap_id,
4686
}
4787
)

src/flowgentic/backend_engines/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABC, abstractmethod
2-
from typing import Any, Callable, List, Optional, Tuple, Dict
2+
from typing import Any, Callable, Dict, List, Optional, Tuple
33

44

55
class BaseEngine(ABC):
@@ -22,6 +22,7 @@ async def execute_tool(
2222
func: Callable,
2323
*args,
2424
task_kwargs: Optional[Dict[str, Any]] = None,
25+
invocation_id: Optional[str] = None,
2526
**kwargs,
2627
) -> Dict[str, Any]:
2728
pass

src/flowgentic/backend_engines/parsl.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import asyncio
2+
import time
3+
from typing import Any, Callable, Dict, Optional
4+
25
import parsl
36
from parsl.app.app import python_app
4-
from typing import Callable, Dict, Any
57
from parsl.config import Config
8+
69
from flowgentic.backend_engines.base import BaseEngine
710

811

912
class ParslEngine(BaseEngine):
10-
def __init__(self, config: Config = None):
13+
def __init__(
14+
self,
15+
config: Config = None,
16+
observer: Optional[Callable[[Dict[str, Any]], None]] = None,
17+
):
18+
super().__init__(observer=observer)
1119
parsl.load(config)
1220

13-
self._task_registry = {}
21+
self._task_registry: Dict[str, Any] = {}
1422

1523
def _make_parsl_app(self, func: Callable):
1624
if asyncio.iscoroutinefunction(func):
@@ -21,15 +29,48 @@ def wrapper(*args, **kwargs):
2129
return python_app(wrapper)
2230
return python_app(func)
2331

24-
async def execute_tool(self, func: Callable, *args, **kwargs) -> Dict[str, Any]:
25-
if func.__name__ not in self._task_registry:
26-
self._task_registry[func.__name__] = self._make_parsl_app(func)
32+
async def execute_tool(
33+
self,
34+
func: Callable,
35+
*args,
36+
task_kwargs: Optional[Dict[str, Any]] = None,
37+
invocation_id: Optional[str] = None,
38+
**kwargs,
39+
) -> Dict[str, Any]:
40+
task_name = getattr(func, "__name__", str(func))
41+
42+
# Track whether the task app was already cached
43+
cache_hit = task_name in self._task_registry
44+
if not cache_hit:
45+
self._task_registry[task_name] = self._make_parsl_app(func)
2746

28-
task_app = self._task_registry[func.__name__]
47+
task_app = self._task_registry[task_name]
48+
49+
# Ts_resolve_end: Task descriptor resolved, about to enter Parsl
50+
self.emit(
51+
{
52+
"event": "tool_resolve_end",
53+
"ts": time.perf_counter(),
54+
"tool_name": task_name,
55+
"invocation_id": invocation_id,
56+
"cache_hit": cache_hit,
57+
}
58+
)
2959

3060
future = task_app(*args, **kwargs)
61+
result = await asyncio.to_thread(future.result)
62+
63+
# Ts_collect_start: Result received from Parsl
64+
self.emit(
65+
{
66+
"event": "tool_collect_start",
67+
"ts": time.perf_counter(),
68+
"tool_name": task_name,
69+
"invocation_id": invocation_id,
70+
}
71+
)
3172

32-
return await asyncio.to_thread(future.result)
73+
return result
3374

3475
def wrap_node(self, node_func: Callable):
3576
node_app = self._make_parsl_app(node_func)

0 commit comments

Comments
 (0)