1+ """
2+ Dynamic handler for approaches and plugins - no hardcoding.
3+ """
4+ import importlib
5+ import importlib .util
6+ import logging
7+ import inspect
8+ from typing import Optional , Tuple , Dict , Any
9+ from pathlib import Path
10+
11+ logger = logging .getLogger (__name__ )
12+
13+ class ApproachHandler :
14+ """Dynamically handles both approaches and plugins"""
15+
16+ def __init__ (self ):
17+ self ._approaches_cache = {}
18+ self ._plugins_cache = {}
19+ self ._discovered = False
20+
21+ def handle (self , name : str , system_prompt : str , initial_query : str ,
22+ client , model : str , request_config : dict = None ) -> Optional [Tuple [str , int ]]:
23+ """
24+ Try to handle the given name as an approach or plugin.
25+ Returns None if not found, otherwise returns (response, tokens)
26+ """
27+ # Lazy discovery
28+ if not self ._discovered :
29+ self ._discover_handlers ()
30+ self ._discovered = True
31+
32+ # Check if it's an approach
33+ if name in self ._approaches_cache :
34+ logger .info (f"Routing approach '{ name } ' through proxy" )
35+ handler = self ._approaches_cache [name ]
36+ return self ._execute_handler (
37+ handler , system_prompt , initial_query , client , model , request_config
38+ )
39+
40+ # Check if it's a plugin
41+ if name in self ._plugins_cache :
42+ logger .info (f"Routing plugin '{ name } ' through proxy" )
43+ handler = self ._plugins_cache [name ]
44+ return self ._execute_handler (
45+ handler , system_prompt , initial_query , client , model , request_config
46+ )
47+
48+ logger .debug (f"'{ name } ' not recognized as approach or plugin" )
49+ return None
50+
51+ def _discover_handlers (self ):
52+ """Discover available approaches and plugins dynamically"""
53+
54+ # Discover approaches
55+ self ._discover_approaches ()
56+
57+ # Discover plugins
58+ self ._discover_plugins ()
59+
60+ logger .info (f"Discovered { len (self ._approaches_cache )} approaches, "
61+ f"{ len (self ._plugins_cache )} plugins" )
62+
63+ def _discover_approaches (self ):
64+ """Discover built-in approaches from optillm package"""
65+ approach_modules = {
66+ 'mcts' : ('optillm.mcts' , 'chat_with_mcts' ),
67+ 'bon' : ('optillm.bon' , 'best_of_n_sampling' ),
68+ 'moa' : ('optillm.moa' , 'mixture_of_agents' ),
69+ 'rto' : ('optillm.rto' , 'round_trip_optimization' ),
70+ 'self_consistency' : ('optillm.self_consistency' , 'advanced_self_consistency_approach' ),
71+ 'pvg' : ('optillm.pvg' , 'inference_time_pv_game' ),
72+ 'z3' : ('optillm.z3_solver' , None ), # Special case
73+ 'rstar' : ('optillm.rstar' , None ), # Special case
74+ 'cot_reflection' : ('optillm.cot_reflection' , 'cot_reflection' ),
75+ 'plansearch' : ('optillm.plansearch' , 'plansearch' ),
76+ 'leap' : ('optillm.leap' , 'leap' ),
77+ 're2' : ('optillm.reread' , 're2_approach' ),
78+ 'cepo' : ('optillm.cepo.cepo' , 'cepo' ), # CEPO approach
79+ }
80+
81+ for name , (module_path , func_name ) in approach_modules .items ():
82+ try :
83+ module = importlib .import_module (module_path )
84+
85+ if name == 'z3' :
86+ # Special handling for Z3
87+ solver_class = getattr (module , 'Z3SymPySolverSystem' )
88+ self ._approaches_cache [name ] = lambda s , q , c , m , ** kw : \
89+ solver_class (s , c , m ).process_query (q )
90+ elif name == 'rstar' :
91+ # Special handling for RStar
92+ rstar_class = getattr (module , 'RStar' )
93+ self ._approaches_cache [name ] = lambda s , q , c , m , ** kw : \
94+ rstar_class (s , c , m , ** kw ).solve (q )
95+ elif name == 'cepo' :
96+ # Special handling for CEPO which needs special config
97+ cepo_func = getattr (module , func_name )
98+ # We'll pass empty CepoConfig for now - it can be enhanced later
99+ self ._approaches_cache [name ] = cepo_func
100+ else :
101+ if func_name :
102+ self ._approaches_cache [name ] = getattr (module , func_name )
103+
104+ except (ImportError , AttributeError ) as e :
105+ logger .debug (f"Could not load approach '{ name } ': { e } " )
106+
107+ def _discover_plugins (self ):
108+ """Discover available plugins dynamically"""
109+ try :
110+ import optillm
111+ import os
112+ import glob
113+
114+ # Get plugin directories
115+ package_dir = Path (optillm .__file__ ).parent / 'plugins'
116+
117+ # Find all Python files in plugins directory
118+ plugin_files = []
119+ if package_dir .exists ():
120+ plugin_files .extend (glob .glob (str (package_dir / '*.py' )))
121+
122+ for plugin_file in plugin_files :
123+ if '__pycache__' in plugin_file or '__init__' in plugin_file :
124+ continue
125+
126+ try :
127+ # Extract module name
128+ module_name = Path (plugin_file ).stem
129+
130+ # Skip self
131+ if module_name == 'proxy_plugin' :
132+ continue
133+
134+ # Import module
135+ spec = importlib .util .spec_from_file_location (module_name , plugin_file )
136+ if spec and spec .loader :
137+ module = importlib .util .module_from_spec (spec )
138+ spec .loader .exec_module (module )
139+
140+ # Check if it has required attributes
141+ if hasattr (module , 'SLUG' ) and hasattr (module , 'run' ):
142+ slug = getattr (module , 'SLUG' )
143+ run_func = getattr (module , 'run' )
144+ self ._plugins_cache [slug ] = run_func
145+
146+ except Exception as e :
147+ logger .debug (f"Could not load plugin from { plugin_file } : { e } " )
148+
149+ except Exception as e :
150+ logger .debug (f"Error discovering plugins: { e } " )
151+
152+ def _execute_handler (self , handler , system_prompt : str , initial_query : str ,
153+ client , model : str , request_config : dict = None ) -> Tuple [str , int ]:
154+ """Execute a handler function with proper signature detection"""
155+ try :
156+ # Check function signature
157+ sig = inspect .signature (handler )
158+ params = sig .parameters
159+
160+ # Build arguments based on signature
161+ args = [system_prompt , initial_query , client , model ]
162+ kwargs = {}
163+
164+ # Check if handler accepts request_config
165+ if 'request_config' in params :
166+ kwargs ['request_config' ] = request_config
167+
168+ # Some handlers may accept additional kwargs
169+ if any (p .kind == inspect .Parameter .VAR_KEYWORD for p in params .values ()):
170+ # Only add safe kwargs that won't conflict
171+ if request_config :
172+ # Filter out parameters that might conflict
173+ safe_kwargs = {k : v for k , v in request_config .items ()
174+ if k not in ['model' , 'messages' , 'system_prompt' , 'initial_query' ]}
175+ kwargs .update (safe_kwargs )
176+
177+ # Execute handler
178+ return handler (* args , ** kwargs )
179+
180+ except Exception as e :
181+ logger .error (f"Error executing handler: { e } " )
182+ raise
0 commit comments