flowforge-sdk 0.3.2__tar.gz → 0.3.4__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.
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/PKG-INFO +1 -1
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/pyproject.toml +1 -1
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/__init__.py +3 -1
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/steps.py +94 -1
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/tools.py +113 -1
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/.gitignore +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/README.md +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/agent.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/agent_def.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/ai/__init__.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/ai/providers.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/client.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/config.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/context.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/decorators.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/dev/__init__.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/dev/server.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/exceptions.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/execution.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/integrations/__init__.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/integrations/fastapi.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/network.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/router.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/streaming.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/triggers.py +0 -0
- {flowforge_sdk-0.3.2 → flowforge_sdk-0.3.4}/src/flowforge/worker.py +0 -0
|
@@ -27,7 +27,7 @@ from flowforge.exceptions import (
|
|
|
27
27
|
from flowforge.network import Network, NetworkResult, NetworkState, RouterContext, network
|
|
28
28
|
from flowforge.steps import step
|
|
29
29
|
from flowforge.streaming import RunEvent, RunEventType
|
|
30
|
-
from flowforge.tools import Tool, tool
|
|
30
|
+
from flowforge.tools import SubAgentConfig, Tool, sub_agent, tool
|
|
31
31
|
from flowforge.triggers import Trigger, trigger
|
|
32
32
|
|
|
33
33
|
__version__ = "0.1.0"
|
|
@@ -45,6 +45,8 @@ __all__ = [
|
|
|
45
45
|
# Tools
|
|
46
46
|
"Tool",
|
|
47
47
|
"tool",
|
|
48
|
+
"sub_agent",
|
|
49
|
+
"SubAgentConfig",
|
|
48
50
|
# Agent
|
|
49
51
|
"AgentConfig",
|
|
50
52
|
"AgentState",
|
|
@@ -11,7 +11,7 @@ from flowforge.agent_def import AgentDefinition
|
|
|
11
11
|
from flowforge.exceptions import StepCompleted, StepFailed
|
|
12
12
|
from flowforge.network import Network, NetworkResult, NetworkState, RouterContext
|
|
13
13
|
from flowforge.router import LLMRouter
|
|
14
|
-
from flowforge.tools import Tool
|
|
14
|
+
from flowforge.tools import SubAgentConfig, Tool
|
|
15
15
|
|
|
16
16
|
T = TypeVar("T")
|
|
17
17
|
|
|
@@ -458,6 +458,8 @@ class StepManager:
|
|
|
458
458
|
checkpoint_strategy: str = "per_tool",
|
|
459
459
|
max_tool_calls: int = 50,
|
|
460
460
|
temperature: float = 0.7,
|
|
461
|
+
_depth: int = 0,
|
|
462
|
+
_max_depth: int = 3,
|
|
461
463
|
**kwargs: Any,
|
|
462
464
|
) -> AgentResult:
|
|
463
465
|
"""
|
|
@@ -467,6 +469,9 @@ class StepManager:
|
|
|
467
469
|
until the task is complete or limits are reached. Each tool execution
|
|
468
470
|
is checkpointed for durability.
|
|
469
471
|
|
|
472
|
+
Sub-agent tools (created via ``sub_agent()``) are automatically
|
|
473
|
+
detected and executed as nested agent loops.
|
|
474
|
+
|
|
470
475
|
Args:
|
|
471
476
|
step_id: Unique identifier for this agent execution.
|
|
472
477
|
task: The task/prompt for the agent to accomplish.
|
|
@@ -480,6 +485,8 @@ class StepManager:
|
|
|
480
485
|
- "final_only": Only checkpoint final result
|
|
481
486
|
max_tool_calls: Maximum total tool calls across iterations (default 50).
|
|
482
487
|
temperature: Sampling temperature for LLM (default 0.7).
|
|
488
|
+
_depth: Current nesting depth (internal, used by sub-agent recursion).
|
|
489
|
+
_max_depth: Maximum allowed nesting depth (default 3).
|
|
483
490
|
**kwargs: Additional LLM parameters.
|
|
484
491
|
|
|
485
492
|
Returns:
|
|
@@ -667,6 +674,88 @@ class StepManager:
|
|
|
667
674
|
# Execute tool via step.run for checkpointing
|
|
668
675
|
tool_step_id = f"{step_id}/iter-{state.iteration}/tool-{tool_call_id}"
|
|
669
676
|
|
|
677
|
+
# --- Sub-agent interception ---
|
|
678
|
+
# If the tool is a sub-agent wrapper, run a nested agent
|
|
679
|
+
# loop instead of calling tool.fn directly.
|
|
680
|
+
cfg: SubAgentConfig | None = getattr(tool, "_sub_agent_config", None)
|
|
681
|
+
if cfg is not None:
|
|
682
|
+
# Depth guard: prevent infinite sub-agent recursion
|
|
683
|
+
if _depth >= _max_depth:
|
|
684
|
+
error_message = json.dumps({
|
|
685
|
+
"error": f"Sub-agent depth limit reached ({_max_depth}). "
|
|
686
|
+
"Cannot spawn further sub-agents.",
|
|
687
|
+
})
|
|
688
|
+
state.messages.append({
|
|
689
|
+
"role": "tool",
|
|
690
|
+
"tool_call_id": tool_call_id,
|
|
691
|
+
"name": tool_name,
|
|
692
|
+
"content": error_message,
|
|
693
|
+
})
|
|
694
|
+
tool_call_record["status"] = "failed"
|
|
695
|
+
tool_call_record["error"] = "depth_limit_reached"
|
|
696
|
+
state.tool_calls_count += 1
|
|
697
|
+
continue
|
|
698
|
+
|
|
699
|
+
sub_step_id = f"{step_id}/sub-{cfg.agent.name}-{tool_call_id}"
|
|
700
|
+
|
|
701
|
+
# Build sub-agent system prompt with optional parent context
|
|
702
|
+
sub_system = cfg.agent.system
|
|
703
|
+
if cfg.context_mode == "summary":
|
|
704
|
+
recent = state.messages[-6:] # last 3 exchanges
|
|
705
|
+
context_lines = [
|
|
706
|
+
f"[{m['role']}]: {str(m.get('content', ''))[:200]}"
|
|
707
|
+
for m in recent
|
|
708
|
+
]
|
|
709
|
+
sub_system += "\n\nParent context:\n" + "\n".join(context_lines)
|
|
710
|
+
elif cfg.context_mode == "full_history":
|
|
711
|
+
sub_system += "\n\nFull parent conversation:\n" + json.dumps(state.messages)
|
|
712
|
+
|
|
713
|
+
try:
|
|
714
|
+
sub_result = await self.agent(
|
|
715
|
+
sub_step_id,
|
|
716
|
+
task=tool_args.get("task", ""),
|
|
717
|
+
model=cfg.agent.model or model,
|
|
718
|
+
system=sub_system,
|
|
719
|
+
tools=cfg.agent.tools,
|
|
720
|
+
max_iterations=cfg.max_iterations,
|
|
721
|
+
max_tool_calls=cfg.max_tool_calls,
|
|
722
|
+
temperature=cfg.temperature,
|
|
723
|
+
_depth=_depth + 1,
|
|
724
|
+
_max_depth=_max_depth,
|
|
725
|
+
**kwargs,
|
|
726
|
+
)
|
|
727
|
+
sub_output = sub_result.output
|
|
728
|
+
|
|
729
|
+
state.messages.append({
|
|
730
|
+
"role": "tool",
|
|
731
|
+
"tool_call_id": tool_call_id,
|
|
732
|
+
"name": tool_name,
|
|
733
|
+
"content": sub_output,
|
|
734
|
+
})
|
|
735
|
+
tool_call_record["status"] = "success"
|
|
736
|
+
tool_call_record["result"] = sub_output
|
|
737
|
+
tool_call_record["sub_agent"] = sub_result.to_dict()
|
|
738
|
+
except StepCompleted:
|
|
739
|
+
# Sub-agent completed its first execution — propagate
|
|
740
|
+
# so the server can memoize the step. On replay the
|
|
741
|
+
# nested agent() call returns normally.
|
|
742
|
+
raise
|
|
743
|
+
except (StepFailed, Exception) as e:
|
|
744
|
+
err_msg = str(e)
|
|
745
|
+
error_message = json.dumps({"error": err_msg})
|
|
746
|
+
state.messages.append({
|
|
747
|
+
"role": "tool",
|
|
748
|
+
"tool_call_id": tool_call_id,
|
|
749
|
+
"name": tool_name,
|
|
750
|
+
"content": error_message,
|
|
751
|
+
})
|
|
752
|
+
tool_call_record["status"] = "failed"
|
|
753
|
+
tool_call_record["error"] = err_msg
|
|
754
|
+
|
|
755
|
+
state.tool_calls_count += 1
|
|
756
|
+
continue
|
|
757
|
+
# --- End sub-agent interception ---
|
|
758
|
+
|
|
670
759
|
try:
|
|
671
760
|
tool_result = await self.run(
|
|
672
761
|
tool_step_id,
|
|
@@ -1129,6 +1218,8 @@ class _StepProxy:
|
|
|
1129
1218
|
checkpoint_strategy: str = "per_tool",
|
|
1130
1219
|
max_tool_calls: int = 50,
|
|
1131
1220
|
temperature: float = 0.7,
|
|
1221
|
+
_depth: int = 0,
|
|
1222
|
+
_max_depth: int = 3,
|
|
1132
1223
|
**kwargs: Any,
|
|
1133
1224
|
) -> AgentResult:
|
|
1134
1225
|
return await self._get_manager().agent(
|
|
@@ -1141,6 +1232,8 @@ class _StepProxy:
|
|
|
1141
1232
|
checkpoint_strategy=checkpoint_strategy,
|
|
1142
1233
|
max_tool_calls=max_tool_calls,
|
|
1143
1234
|
temperature=temperature,
|
|
1235
|
+
_depth=_depth,
|
|
1236
|
+
_max_depth=_max_depth,
|
|
1144
1237
|
**kwargs,
|
|
1145
1238
|
)
|
|
1146
1239
|
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"""Tool definition API for FlowForge agents."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import inspect
|
|
4
6
|
import types as _builtintypes
|
|
5
7
|
from collections.abc import Awaitable, Callable
|
|
6
8
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Any, Literal, Union, get_args, get_origin, get_type_hints
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal, Union, get_args, get_origin, get_type_hints
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from flowforge.agent_def import AgentDefinition
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
@dataclass
|
|
@@ -270,3 +275,110 @@ def tool(
|
|
|
270
275
|
)
|
|
271
276
|
|
|
272
277
|
return decorator
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dataclass
|
|
281
|
+
class SubAgentConfig:
|
|
282
|
+
"""Configuration for a sub-agent tool.
|
|
283
|
+
|
|
284
|
+
This is attached to a Tool via ``_sub_agent_config`` so that
|
|
285
|
+
``step.agent()`` can detect the tool as a sub-agent delegation
|
|
286
|
+
and run a nested agent loop instead of calling ``tool.fn``.
|
|
287
|
+
|
|
288
|
+
Attributes:
|
|
289
|
+
agent: The agent definition to run as a sub-agent.
|
|
290
|
+
max_iterations: Max iterations for the sub-agent loop.
|
|
291
|
+
max_tool_calls: Max tool calls for the sub-agent loop.
|
|
292
|
+
temperature: Sampling temperature for the sub-agent LLM.
|
|
293
|
+
context_mode: How much parent context to share with the sub-agent.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
agent: AgentDefinition
|
|
297
|
+
max_iterations: int = 20
|
|
298
|
+
max_tool_calls: int = 50
|
|
299
|
+
temperature: float = 0.7
|
|
300
|
+
context_mode: Literal["task_only", "summary", "full_history"] = "task_only"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def sub_agent(
|
|
304
|
+
agent: AgentDefinition,
|
|
305
|
+
*,
|
|
306
|
+
description: str | None = None,
|
|
307
|
+
max_iterations: int = 20,
|
|
308
|
+
max_tool_calls: int = 50,
|
|
309
|
+
temperature: float = 0.7,
|
|
310
|
+
context_mode: Literal["task_only", "summary", "full_history"] = "task_only",
|
|
311
|
+
) -> Tool:
|
|
312
|
+
"""Create a Tool that delegates work to a sub-agent.
|
|
313
|
+
|
|
314
|
+
When the parent agent's LLM calls this tool, ``step.agent()``
|
|
315
|
+
intercepts the call and runs a nested agent loop with the
|
|
316
|
+
given agent definition, returning the sub-agent's output as
|
|
317
|
+
the tool result.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
agent: Agent definition describing the sub-agent.
|
|
321
|
+
description: Tool description shown to the LLM. If omitted,
|
|
322
|
+
auto-generated from the agent name and system prompt.
|
|
323
|
+
max_iterations: Maximum reasoning iterations for the sub-agent.
|
|
324
|
+
max_tool_calls: Maximum tool calls for the sub-agent.
|
|
325
|
+
temperature: Sampling temperature for the sub-agent.
|
|
326
|
+
context_mode: How much parent context to pass:
|
|
327
|
+
- "task_only" (default): Only the delegated task string.
|
|
328
|
+
- "summary": Task + last 3 parent exchanges as context.
|
|
329
|
+
- "full_history": Task + full parent conversation.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
A Tool with a single ``task: str`` parameter and a
|
|
333
|
+
``_sub_agent_config`` attribute for detection.
|
|
334
|
+
|
|
335
|
+
Example:
|
|
336
|
+
researcher = agent_def(
|
|
337
|
+
name="researcher",
|
|
338
|
+
system="You research topics thoroughly.",
|
|
339
|
+
tools=[web_search],
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
research_tool = sub_agent(researcher, description="Delegate research tasks.")
|
|
343
|
+
|
|
344
|
+
result = await step.agent(
|
|
345
|
+
"manager",
|
|
346
|
+
task="Plan a project",
|
|
347
|
+
model="claude-opus-4-6",
|
|
348
|
+
tools=[research_tool],
|
|
349
|
+
)
|
|
350
|
+
"""
|
|
351
|
+
desc = description or f"Delegate a task to the '{agent.name}' sub-agent. {agent.system[:100]}"
|
|
352
|
+
|
|
353
|
+
async def _placeholder(task: str) -> str: # noqa: ARG001
|
|
354
|
+
raise RuntimeError(
|
|
355
|
+
"Sub-agent tool should not be called directly. "
|
|
356
|
+
"It is intercepted by step.agent()."
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
t = Tool(
|
|
360
|
+
name=agent.name,
|
|
361
|
+
description=desc,
|
|
362
|
+
fn=_placeholder,
|
|
363
|
+
parameters={
|
|
364
|
+
"type": "object",
|
|
365
|
+
"properties": {
|
|
366
|
+
"task": {
|
|
367
|
+
"type": "string",
|
|
368
|
+
"description": "The task to delegate to the sub-agent.",
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
"required": ["task"],
|
|
372
|
+
},
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Attach sub-agent config as a marker for step.agent() detection
|
|
376
|
+
t._sub_agent_config = SubAgentConfig( # type: ignore[attr-defined]
|
|
377
|
+
agent=agent,
|
|
378
|
+
max_iterations=max_iterations,
|
|
379
|
+
max_tool_calls=max_tool_calls,
|
|
380
|
+
temperature=temperature,
|
|
381
|
+
context_mode=context_mode,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return t
|
|
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
|