flowforge-sdk 0.1.0__py3-none-any.whl
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/__init__.py +79 -0
- flowforge/agent.py +86 -0
- flowforge/agent_def.py +76 -0
- flowforge/ai/__init__.py +5 -0
- flowforge/ai/providers.py +203 -0
- flowforge/client.py +380 -0
- flowforge/config.py +199 -0
- flowforge/context.py +154 -0
- flowforge/decorators.py +167 -0
- flowforge/dev/__init__.py +5 -0
- flowforge/dev/server.py +303 -0
- flowforge/exceptions.py +116 -0
- flowforge/execution.py +201 -0
- flowforge/integrations/__init__.py +5 -0
- flowforge/integrations/fastapi.py +171 -0
- flowforge/network.py +142 -0
- flowforge/router.py +155 -0
- flowforge/steps.py +1077 -0
- flowforge/tools.py +282 -0
- flowforge/triggers.py +113 -0
- flowforge/worker.py +144 -0
- flowforge_sdk-0.1.0.dist-info/METADATA +84 -0
- flowforge_sdk-0.1.0.dist-info/RECORD +24 -0
- flowforge_sdk-0.1.0.dist-info/WHEEL +4 -0
flowforge/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""FlowForge SDK - Build reliable AI workflows with durable execution."""
|
|
2
|
+
|
|
3
|
+
from flowforge.client import FlowForge
|
|
4
|
+
from flowforge.context import Context, Event
|
|
5
|
+
from flowforge.decorators import function
|
|
6
|
+
from flowforge.steps import step
|
|
7
|
+
from flowforge.triggers import Trigger, trigger
|
|
8
|
+
from flowforge.tools import Tool, tool
|
|
9
|
+
from flowforge.agent import AgentConfig, AgentState, AgentResult
|
|
10
|
+
from flowforge.network import Network, NetworkState, NetworkResult, RouterContext, network
|
|
11
|
+
from flowforge.agent_def import AgentDefinition, agent_def
|
|
12
|
+
from flowforge import router
|
|
13
|
+
from flowforge.config import (
|
|
14
|
+
Concurrency,
|
|
15
|
+
RateLimit,
|
|
16
|
+
Throttle,
|
|
17
|
+
FunctionConfig,
|
|
18
|
+
concurrency,
|
|
19
|
+
rate_limit,
|
|
20
|
+
throttle,
|
|
21
|
+
)
|
|
22
|
+
from flowforge.exceptions import (
|
|
23
|
+
FlowForgeError,
|
|
24
|
+
StepError,
|
|
25
|
+
StepCompleted,
|
|
26
|
+
StepFailed,
|
|
27
|
+
StepTimeout,
|
|
28
|
+
RetryableError,
|
|
29
|
+
NonRetryableError,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__version__ = "0.1.0"
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Main client
|
|
36
|
+
"FlowForge",
|
|
37
|
+
# Context and events
|
|
38
|
+
"Context",
|
|
39
|
+
"Event",
|
|
40
|
+
# Decorators
|
|
41
|
+
"function",
|
|
42
|
+
# Steps
|
|
43
|
+
"step",
|
|
44
|
+
# Tools
|
|
45
|
+
"Tool",
|
|
46
|
+
"tool",
|
|
47
|
+
# Agent
|
|
48
|
+
"AgentConfig",
|
|
49
|
+
"AgentState",
|
|
50
|
+
"AgentResult",
|
|
51
|
+
# Network
|
|
52
|
+
"Network",
|
|
53
|
+
"NetworkState",
|
|
54
|
+
"NetworkResult",
|
|
55
|
+
"RouterContext",
|
|
56
|
+
"network",
|
|
57
|
+
"AgentDefinition",
|
|
58
|
+
"agent_def",
|
|
59
|
+
"router",
|
|
60
|
+
# Triggers
|
|
61
|
+
"Trigger",
|
|
62
|
+
"trigger",
|
|
63
|
+
# Configuration
|
|
64
|
+
"Concurrency",
|
|
65
|
+
"RateLimit",
|
|
66
|
+
"Throttle",
|
|
67
|
+
"FunctionConfig",
|
|
68
|
+
"concurrency",
|
|
69
|
+
"rate_limit",
|
|
70
|
+
"throttle",
|
|
71
|
+
# Exceptions
|
|
72
|
+
"FlowForgeError",
|
|
73
|
+
"StepError",
|
|
74
|
+
"StepCompleted",
|
|
75
|
+
"StepFailed",
|
|
76
|
+
"StepTimeout",
|
|
77
|
+
"RetryableError",
|
|
78
|
+
"NonRetryableError",
|
|
79
|
+
]
|
flowforge/agent.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Agent loop primitives for FlowForge."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from flowforge.tools import Tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class AgentConfig:
|
|
11
|
+
"""
|
|
12
|
+
Configuration for agent execution.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
model: Model to use (e.g., "claude-sonnet-4-20250514", "gpt-4o").
|
|
16
|
+
system: System prompt for the agent.
|
|
17
|
+
tools: List of tools the agent can use.
|
|
18
|
+
max_iterations: Maximum number of agent loop iterations.
|
|
19
|
+
checkpoint_strategy: When to checkpoint agent state.
|
|
20
|
+
max_tool_calls: Maximum number of tool calls across all iterations.
|
|
21
|
+
temperature: Sampling temperature for LLM.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
model: str
|
|
25
|
+
system: str = ""
|
|
26
|
+
tools: list[Tool] = field(default_factory=list)
|
|
27
|
+
max_iterations: int = 20
|
|
28
|
+
checkpoint_strategy: Literal["per_tool", "per_iteration", "final_only"] = "per_tool"
|
|
29
|
+
max_tool_calls: int = 50
|
|
30
|
+
temperature: float = 0.7
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AgentState:
|
|
35
|
+
"""
|
|
36
|
+
State of an agent during execution.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
messages: Conversation history including user, assistant, and tool messages.
|
|
40
|
+
iteration: Current iteration number.
|
|
41
|
+
tool_calls_count: Total number of tool calls made.
|
|
42
|
+
tokens_used: Total tokens used across all iterations.
|
|
43
|
+
status: Current status of the agent.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
messages: list[dict[str, Any]] = field(default_factory=list)
|
|
47
|
+
iteration: int = 0
|
|
48
|
+
tool_calls_count: int = 0
|
|
49
|
+
tokens_used: int = 0
|
|
50
|
+
status: Literal["running", "completed", "max_iterations", "max_tool_calls", "failed"] = "running"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class AgentResult:
|
|
55
|
+
"""
|
|
56
|
+
Result of agent execution.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
output: Final output text from the agent.
|
|
60
|
+
status: Final status of the agent execution.
|
|
61
|
+
iterations: Number of iterations completed.
|
|
62
|
+
tool_calls_count: Total number of tool calls made.
|
|
63
|
+
tokens_used: Total tokens used.
|
|
64
|
+
messages: Full conversation history.
|
|
65
|
+
tool_calls: Detailed list of all tool calls made.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
output: str
|
|
69
|
+
status: Literal["completed", "max_iterations", "max_tool_calls", "failed"]
|
|
70
|
+
iterations: int
|
|
71
|
+
tool_calls_count: int
|
|
72
|
+
tokens_used: int
|
|
73
|
+
messages: list[dict[str, Any]]
|
|
74
|
+
tool_calls: list[dict[str, Any]] = field(default_factory=list)
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> dict[str, Any]:
|
|
77
|
+
"""Convert result to dictionary for serialization."""
|
|
78
|
+
return {
|
|
79
|
+
"output": self.output,
|
|
80
|
+
"status": self.status,
|
|
81
|
+
"iterations": self.iterations,
|
|
82
|
+
"tool_calls_count": self.tool_calls_count,
|
|
83
|
+
"tokens_used": self.tokens_used,
|
|
84
|
+
"messages": self.messages,
|
|
85
|
+
"tool_calls": self.tool_calls,
|
|
86
|
+
}
|
flowforge/agent_def.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Agent definition for multi-agent networks."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from flowforge.tools import Tool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class AgentDefinition:
|
|
12
|
+
"""
|
|
13
|
+
Definition of an agent within a network.
|
|
14
|
+
|
|
15
|
+
Agents in a network are defined separately from AgentConfig to allow
|
|
16
|
+
networks to manage agent instances and routing independently.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
name: Unique identifier for the agent within the network.
|
|
20
|
+
system: System prompt for the agent.
|
|
21
|
+
tools: List of tools available to the agent.
|
|
22
|
+
model: Model to use (overrides network default if specified).
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
classifier = AgentDefinition(
|
|
26
|
+
name="classifier",
|
|
27
|
+
system="You classify customer inquiries...",
|
|
28
|
+
tools=[classify_tool],
|
|
29
|
+
model="gpt-4o-mini",
|
|
30
|
+
)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
name: str
|
|
34
|
+
system: str
|
|
35
|
+
tools: list["Tool"] = field(default_factory=list)
|
|
36
|
+
model: str | None = None # Override network default
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def agent_def(
|
|
40
|
+
name: str,
|
|
41
|
+
system: str,
|
|
42
|
+
tools: list["Tool"] | None = None,
|
|
43
|
+
model: str | None = None,
|
|
44
|
+
) -> AgentDefinition:
|
|
45
|
+
"""
|
|
46
|
+
Create an agent definition for use in networks.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
name: Unique agent name within the network.
|
|
50
|
+
system: System prompt for agent behavior.
|
|
51
|
+
tools: List of Tool instances the agent can use.
|
|
52
|
+
model: Model override (uses network default if None).
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
AgentDefinition ready for network inclusion.
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
from flowforge import agent_def, tool
|
|
59
|
+
|
|
60
|
+
@tool()
|
|
61
|
+
def search_kb(query: str) -> dict:
|
|
62
|
+
return {"results": [...]}
|
|
63
|
+
|
|
64
|
+
support = agent_def(
|
|
65
|
+
name="support",
|
|
66
|
+
system="Provide customer support using the knowledge base",
|
|
67
|
+
tools=[search_kb],
|
|
68
|
+
model="claude-sonnet-4-20250514",
|
|
69
|
+
)
|
|
70
|
+
"""
|
|
71
|
+
return AgentDefinition(
|
|
72
|
+
name=name,
|
|
73
|
+
system=system,
|
|
74
|
+
tools=tools or [],
|
|
75
|
+
model=model,
|
|
76
|
+
)
|
flowforge/ai/__init__.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""AI provider implementations for FlowForge."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AIProvider(ABC):
|
|
9
|
+
"""Base class for AI providers."""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
async def complete(
|
|
13
|
+
self,
|
|
14
|
+
model: str,
|
|
15
|
+
messages: list[dict[str, str]],
|
|
16
|
+
max_tokens: int = 1000,
|
|
17
|
+
temperature: float = 0.7,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Generate a completion from the AI model.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
model: Model identifier.
|
|
25
|
+
messages: List of message dicts with 'role' and 'content'.
|
|
26
|
+
max_tokens: Maximum tokens to generate.
|
|
27
|
+
temperature: Sampling temperature.
|
|
28
|
+
**kwargs: Additional provider-specific parameters.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dict with 'content', 'model', 'usage', and provider-specific data.
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OpenAIProvider(AIProvider):
|
|
37
|
+
"""OpenAI API provider."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, api_key: str | None = None) -> None:
|
|
40
|
+
self.api_key = api_key
|
|
41
|
+
|
|
42
|
+
async def complete(
|
|
43
|
+
self,
|
|
44
|
+
model: str,
|
|
45
|
+
messages: list[dict[str, str]],
|
|
46
|
+
max_tokens: int = 1000,
|
|
47
|
+
temperature: float = 0.7,
|
|
48
|
+
**kwargs: Any,
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
try:
|
|
51
|
+
from openai import AsyncOpenAI
|
|
52
|
+
except ImportError:
|
|
53
|
+
raise ImportError("openai package required. Install with: pip install openai")
|
|
54
|
+
|
|
55
|
+
client = AsyncOpenAI(api_key=self.api_key)
|
|
56
|
+
|
|
57
|
+
response = await client.chat.completions.create(
|
|
58
|
+
model=model,
|
|
59
|
+
messages=messages, # type: ignore
|
|
60
|
+
max_tokens=max_tokens,
|
|
61
|
+
temperature=temperature,
|
|
62
|
+
**kwargs,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
"content": response.choices[0].message.content,
|
|
67
|
+
"model": response.model,
|
|
68
|
+
"usage": {
|
|
69
|
+
"prompt_tokens": response.usage.prompt_tokens if response.usage else 0,
|
|
70
|
+
"completion_tokens": response.usage.completion_tokens if response.usage else 0,
|
|
71
|
+
"total_tokens": response.usage.total_tokens if response.usage else 0,
|
|
72
|
+
},
|
|
73
|
+
"finish_reason": response.choices[0].finish_reason,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AnthropicProvider(AIProvider):
|
|
78
|
+
"""Anthropic API provider."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, api_key: str | None = None) -> None:
|
|
81
|
+
self.api_key = api_key
|
|
82
|
+
|
|
83
|
+
async def complete(
|
|
84
|
+
self,
|
|
85
|
+
model: str,
|
|
86
|
+
messages: list[dict[str, str]],
|
|
87
|
+
max_tokens: int = 1000,
|
|
88
|
+
temperature: float = 0.7,
|
|
89
|
+
**kwargs: Any,
|
|
90
|
+
) -> dict[str, Any]:
|
|
91
|
+
try:
|
|
92
|
+
from anthropic import AsyncAnthropic
|
|
93
|
+
except ImportError:
|
|
94
|
+
raise ImportError("anthropic package required. Install with: pip install anthropic")
|
|
95
|
+
|
|
96
|
+
client = AsyncAnthropic(api_key=self.api_key)
|
|
97
|
+
|
|
98
|
+
# Extract system message if present
|
|
99
|
+
system = None
|
|
100
|
+
filtered_messages = []
|
|
101
|
+
for msg in messages:
|
|
102
|
+
if msg["role"] == "system":
|
|
103
|
+
system = msg["content"]
|
|
104
|
+
else:
|
|
105
|
+
filtered_messages.append(msg)
|
|
106
|
+
|
|
107
|
+
response = await client.messages.create(
|
|
108
|
+
model=model,
|
|
109
|
+
messages=filtered_messages, # type: ignore
|
|
110
|
+
max_tokens=max_tokens,
|
|
111
|
+
system=system or "",
|
|
112
|
+
**kwargs,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
"content": response.content[0].text if response.content else "",
|
|
117
|
+
"model": response.model,
|
|
118
|
+
"usage": {
|
|
119
|
+
"prompt_tokens": response.usage.input_tokens,
|
|
120
|
+
"completion_tokens": response.usage.output_tokens,
|
|
121
|
+
"total_tokens": response.usage.input_tokens + response.usage.output_tokens,
|
|
122
|
+
},
|
|
123
|
+
"stop_reason": response.stop_reason,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class LiteLLMProvider(AIProvider):
|
|
128
|
+
"""
|
|
129
|
+
LiteLLM provider for unified access to multiple AI providers.
|
|
130
|
+
|
|
131
|
+
Supports OpenAI, Anthropic, Cohere, Hugging Face, and many more
|
|
132
|
+
through a unified interface.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(self) -> None:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
async def complete(
|
|
139
|
+
self,
|
|
140
|
+
model: str,
|
|
141
|
+
messages: list[dict[str, str]],
|
|
142
|
+
max_tokens: int = 1000,
|
|
143
|
+
temperature: float = 0.7,
|
|
144
|
+
**kwargs: Any,
|
|
145
|
+
) -> dict[str, Any]:
|
|
146
|
+
try:
|
|
147
|
+
import litellm
|
|
148
|
+
except ImportError:
|
|
149
|
+
raise ImportError("litellm package required. Install with: pip install litellm")
|
|
150
|
+
|
|
151
|
+
response = await litellm.acompletion(
|
|
152
|
+
model=model,
|
|
153
|
+
messages=messages,
|
|
154
|
+
max_tokens=max_tokens,
|
|
155
|
+
temperature=temperature,
|
|
156
|
+
**kwargs,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
"content": response.choices[0].message.content,
|
|
161
|
+
"model": response.model,
|
|
162
|
+
"usage": {
|
|
163
|
+
"prompt_tokens": response.usage.prompt_tokens if response.usage else 0,
|
|
164
|
+
"completion_tokens": response.usage.completion_tokens if response.usage else 0,
|
|
165
|
+
"total_tokens": response.usage.total_tokens if response.usage else 0,
|
|
166
|
+
},
|
|
167
|
+
"finish_reason": response.choices[0].finish_reason,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_provider(provider_name: str | None = None, model: str | None = None) -> AIProvider:
|
|
172
|
+
"""
|
|
173
|
+
Get an AI provider instance.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
provider_name: Explicit provider name ('openai', 'anthropic', 'litellm').
|
|
177
|
+
model: Model name to auto-detect provider from.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
AIProvider instance.
|
|
181
|
+
"""
|
|
182
|
+
if provider_name:
|
|
183
|
+
provider_name = provider_name.lower()
|
|
184
|
+
if provider_name == "openai":
|
|
185
|
+
return OpenAIProvider()
|
|
186
|
+
elif provider_name == "anthropic":
|
|
187
|
+
return AnthropicProvider()
|
|
188
|
+
elif provider_name == "litellm":
|
|
189
|
+
return LiteLLMProvider()
|
|
190
|
+
else:
|
|
191
|
+
# Default to LiteLLM for unknown providers
|
|
192
|
+
return LiteLLMProvider()
|
|
193
|
+
|
|
194
|
+
# Auto-detect from model name
|
|
195
|
+
if model:
|
|
196
|
+
model_lower = model.lower()
|
|
197
|
+
if model_lower.startswith("gpt") or model_lower.startswith("o1"):
|
|
198
|
+
return OpenAIProvider()
|
|
199
|
+
elif model_lower.startswith("claude"):
|
|
200
|
+
return AnthropicProvider()
|
|
201
|
+
|
|
202
|
+
# Default to LiteLLM
|
|
203
|
+
return LiteLLMProvider()
|