Skip to content

Commit b89ab73

Browse files
committed
test: refactor pod/job into abstractions
Signed-off-by: vsoch <[email protected]>
1 parent 7987320 commit b89ab73

File tree

11 files changed

+567
-433
lines changed

11 files changed

+567
-433
lines changed

fractale/agent/base.py

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
import google.generativeai as genai
55

6-
from fractale.agent.decorators import callback, save_logs
76
import fractale.agent.defaults as defaults
87
import fractale.agent.logger as logger
98
import fractale.utils as utils
109
from fractale.agent.context import get_context
10+
from fractale.agent.decorators import callback, save_logs
1111

1212

1313
class Agent:
@@ -23,18 +23,17 @@ class Agent:
2323

2424
# name and description should be on the class
2525

26-
def __init__(self, use_cache=False, results_dir=None, incremental=False):
27-
28-
# Max attempts defaults to unlimited
29-
# We start counting at 1 for the user to see.
30-
# Eat your heart out, Matlab.
31-
self.attempts = 1
32-
self.max_attempts = None
26+
def __init__(
27+
self, use_cache=False, results_dir=None, save_incremental=False, max_attempts=None
28+
):
29+
self.attempts = 0
30+
self.max_attempts = max_attempts
3331

34-
# For now, assume this is for the manager.
32+
# For now, assume these are for the manager.
33+
# They get added to other agents via the step creation
3534
# We can optionally save incremental result objects
3635
self.results_dir = results_dir or os.getcwd()
37-
self.save_incremental = incremental
36+
self.save_incremental = save_incremental
3837

3938
# The user can save if desired - caching the context to skip steps that already run.
4039
self.setup_cache(use_cache)
@@ -50,9 +49,6 @@ def run(self, context):
5049
"""
5150
Run the agent - a wrapper around internal function _run that prepares it.
5251
"""
53-
# Init attempts. Each agent has an internal counter for total attempts
54-
self.attempts = self.attempts or 1
55-
5652
# Load cached context. This is assumed to override user provided args
5753
# If we have a saved context, we assume we want to use it, return early
5854
cached_context = self.load_cache()
@@ -66,6 +62,7 @@ def run(self, context):
6662
context = get_context(context)
6763

6864
# Run, wrapping with a load and save of cache
65+
# This will return here when the internal loop is done
6966
context = self._run(context)
7067
self.save_cache(context)
7168
return context
@@ -79,6 +76,17 @@ def print_result(self, result):
7976
"""
8077
pass
8178

79+
def reset_context(self, context):
80+
"""
81+
Remove output and any stateful variables. This is assuming we
82+
are starting again.
83+
"""
84+
for key in ["result", "error_message"]:
85+
if key in context.data:
86+
del context.data[key]
87+
# We don't need a return here, but let's be explicit
88+
return context
89+
8290
def setup_cache(self, use_cache=False):
8391
"""
8492
Setup (or load) a cache.
@@ -132,10 +140,7 @@ def reached_max_attempts(self):
132140
# Unset (None) or 1.
133141
if not self.max_attempts:
134142
return False
135-
return self.attempts >= self.max_attempts
136-
137-
def set_max_attempts(self, max_attempts):
138-
self.max_attempts = max_attempts
143+
return self.attempts > self.max_attempts
139144

140145
def add_shared_arguments(self, agent):
141146
"""
@@ -214,14 +219,6 @@ def _run(self, context):
214219
assert context
215220
raise NotImplementedError(f"The {self.name} agent is missing internal 'run' function")
216221

217-
def get_initial_prompt(self, context):
218-
"""
219-
Get the initial prompt (with details) to provide context to the manager.
220-
221-
If we don't do this, the manager can provide a bad instruction for how to fix the error.
222-
"""
223-
return self.get_prompt(context)
224-
225222
def get_prompt(self, context):
226223
"""
227224
This function should take the same context as run and return the parsed prompt that

fractale/agent/build/agent.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ def _add_arguments(self, subparser):
5757
)
5858
return build
5959

60+
def reset_context(self, context):
61+
"""
62+
Remove output and any stateful variables. This is assuming we
63+
are starting again.
64+
"""
65+
for key in ["result", "dockerfile", "error_message"]:
66+
if key in context.data:
67+
del context.data[key]
68+
# We don't need a return here, but let's be explicit
69+
return context
70+
6071
def get_prompt(self, context):
6172
"""
6273
Get the prompt for the LLM. We expose this so the manager can take it
@@ -125,6 +136,7 @@ def _run(self, context):
125136
agent = DebugAgent()
126137
# This updates the error message to be the output
127138
context = agent.run(context, requires=prompts.requires)
139+
print("\n[bold cyan] Requesting Correction from Build Agent[/bold cyan]")
128140

129141
# If we have reached the max attempts...
130142
if self.reached_max_attempts():
@@ -139,10 +151,9 @@ def _run(self, context):
139151
logger.exit(f"Max attempts {self.max_attempts} reached.", title="Agent Failure")
140152

141153
self.attempts += 1
142-
print("\n[bold cyan] Requesting Correction from Build Agent[/bold cyan]")
143154

144155
# Update the context with error message
145-
return self.run(context)
156+
return self._run(context)
146157

147158
# Add generation line
148159
self.write_file(context, context.result)
@@ -219,6 +230,17 @@ def build(self, context):
219230
shutil.rmtree(build_dir, ignore_errors=True)
220231
return (p.returncode, p.stdout + p.stderr)
221232

233+
def save_dockerfile(self, dockerfile):
234+
"""
235+
Save logs to metadata
236+
"""
237+
if self.save_incremental:
238+
if "steps" not in self.metadata:
239+
self.metadata["steps"] = []
240+
self.metadata["steps"].append(
241+
{"item": dockerfile, "type": "dockerfile", "attempt": self.attempts}
242+
)
243+
222244
def generate_dockerfile(self, context):
223245
"""
224246
Generates or refines a Dockerfile using the Gemini API.
@@ -241,6 +263,7 @@ def generate_dockerfile(self, context):
241263
dockerfile = match.group(1).strip()
242264
else:
243265
dockerfile = content.strip()
266+
self.save_dockerfile(dockerfile)
244267

245268
# The result is saved as a build step
246269
# The dockerfile is the argument used internally

fractale/agent/decorators.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,45 @@
11
import time
22

3+
34
def callback(callback_func):
45
"""
56
A decorator that executes a callback function after the decorated function.
67
78
We need this so that specific functions for the agent can return objects that save
89
one or more metadata items (automatically).
910
"""
11+
1012
def decorator(func):
1113
def wrapper(self, *args, **kwargs):
1214
# This is the original function
1315
start = time.time()
1416
result = func(self, *args, **kwargs)
1517
# Get the result and pass to the callback!
1618
end = time.time()
17-
callback_func(self, result, end-start)
19+
callback_func(self, result, end - start)
1820
return result
21+
1922
return wrapper
23+
2024
return decorator
2125

2226

2327
def save_logs(instance, context, elapsed_time):
2428
"""
2529
If defined (requested by the user) save the stage result.
2630
"""
27-
result = context.get('result')
31+
return save_general(instance, context, elapsed_time, "final-result")
32+
33+
34+
def save_general(instance, context, elapsed_time, result_type):
35+
"""
36+
Shared saving function.
37+
"""
38+
result = context.get("result")
2839
if not instance.save_incremental or not result:
2940
return
3041
if "logs" not in instance.metadata:
31-
instance.metadata['logs'] = []
32-
instance.metadata['logs'].append({"log": result, "elapsed_time_seconds": elapsed_time})
42+
instance.metadata["logs"] = []
43+
instance.metadata["logs"].append(
44+
{"item": result, "type": result_type, "total_seconds": elapsed_time}
45+
)

0 commit comments

Comments
 (0)