Skip to content

Commit eed77c7

Browse files
authored
Merge pull request #248 from tcdent/framework-swarm
OpenAI Swarm is now supported by AgentStack. `agentstack init <project_name> --framework=openai_swarm`
2 parents 0436d04 + eaf7eb1 commit eed77c7

27 files changed

Lines changed: 1452 additions & 78 deletions

agentstack/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from pathlib import Path
1010
from agentstack import conf
1111
from agentstack.utils import get_framework
12-
from agentstack.agents import get_agent
13-
from agentstack.tasks import get_task
12+
from agentstack.agents import get_agent, get_all_agents, get_all_agent_names
13+
from agentstack.tasks import get_task, get_all_tasks, get_all_task_names
1414
from agentstack.inputs import get_inputs
1515
from agentstack import frameworks
1616

@@ -22,7 +22,11 @@
2222
"get_tags",
2323
"get_framework",
2424
"get_agent",
25+
"get_all_agents",
26+
"get_all_agent_names",
2527
"get_task",
28+
"get_all_tasks",
29+
"get_all_task_names",
2630
"get_inputs",
2731
]
2832

agentstack/frameworks/__init__.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
CREWAI = 'crewai'
1818
LANGGRAPH = 'langgraph'
19+
OPENAI_SWARM = 'openai_swarm'
1920
SUPPORTED_FRAMEWORKS = [
2021
CREWAI,
2122
LANGGRAPH,
23+
OPENAI_SWARM,
2224
]
2325

2426

@@ -58,9 +60,9 @@ def remove_tool(self, tool: ToolConfig, agent_name: str) -> None:
5860
"""
5961
...
6062

61-
def get_tool_callables(self, tool_name: str) -> list[Callable]:
63+
def wrap_tool(self, tool_func: Callable) -> Callable:
6264
"""
63-
Get a tool by name and return it as a list of framework-native callables.
65+
Wrap a tool function with framework-specific functionality.
6466
"""
6567
...
6668

@@ -173,7 +175,43 @@ def get_tool_callables(tool_name: str) -> list[Callable]:
173175
"""
174176
Get a tool by name and return it as a list of framework-native callables.
175177
"""
176-
return get_framework_module(get_framework()).get_tool_callables(tool_name)
178+
# TODO: remove after agentops fixes their issue
179+
# wrap method with agentops tool event
180+
def wrap_method(method: Callable) -> Callable:
181+
from inspect import signature
182+
183+
original_signature = signature(method)
184+
def wrapped_method(*args, **kwargs):
185+
import agentops
186+
tool_event = agentops.ToolEvent(method.__name__)
187+
result = method(*args, **kwargs)
188+
agentops.record(tool_event)
189+
return result
190+
191+
# Preserve all original attributes
192+
wrapped_method.__name__ = method.__name__
193+
wrapped_method.__doc__ = method.__doc__
194+
wrapped_method.__module__ = method.__module__
195+
wrapped_method.__qualname__ = method.__qualname__
196+
wrapped_method.__annotations__ = getattr(method, '__annotations__', {})
197+
wrapped_method.__signature__ = original_signature # type: ignore
198+
return wrapped_method
199+
200+
tool_funcs = []
201+
tool_config = ToolConfig.from_tool_name(tool_name)
202+
for tool_func_name in tool_config.tools:
203+
tool_func = getattr(tool_config.module, tool_func_name)
204+
205+
assert callable(tool_func), f"Tool function {tool_func_name} is not callable."
206+
assert tool_func.__doc__, f"Tool function {tool_func_name} is missing a docstring."
207+
208+
# First wrap with agentops
209+
agentops_wrapped = wrap_method(tool_func)
210+
# Then apply framework decorators
211+
framework_wrapped = get_framework_module(get_framework()).wrap_tool(agentops_wrapped)
212+
tool_funcs.append(framework_wrapped)
213+
214+
return tool_funcs
177215

178216

179217
def get_agent_method_names() -> list[str]:

agentstack/frameworks/crewai.py

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from agentstack.agents import AgentConfig
99
from agentstack.generation import asttools
1010
from agentstack import graph
11+
1112
if TYPE_CHECKING:
1213
from agentstack.generation import InsertionPoint
1314

@@ -19,6 +20,7 @@ class CrewFile(asttools.File):
1920
Parses and manipulates the CrewAI entrypoint file.
2021
All AST interactions should happen within the methods of this class.
2122
"""
23+
2224
def write(self):
2325
"""
2426
Early versions of the crew entrypoint file used tabs instead of spaces.
@@ -65,7 +67,7 @@ def {task.name}(self) -> Task:
6567
return Task(
6668
config=self.tasks_config['{task.name}'],
6769
)"""
68-
70+
6971
if not self.source[:pos].endswith('\n'):
7072
code = '\n\n' + code
7173
if not self.source[pos:].startswith('\n'):
@@ -95,7 +97,7 @@ def {agent.name}(self) -> Agent:
9597
tools=[], # add tools here or use `agentstack tools add <tool_name>
9698
verbose=True,
9799
)"""
98-
100+
99101
if not self.source[:pos].endswith('\n'):
100102
code = '\n\n' + code
101103
if not self.source[pos:].startswith('\n'):
@@ -280,7 +282,7 @@ def add_agent(agent: AgentConfig, position: Optional['InsertionPoint'] = None) -
280282
"""
281283
if position is not None:
282284
raise NotImplementedError("Agent insertion points are not supported in CrewAI.")
283-
285+
284286
with CrewFile(conf.PATH / ENTRYPOINT) as crew_file:
285287
crew_file.add_agent_method(agent)
286288

@@ -302,52 +304,19 @@ def remove_tool(tool: ToolConfig, agent_name: str):
302304
crew_file.remove_agent_tools(agent_name, tool)
303305

304306

305-
def get_tool_callables(tool_name: str) -> list[Callable]:
307+
def wrap_tool(tool_func: Callable) -> Callable:
306308
"""
307-
Get a tool implementations for use directly by a CrewAI agent.
309+
Wrap a tool function with framework-specific functionality.
308310
"""
309311
try:
310312
from crewai.tools import tool as _crewai_tool_decorator
311313
except ImportError:
312314
raise ValidationError("Could not import `crewai`. Is this an AgentStack CrewAI project?")
313315

314-
# TODO: remove after agentops fixes their issue
315-
# wrap method with agentops tool event
316-
def wrap_method(method: Callable) -> Callable:
317-
def wrapped_method(*args, **kwargs):
318-
import agentops
319-
tool_event = agentops.ToolEvent(method.__name__)
320-
result = method(*args, **kwargs)
321-
agentops.record(tool_event)
322-
return result
323-
324-
# Preserve all original attributes
325-
wrapped_method.__name__ = method.__name__
326-
wrapped_method.__doc__ = method.__doc__
327-
wrapped_method.__module__ = method.__module__
328-
wrapped_method.__qualname__ = method.__qualname__
329-
wrapped_method.__annotations__ = getattr(method, '__annotations__', {})
330-
return wrapped_method
331-
332-
tool_funcs = []
333-
tool_config = ToolConfig.from_tool_name(tool_name)
334-
for tool_func_name in tool_config.tools:
335-
tool_func = getattr(tool_config.module, tool_func_name)
336-
337-
assert callable(tool_func), f"Tool function {tool_func_name} is not callable."
338-
assert tool_func.__doc__, f"Tool function {tool_func_name} is missing a docstring."
339-
340-
# First wrap with agentops
341-
agentops_wrapped = wrap_method(tool_func)
342-
# Then apply CrewAI decorator last so it properly inherits from BaseTool
343-
crewai_wrapped = _crewai_tool_decorator(agentops_wrapped)
344-
tool_funcs.append(crewai_wrapped)
345-
346-
return tool_funcs
316+
return _crewai_tool_decorator(tool_func)
347317

348318

349319
def get_graph() -> list[graph.Edge]:
350320
"""Get the graph of the user's project."""
351321
log.debug("CrewAI does not support graph generation.")
352322
return []
353-

agentstack/frameworks/langgraph.py

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
GRAPH_NODE_END = 'END'
1919
GRAPH_NODE_TOOLS = 'tools' # references the `ToolNode` instance
2020
GRAPH_NODE_TOOLS_CONDITION = 'tools_condition'
21-
GRAPH_NODES_SPECIAL = (GRAPH_NODE_START, GRAPH_NODE_END, GRAPH_NODE_TOOLS_CONDITION, )
21+
GRAPH_NODES_SPECIAL = (
22+
GRAPH_NODE_START,
23+
GRAPH_NODE_END,
24+
GRAPH_NODE_TOOLS_CONDITION,
25+
)
2226

2327

2428
@dataclass
@@ -347,7 +351,7 @@ def _get_node_name(node: ast.expr) -> str:
347351
for node in nodes:
348352
source, target = node.args
349353
source_name = _get_node_name(source)
350-
#target_name = _get_node_name(target)
354+
# target_name = _get_node_name(target)
351355
if source_name == GRAPH_NODE_TOOLS: # TODO this is a bit brittle
352356
nodes.remove(node)
353357
# if target_name == GRAPH_NODE_TOOLS:
@@ -430,7 +434,7 @@ def add_conditional_edge(self, edge: graph.Edge):
430434
else:
431435
graph_instance = asttools.find_method_calls(self.get_run_method(), 'StateGraph')[0]
432436
_, end = self.get_node_range(graph_instance)
433-
437+
434438
source, target = edge.source.name, edge.target.name
435439
# wrap the node names in quotes if they are not special nodes
436440
if edge.source.type != graph.NodeType.SPECIAL:
@@ -745,36 +749,12 @@ def remove_tool(tool: ToolConfig, agent_name: str):
745749
entrypoint.remove_agent_tools(agent_name, tool)
746750

747751

748-
def get_tool_callables(tool_name: str) -> list[Callable]:
752+
def wrap_tool(tool_func: Callable) -> Callable:
749753
"""
750-
Get a tool by name and return it as a list of framework-native callables.
754+
Wrap a tool function with framework-specific functionality.
751755
"""
752-
# LangGraph accepts functions as tools, so we can return them directly
753-
tool_funcs = []
754-
tool_config = ToolConfig.from_tool_name(tool_name)
755-
756-
# TODO: remove after agentops supports langgraph
757-
# wrap method with agentops tool event
758-
def wrap_method(method: Callable) -> Callable:
759-
@wraps(method) # This preserves the original function's metadata
760-
def wrapped_method(*args, **kwargs):
761-
import agentops
762-
tool_event = agentops.ToolEvent(method.__name__)
763-
result = method(*args, **kwargs)
764-
agentops.record(tool_event)
765-
return result
766-
767-
return wrapped_method
768-
769-
for tool_func_name in tool_config.tools:
770-
tool_func = getattr(tool_config.module, tool_func_name)
771-
772-
assert callable(tool_func), f"Tool function {tool_func_name} is not callable."
773-
assert tool_func.__doc__, f"Tool function {tool_func_name} is missing a docstring."
774-
775-
tool_funcs.append(wrap_method(tool_func))
776-
777-
return tool_funcs
756+
# LangGraph accepts bare functions as tools, so we don't need to do anything here.
757+
return tool_func
778758

779759

780760
def get_graph() -> list[graph.Edge]:

0 commit comments

Comments
 (0)