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 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
+ )
@@ -0,0 +1,5 @@
1
+ """AI provider integrations for FlowForge."""
2
+
3
+ from flowforge.ai.providers import AIProvider, get_provider
4
+
5
+ __all__ = ["AIProvider", "get_provider"]
@@ -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()