11import abc
22import argparse
3- from dataclasses import dataclass , field
3+ import typing
4+ from dataclasses import dataclass
5+ from rich .panel import Panel
46from typing import Dict , Type
57
6- from hackingBuddyGPT .utils .configurable import ParameterDefinitions , build_parser , get_arguments , get_class_parameters
8+ from hackingBuddyGPT .utils .configurable import ParameterDefinitions , build_parser , get_arguments , get_class_parameters , transparent
9+ from hackingBuddyGPT .utils .console .console import Console
10+ from hackingBuddyGPT .utils .db_storage .db_storage import DbStorage
711
12+
13+ @dataclass
14+ class Logger :
15+ log_db : DbStorage
16+ console : Console
17+ tag : str = ""
18+ run_id : int = 0
19+
20+
21+ @dataclass
822class UseCase (abc .ABC ):
923 """
1024 A UseCase is the combination of tools and capabilities to solve a specific problem.
@@ -16,13 +30,21 @@ class UseCase(abc.ABC):
1630 so that they can be automatically discovered and run from the command line.
1731 """
1832
33+ log_db : DbStorage
34+ console : Console
35+ tag : str = ""
36+
37+ _run_id : int = 0
38+ _log : Logger = None
39+
1940 def init (self ):
2041 """
2142 The init method is called before the run method. It is used to initialize the UseCase, and can be used to
2243 perform any dynamic setup that is needed before the run method is called. One of the most common use cases is
2344 setting up the llm capabilities from the tools that were injected.
2445 """
25- pass
46+ self ._run_id = self .log_db .create_new_run (self .get_name (), self .tag )
47+ self ._log = Logger (self .log_db , self .console , self .tag , self ._run_id )
2648
2749 @abc .abstractmethod
2850 def run (self ):
@@ -33,6 +55,46 @@ def run(self):
3355 """
3456 pass
3557
58+ @abc .abstractmethod
59+ def get_name (self ) -> str :
60+ """
61+ This method should return the name of the use case. It is used for logging and debugging purposes.
62+ """
63+ pass
64+
65+
66+ # this runs the main loop for a bounded amount of turns or until root was achieved
67+ @dataclass
68+ class AutonomousUseCase (UseCase , abc .ABC ):
69+ max_turns : int = 10
70+
71+ _got_root : bool = False
72+
73+ @abc .abstractmethod
74+ def perform_round (self , turn : int ):
75+ pass
76+
77+ def run (self ):
78+ turn = 1
79+ while turn <= self .max_turns and not self ._got_root :
80+ self ._log .console .log (f"[yellow]Starting turn { turn } of { self .max_turns } " )
81+
82+ self ._got_root = self .perform_round (turn )
83+
84+ # finish turn and commit logs to storage
85+ self ._log .log_db .commit ()
86+ turn += 1
87+
88+ # write the final result to the database and console
89+ if self ._got_root :
90+ self ._log .log_db .run_was_success (self ._run_id , turn )
91+ self ._log .console .print (Panel ("[bold green]Got Root!" , title = "Run finished" ))
92+ else :
93+ self ._log .log_db .run_was_failure (self ._run_id , turn )
94+ self ._log .console .print (Panel ("[green]maximum turn number reached" , title = "Run finished" ))
95+
96+ return self ._got_root
97+
3698
3799@dataclass
38100class _WrappedUseCase :
@@ -56,17 +118,55 @@ def __call__(self, args: argparse.Namespace):
56118use_cases : Dict [str , _WrappedUseCase ] = dict ()
57119
58120
59- def use_case (name : str , desc : str ):
60- """
61- By wrapping a UseCase with this decorator, it will be automatically discoverable and can be run from the command
62- line.
63- """
121+ T = typing .TypeVar ("T" )
122+
123+
124+ class AutonomousAgentUseCase (AutonomousUseCase , typing .Generic [T ]):
125+ agent : T = None
126+
127+ def perform_round (self , turn : int ):
128+ raise ValueError ("Do not use AutonomousAgentUseCase without supplying an agent type as generic" )
129+
130+ def get_name (self ) -> str :
131+ raise ValueError ("Do not use AutonomousAgentUseCase without supplying an agent type as generic" )
132+
133+ @classmethod
134+ def __class_getitem__ (cls , item ):
135+ item = dataclass (item )
136+ item .__parameters__ = get_class_parameters (item )
64137
65- def inner (cls : Type [UseCase ]):
138+ class AutonomousAgentUseCase (AutonomousUseCase ):
139+ agent : transparent (item ) = None
140+
141+ def init (self ):
142+ super ().init ()
143+ self .agent ._log = self ._log
144+ self .agent .init ()
145+
146+ def get_name (self ) -> str :
147+ return self .__class__ .__name__
148+
149+ def perform_round (self , turn : int ):
150+ return self .agent .perform_round (turn )
151+
152+ constructed_class = dataclass (AutonomousAgentUseCase )
153+
154+ return constructed_class
155+
156+
157+ def use_case (description ):
158+ def inner (cls ):
159+ name = cls .__name__ .removesuffix ("UseCase" )
66160 if name in use_cases :
67161 raise IndexError (f"Use case with name { name } already exists" )
68- use_cases [name ] = _WrappedUseCase (name , desc , cls , get_class_parameters (cls , name ))
162+ use_cases [name ] = _WrappedUseCase (name , description , cls , get_class_parameters (cls ))
163+ return inner
69164
70- return cls
71165
72- return inner
166+ def register_use_case (name : str , description : str , use_case : Type [UseCase ]):
167+ """
168+ This function is used to register a UseCase that was created manually, and not through the use_case decorator.
169+ """
170+ if name in use_cases :
171+ raise IndexError (f"Use case with name { name } already exists" )
172+ use_cases [name ] = _WrappedUseCase (name , description , use_case , get_class_parameters (use_case ))
0 commit comments