Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/agent_engine/engine/langgraph/graph/graph_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,10 @@ def _build_agent_tools(
server_by_tool: dict[str, str] = {}
for tool_spec in spec.tools:
function = self._tool_loader.load(tool_spec.id)
tools.append(StructuredTool.from_function(function, description=tool_spec.description))
if inspect.iscoroutinefunction(function):
tools.append(StructuredTool.from_function(coroutine=function, description=tool_spec.description))
else:
tools.append(StructuredTool.from_function(func=function, description=tool_spec.description))
for mcp in spec.mcps:
server_tools = self._mcp_tools.get(mcp.id, [])
tools.extend(server_tools)
Expand Down
39 changes: 39 additions & 0 deletions tests/test_review_regression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pytest
from langchain_core.messages import AIMessage, ToolMessage

from agent_engine.engine.langgraph.engine import LangGraphEngine
from tests.approvals.test_engine_hitl import _spec as approval_spec


class SingleToolCallModel:
def __init__(self):
self.calls = 0

def bind_tools(self, tools):
return self

async def ainvoke(self, messages):
if any(isinstance(m, ToolMessage) for m in messages):
return AIMessage(content='done')
self.calls += 1
return AIMessage(content='', tool_calls=[{
'name': 'record_value', 'args': {'message': 'reviewed-value'},
'id': f'call-{self.calls}', 'type': 'tool_call',
}])


@pytest.mark.asyncio
async def test_async_local_tool_executes(tmp_path):
output = tmp_path / 'async-executed.txt'
tool = tmp_path / 'plugins/tools/record_value.py'
tool.parent.mkdir(parents=True)
tool.write_text('from pathlib import Path\n'
'async def record_value(message: str) -> str:\n'
f' Path({str(output)!r}).write_text(message)\n'
' return message\n')
model = SingleToolCallModel()
async with LangGraphEngine(tmp_path, model_factory=lambda *a, **kw: model) as engine:
await engine.build(approval_spec('record_value', auto_mode=True))
result = await engine.run('record a value')
print(f'async tool executed={output.exists()}, used_tools={result.used_tools}')
assert output.exists()