thoughtflow 0.0.2__py3-none-any.whl → 0.0.4__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.
thoughtflow/action.py ADDED
@@ -0,0 +1,357 @@
1
+ """
2
+ ACTION class for ThoughtFlow.
3
+
4
+ The ACTION class encapsulates an external or internal operation that can be invoked
5
+ within a Thoughtflow agent workflow.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+
12
+ from thoughtflow._util import event_stamp
13
+
14
+
15
+ class ACTION:
16
+ """
17
+ The ACTION class encapsulates an external or internal operation that can be invoked within a Thoughtflow agent.
18
+ It is designed to represent a single, named action (such as a tool call, API request, or function) whose result
19
+ is stored in the agent's state for later inspection, branching, or retry.
20
+
21
+ An ACTION represents a discrete, named operation (function, API call, tool invocation) that can be defined once
22
+ and executed multiple times with different parameters. When executed, the ACTION handles logging, error management,
23
+ and result storage in a consistent way.
24
+
25
+ Attributes:
26
+ name (str): Identifier for this action, used for logging and storing results.
27
+ id (str): Unique identifier for this action instance (event_stamp).
28
+ fn (callable): The function to execute when this action is called.
29
+ config (dict): Default configuration parameters that will be passed to the function.
30
+ result_key (str): Key where results are stored in memory (defaults to "{name}_result").
31
+ description (str): Human-readable description of what this action does.
32
+ last_result (Any): The most recent result from executing this action.
33
+ last_error (Exception): The most recent error from executing this action, if any.
34
+ execution_count (int): Number of times this action has been executed.
35
+ execution_history (list): Full execution history with timing and success/error tracking.
36
+
37
+ Methods:
38
+ __init__(name, fn, config=None, result_key=None, description=None):
39
+ Initializes an ACTION with a name, function, and optional configuration.
40
+
41
+ __call__(memory, **kwargs):
42
+ Executes the action function with the memory object and any override parameters.
43
+ The function receives (memory, **merged_kwargs) where merged_kwargs combines
44
+ self.config with any call-specific kwargs.
45
+
46
+ Returns the memory object with results stored via set_var.
47
+ Logs execution details with JSON-formatted event data.
48
+ Tracks execution timing and history.
49
+
50
+ Handles exceptions during execution by logging them rather than raising them,
51
+ allowing the workflow to continue and decide how to handle failures.
52
+
53
+ get_last_result():
54
+ Returns the most recent result from executing this action.
55
+
56
+ was_successful():
57
+ Returns True if the last execution was successful, False otherwise.
58
+
59
+ reset_stats():
60
+ Resets execution statistics (count, last_result, last_error, execution_history).
61
+
62
+ copy():
63
+ Returns a copy of this ACTION with a new ID and reset statistics.
64
+
65
+ to_dict():
66
+ Returns a serializable dictionary representation of this action.
67
+
68
+ from_dict(cls, data, fn_registry):
69
+ Class method to reconstruct an ACTION from a dictionary representation.
70
+
71
+ Example Usage:
72
+ # Define a web search action
73
+ def search_web(memory, query, max_results=3):
74
+ # Implementation of web search
75
+ results = web_api.search(query, limit=max_results)
76
+ return {"status": "success", "hits": results}
77
+
78
+ search_action = ACTION(
79
+ name="web_search",
80
+ fn=search_web,
81
+ config={"max_results": 5},
82
+ description="Searches the web for information"
83
+ )
84
+
85
+ # Execute the action
86
+ memory = MEMORY()
87
+ memory = search_action(memory, query="thoughtflow framework")
88
+
89
+ # Access results
90
+ result = memory.get_var("web_search_result")
91
+
92
+ # Check execution history
93
+ print(search_action.execution_history[-1]['duration_ms']) # Execution time
94
+ print(search_action.execution_history[-1]['success']) # True/False
95
+
96
+ Design Principles:
97
+ 1. Explicit and inspectable operations with consistent logging
98
+ 2. Predictable result storage via memory.set_var
99
+ 3. Error handling that doesn't interrupt workflow execution
100
+ 4. Composability with other Thoughtflow components (MEMORY, THOUGHT)
101
+ 5. Serialization support for reproducibility
102
+ 6. Full execution history with timing for debugging and optimization
103
+ """
104
+
105
+ def __init__(self, name, fn, config=None, result_key=None, description=None):
106
+ """
107
+ Initialize an ACTION with a name, function, and optional configuration.
108
+
109
+ Args:
110
+ name (str): Identifier for this action, used for logging and result storage.
111
+ fn (callable): The function to execute when this action is called.
112
+ config (dict, optional): Default configuration parameters passed to the function.
113
+ result_key (str, optional): Key where results are stored in memory (defaults to "{name}_result").
114
+ description (str, optional): Human-readable description of what this action does.
115
+ """
116
+ self.name = name
117
+ self.id = event_stamp() # Unique identifier for this action instance
118
+ self.fn = fn
119
+ self.config = config or {}
120
+ self.result_key = result_key or "{}_result".format(name)
121
+ self.description = description or "Action: {}".format(name)
122
+ self.last_result = None
123
+ self.last_error = None
124
+ self.execution_count = 0
125
+ self.execution_history = [] # Full execution tracking with timing
126
+
127
+ def __call__(self, memory, **kwargs):
128
+ """
129
+ Execute the action function with the memory object and any override parameters.
130
+
131
+ Args:
132
+ memory (MEMORY): The memory object to update with results.
133
+ **kwargs: Parameters that override the default config for this execution.
134
+
135
+ Returns:
136
+ MEMORY: The updated memory object with results stored in memory.vars[result_key].
137
+
138
+ Note:
139
+ The function receives (memory, **merged_kwargs) where merged_kwargs combines
140
+ self.config with any call-specific kwargs.
141
+
142
+ Exceptions during execution are logged rather than raised, allowing the
143
+ workflow to continue and decide how to handle failures.
144
+ """
145
+ import time as time_module
146
+
147
+ start_time = time_module.time()
148
+
149
+ # Merge default config with call-specific kwargs
150
+ merged_kwargs = {**self.config, **kwargs}
151
+ self.execution_count += 1
152
+
153
+ try:
154
+ # Execute the function
155
+ result = self.fn(memory, **merged_kwargs)
156
+ self.last_result = result
157
+ self.last_error = None
158
+
159
+ # Calculate execution duration
160
+ duration_ms = (time_module.time() - start_time) * 1000
161
+
162
+ # Store result in memory using set_var (correct API)
163
+ if hasattr(memory, "set_var") and callable(getattr(memory, "set_var", None)):
164
+ memory.set_var(self.result_key, result, desc="Result of action: {}".format(self.name))
165
+
166
+ # Build execution event for logging (JSON format like THOUGHT)
167
+ execution_event = {
168
+ 'action_name': self.name,
169
+ 'action_id': self.id,
170
+ 'status': 'success',
171
+ 'duration_ms': round(duration_ms, 2),
172
+ 'result_key': self.result_key
173
+ }
174
+
175
+ # Log successful execution (single message with JSON, no invalid details param)
176
+ if hasattr(memory, "add_log") and callable(getattr(memory, "add_log", None)):
177
+ memory.add_log("Action execution complete: " + json.dumps(execution_event))
178
+
179
+ # Track execution history
180
+ self.execution_history.append({
181
+ 'stamp': event_stamp(),
182
+ 'memory_id': getattr(memory, 'id', None),
183
+ 'duration_ms': duration_ms,
184
+ 'success': True,
185
+ 'error': None
186
+ })
187
+
188
+ except Exception as e:
189
+ # Handle and log exceptions
190
+ self.last_error = e
191
+
192
+ # Calculate execution duration
193
+ duration_ms = (time_module.time() - start_time) * 1000
194
+
195
+ # Build error event for logging
196
+ error_event = {
197
+ 'action_name': self.name,
198
+ 'action_id': self.id,
199
+ 'status': 'error',
200
+ 'error': str(e),
201
+ 'duration_ms': round(duration_ms, 2),
202
+ 'result_key': self.result_key
203
+ }
204
+
205
+ # Log failed execution (single message with JSON)
206
+ if hasattr(memory, "add_log") and callable(getattr(memory, "add_log", None)):
207
+ memory.add_log("Action execution failed: " + json.dumps(error_event))
208
+
209
+ # Store error info in memory using set_var
210
+ if hasattr(memory, "set_var") and callable(getattr(memory, "set_var", None)):
211
+ memory.set_var(self.result_key, error_event, desc="Error in action: {}".format(self.name))
212
+
213
+ # Track execution history
214
+ self.execution_history.append({
215
+ 'stamp': event_stamp(),
216
+ 'memory_id': getattr(memory, 'id', None),
217
+ 'duration_ms': duration_ms,
218
+ 'success': False,
219
+ 'error': str(e)
220
+ })
221
+
222
+ return memory
223
+
224
+ def get_last_result(self):
225
+ """
226
+ Returns the most recent result from executing this action.
227
+
228
+ Returns:
229
+ Any: The last result or None if the action hasn't been executed.
230
+ """
231
+ return self.last_result
232
+
233
+ def was_successful(self):
234
+ """
235
+ Returns True if the last execution was successful, False otherwise.
236
+
237
+ Returns:
238
+ bool: True if the last execution completed without errors, False otherwise.
239
+ """
240
+ return self.last_error is None and self.execution_count > 0
241
+
242
+ def reset_stats(self):
243
+ """
244
+ Resets execution statistics (count, last_result, last_error, execution_history).
245
+
246
+ Returns:
247
+ ACTION: Self for method chaining.
248
+ """
249
+ self.execution_count = 0
250
+ self.last_result = None
251
+ self.last_error = None
252
+ self.execution_history = []
253
+ return self
254
+
255
+ def copy(self):
256
+ """
257
+ Return a copy of this ACTION with a new ID.
258
+
259
+ The function reference is shared (same callable), but config is copied.
260
+ Execution statistics are reset in the copy.
261
+
262
+ Returns:
263
+ ACTION: A new ACTION instance with copied attributes and new ID.
264
+ """
265
+ new_action = ACTION(
266
+ name=self.name,
267
+ fn=self.fn, # Same function reference
268
+ config=self.config.copy() if self.config else None,
269
+ result_key=self.result_key,
270
+ description=self.description
271
+ )
272
+ # New ID is already assigned in __init__, no need to set it
273
+ return new_action
274
+
275
+ def to_dict(self):
276
+ """
277
+ Returns a serializable dictionary representation of this action.
278
+
279
+ Note: The function itself cannot be serialized, so it's represented by name.
280
+ When deserializing, a function registry must be provided.
281
+
282
+ Returns:
283
+ dict: Serializable representation of this action.
284
+ """
285
+ return {
286
+ "name": self.name,
287
+ "id": self.id,
288
+ "fn_name": self.fn.__name__,
289
+ "config": self.config,
290
+ "result_key": self.result_key,
291
+ "description": self.description,
292
+ "execution_count": self.execution_count,
293
+ "execution_history": self.execution_history
294
+ }
295
+
296
+ @classmethod
297
+ def from_dict(cls, data, fn_registry):
298
+ """
299
+ Reconstruct an ACTION from a dictionary representation.
300
+
301
+ Args:
302
+ data (dict): Dictionary representation of an ACTION.
303
+ fn_registry (dict): Dictionary mapping function names to function objects.
304
+
305
+ Returns:
306
+ ACTION: Reconstructed ACTION object.
307
+
308
+ Raises:
309
+ KeyError: If the function name is not found in the registry.
310
+ """
311
+ if data["fn_name"] not in fn_registry:
312
+ raise KeyError("Function '{}' not found in registry".format(data['fn_name']))
313
+
314
+ action = cls(
315
+ name=data["name"],
316
+ fn=fn_registry[data["fn_name"]],
317
+ config=data["config"],
318
+ result_key=data["result_key"],
319
+ description=data["description"]
320
+ )
321
+ # Restore ID if provided, otherwise keep the new one from __init__
322
+ if data.get("id"):
323
+ action.id = data["id"]
324
+ action.execution_count = data.get("execution_count", 0)
325
+ action.execution_history = data.get("execution_history", [])
326
+ return action
327
+
328
+ def __str__(self):
329
+ """
330
+ Returns a string representation of this action.
331
+
332
+ Returns:
333
+ str: String representation.
334
+ """
335
+ return "ACTION({}, desc='{}', executions={})".format(self.name, self.description, self.execution_count)
336
+
337
+ def __repr__(self):
338
+ """
339
+ Returns a detailed string representation of this action.
340
+
341
+ Returns:
342
+ str: Detailed string representation.
343
+ """
344
+ return ("ACTION(name='{}', fn={}, "
345
+ "config={}, result_key='{}', "
346
+ "description='{}', execution_count={})".format(
347
+ self.name, self.fn.__name__, self.config,
348
+ self.result_key, self.description, self.execution_count))
349
+
350
+
351
+ ### ACTION CLASS TESTS
352
+
353
+ ActionClassTests = """
354
+ # --- ACTION Class Tests ---
355
+
356
+
357
+ """
thoughtflow/agent.py CHANGED
@@ -1,147 +1,66 @@
1
1
  """
2
- Core Agent contract for ThoughtFlow.
3
-
4
- The Agent is the fundamental primitive - something that can be called
5
- with messages and parameters. Everything else is composition.
6
-
7
- Example:
8
- >>> adapter = OpenAIAdapter(api_key="...")
9
- >>> agent = Agent(adapter)
10
- >>> response = agent.call([{"role": "user", "content": "Hello"}])
2
+ DEPRECATED: Use THOUGHT class instead.
3
+
4
+ The Agent class has been replaced by the THOUGHT class which provides
5
+ a more powerful and flexible interface for LLM interactions.
6
+
7
+ Example migration:
8
+ # Old (deprecated):
9
+ agent = Agent(adapter)
10
+ response = agent.call(messages)
11
+
12
+ # New:
13
+ from thoughtflow import THOUGHT, MEMORY, LLM
14
+
15
+ llm = LLM("openai:gpt-4o", key="your-api-key")
16
+ thought = THOUGHT(name="my_thought", llm=llm, prompt="...")
17
+ memory = MEMORY()
18
+ memory = thought(memory)
19
+ result = memory.get_var("my_thought_result")
11
20
  """
12
21
 
13
22
  from __future__ import annotations
14
23
 
15
- from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
16
-
17
- if TYPE_CHECKING:
18
- from thoughtflow.adapters.base import Adapter
19
- from thoughtflow.message import MessageList
20
- from thoughtflow.trace.session import Session
21
-
22
-
23
- @runtime_checkable
24
- class AgentProtocol(Protocol):
25
- """Protocol defining the Agent contract.
26
-
27
- Any object implementing `call(msg_list, params)` is an Agent.
28
- """
29
-
30
- def call(
31
- self,
32
- msg_list: MessageList,
33
- params: dict[str, Any] | None = None,
34
- ) -> str:
35
- """Call the agent with a message list.
36
-
37
- Args:
38
- msg_list: List of messages in the conversation.
39
- params: Optional parameters (temperature, max_tokens, etc.)
40
-
41
- Returns:
42
- The agent's response as a string.
43
- """
44
- ...
24
+ import warnings
45
25
 
46
26
 
47
27
  class Agent:
48
- """Base Agent implementation.
49
-
50
- An Agent wraps an adapter and provides a simple `call` interface.
51
- This is the core primitive of ThoughtFlow - explicit, composable, testable.
52
-
53
- Attributes:
54
- adapter: The provider adapter to use for completions.
55
-
56
- Example:
57
- >>> from thoughtflow import Agent
58
- >>> from thoughtflow.adapters import OpenAIAdapter
59
- >>>
60
- >>> agent = Agent(OpenAIAdapter(api_key="..."))
61
- >>> response = agent.call([
62
- ... {"role": "system", "content": "You are helpful."},
63
- ... {"role": "user", "content": "Hello!"}
64
- ... ])
65
28
  """
66
-
67
- def __init__(self, adapter: Adapter) -> None:
68
- """Initialize the Agent with an adapter.
69
-
70
- Args:
71
- adapter: The provider adapter for making LLM calls.
72
- """
73
- self.adapter = adapter
74
-
75
- def call(
76
- self,
77
- msg_list: MessageList,
78
- params: dict[str, Any] | None = None,
79
- session: Session | None = None,
80
- ) -> str:
81
- """Call the agent with a message list.
82
-
83
- Args:
84
- msg_list: List of message dicts with 'role' and 'content' keys.
85
- params: Optional parameters (temperature, max_tokens, seed, etc.)
86
- session: Optional Session for tracing the call.
87
-
88
- Returns:
89
- The model's response as a string.
90
-
91
- Raises:
92
- NotImplementedError: This is a placeholder implementation.
93
- """
94
- # TODO: Implement actual adapter call
95
- # TODO: Add session tracing
29
+ DEPRECATED: Use THOUGHT class instead.
30
+
31
+ The Agent class has been deprecated in favor of the THOUGHT class,
32
+ which provides a more powerful and flexible interface for LLM interactions.
33
+ """
34
+
35
+ def __init__(self, *args, **kwargs):
36
+ warnings.warn(
37
+ "Agent is deprecated. Use THOUGHT instead. "
38
+ "See the migration guide in the module docstring.",
39
+ DeprecationWarning,
40
+ stacklevel=2
41
+ )
96
42
  raise NotImplementedError(
97
- "Agent.call() is not yet implemented. "
98
- "This is a placeholder for the ThoughtFlow alpha release."
43
+ "Agent is deprecated. Use THOUGHT instead. "
44
+ "Example: thought = THOUGHT(name='my_thought', llm=llm, prompt='...')"
99
45
  )
100
46
 
101
47
 
102
48
  class TracedAgent:
103
- """Agent wrapper that automatically traces all calls.
104
-
105
- Wraps any Agent and records inputs, outputs, timing, and metadata
106
- to a Session object for debugging, evaluation, and replay.
107
-
108
- Example:
109
- >>> from thoughtflow.trace import Session
110
- >>> session = Session()
111
- >>> traced = TracedAgent(agent, session)
112
- >>> response = traced.call(messages)
113
- >>> print(session.events) # See all recorded events
114
49
  """
115
-
116
- def __init__(self, agent: Agent, session: Session) -> None:
117
- """Initialize TracedAgent.
118
-
119
- Args:
120
- agent: The underlying agent to wrap.
121
- session: The session to record traces to.
122
- """
123
- self.agent = agent
124
- self.session = session
125
-
126
- def call(
127
- self,
128
- msg_list: MessageList,
129
- params: dict[str, Any] | None = None,
130
- ) -> str:
131
- """Call the agent and trace the execution.
132
-
133
- Args:
134
- msg_list: List of messages.
135
- params: Optional parameters.
136
-
137
- Returns:
138
- The agent's response.
139
-
140
- Raises:
141
- NotImplementedError: This is a placeholder implementation.
142
- """
143
- # TODO: Implement tracing wrapper
50
+ DEPRECATED: Use THOUGHT class instead.
51
+
52
+ The TracedAgent class has been deprecated. THOUGHT provides built-in
53
+ execution history tracking and tracing capabilities.
54
+ """
55
+
56
+ def __init__(self, *args, **kwargs):
57
+ warnings.warn(
58
+ "TracedAgent is deprecated. Use THOUGHT instead. "
59
+ "THOUGHT provides built-in execution history tracking.",
60
+ DeprecationWarning,
61
+ stacklevel=2
62
+ )
144
63
  raise NotImplementedError(
145
- "TracedAgent.call() is not yet implemented. "
146
- "This is a placeholder for the ThoughtFlow alpha release."
64
+ "TracedAgent is deprecated. Use THOUGHT instead. "
65
+ "THOUGHT provides built-in execution history tracking via execution_history."
147
66
  )