11import argparse
22
33from .factory import create_controller
4+ from .model_selection import (
5+ DEFAULT_INTERACTIVE_MODEL ,
6+ DEFAULT_INTERACTIVE_PROVIDER ,
7+ DEFAULT_OLLAMA_HOST ,
8+ ModelSelection ,
9+ parse_model_command ,
10+ provider_registry ,
11+ render_model_selection ,
12+ )
413from .session import AgentSession
514
615
1019 /rules Show rule toggles.
1120 /rule on KEY
1221 /rule off KEY
22+ /models Show model providers.
23+ /model Show current model.
24+ /model PROVIDER MODEL
25+ /model provider=PROVIDER model=MODEL [api_base=URL] [api_key=KEY]
1326 /workflows Show workflow presets.
1427 /workflow KEY
1528 /loop [goal]
1629 /search [query]
30+ /release [goal]
1731 /history Show the current session transcript.
1832 /exit Quit.
1933"""
@@ -29,10 +43,10 @@ def run_chat(argv=None) -> int:
2943 parser .add_argument (
3044 "--provider" ,
3145 choices = ["rule" , "ollama" , "openai" , "openai-compatible" , "gemini" , "localai" ],
32- default = "rule" ,
46+ default = DEFAULT_INTERACTIVE_PROVIDER ,
3347 )
3448 parser .add_argument ("--model" , default = None )
35- parser .add_argument ("--ollama-host" , default = "http://127.0.0.1:11434" )
49+ parser .add_argument ("--ollama-host" , default = DEFAULT_OLLAMA_HOST )
3650 parser .add_argument ("--api-base" , default = None )
3751 parser .add_argument ("--api-key" , default = None )
3852 parser .add_argument ("--write-root" , action = "append" , default = [])
@@ -48,29 +62,46 @@ def run_chat(argv=None) -> int:
4862 args = parser .parse_args (argv )
4963
5064 write_roots = ["outputs" , * args .write_root ]
51- controller = create_controller (
52- workspace = args .workspace ,
53- memory_path = args .memory ,
54- trace_path = args .trace ,
55- db_path = args .db ,
56- max_steps = args .max_steps ,
65+ model_name = args .model
66+ if args .provider == DEFAULT_INTERACTIVE_PROVIDER and model_name is None :
67+ model_name = DEFAULT_INTERACTIVE_MODEL
68+ current_model = ModelSelection .from_values (
5769 provider = args .provider ,
58- model_name = args . model ,
70+ model_name = model_name ,
5971 ollama_host = args .ollama_host ,
6072 api_base = args .api_base ,
6173 api_key = args .api_key ,
62- write_roots = write_roots ,
63- approval_required_roots = args .approval_root ,
64- enable_network_tools = args .enable_network_tools ,
65- enabled_rule_keys = args .rule ,
66- disabled_rule_keys = args .no_rule ,
67- workflow_key = args .workflow ,
6874 )
75+
76+ def make_controller (model_selection : ModelSelection ):
77+ return create_controller (
78+ workspace = args .workspace ,
79+ memory_path = args .memory ,
80+ trace_path = args .trace ,
81+ db_path = args .db ,
82+ max_steps = args .max_steps ,
83+ provider = model_selection .provider ,
84+ model_name = model_selection .model_name ,
85+ ollama_host = model_selection .ollama_host or args .ollama_host ,
86+ api_base = model_selection .api_base ,
87+ api_key = model_selection .api_key ,
88+ write_roots = write_roots ,
89+ approval_required_roots = args .approval_root ,
90+ enable_network_tools = args .enable_network_tools ,
91+ enabled_rule_keys = args .rule ,
92+ disabled_rule_keys = args .no_rule ,
93+ workflow_key = args .workflow ,
94+ )
95+
96+ controller = make_controller (current_model )
6997 session = AgentSession (controller )
7098 current_workflow = args .workflow
7199
72100 print ("agentic-loop chat" )
73101 print ("Type /help for commands, /exit to quit." )
102+ print (f"model: { render_model_selection (current_model )} " )
103+ if current_model .provider == "rule" :
104+ print ("rule mode is deterministic/offline. Use /models and /model PROVIDER MODEL for a real chat model." )
74105
75106 while True :
76107 try :
@@ -92,6 +123,29 @@ def run_chat(argv=None) -> int:
92123 if user_input == "/tools" :
93124 print (", " .join (sorted (controller .tools .names ())))
94125 continue
126+ if user_input == "/models" :
127+ _print_models ()
128+ continue
129+ if user_input == "/model" :
130+ print (f"model: { render_model_selection (current_model )} " )
131+ continue
132+ if user_input .startswith ("/model " ):
133+ requested = user_input .split (maxsplit = 1 )[1 ].strip ()
134+ try :
135+ current_model = parse_model_command (requested , fallback = current_model )
136+ old_history = session .history
137+ controller = make_controller (current_model )
138+ session = AgentSession (
139+ controller ,
140+ history = old_history ,
141+ session_id = session .session_id ,
142+ storage = session .storage ,
143+ )
144+ except ValueError as exc :
145+ print (f"model error: { exc } " )
146+ continue
147+ print (f"model: { render_model_selection (current_model )} " )
148+ continue
95149 if user_input == "/rules" :
96150 _print_rules (controller )
97151 continue
@@ -114,35 +168,34 @@ def run_chat(argv=None) -> int:
114168 current_workflow = workflow .key
115169 print (f"workflow active: { workflow .command } " )
116170 continue
117- if user_input == "/loop" or user_input .startswith ("/loop " ):
118- rest = user_input [len ("/loop" ):].strip ()
119- if rest :
120- result = session .ask (rest , workflow_key = "loop" )
121- print (result .final_answer )
122- else :
123- current_workflow = "loop"
124- print ("workflow active: /loop" )
125- continue
126- if user_input == "/search" or user_input .startswith ("/search " ):
127- rest = user_input [len ("/search" ):].strip ()
171+ shortcut = _workflow_shortcut (controller , user_input )
172+ if shortcut is not None :
173+ workflow , rest = shortcut
128174 if rest :
129- result = session .ask (rest , workflow_key = "search" )
130- print (result .final_answer )
175+ _ask_and_print (session , rest , workflow_key = workflow .key )
131176 else :
132- current_workflow = "search"
133- print ("workflow active: /search " )
177+ current_workflow = workflow . key
178+ print (f "workflow active: { workflow . command } " )
134179 continue
135180 if user_input == "/history" :
136181 for item in session .transcript ():
137182 print (f"{ item ['role' ]} : { item ['content' ]} " )
138183 continue
139184
140- result = session .ask (user_input , workflow_key = current_workflow )
141- print (result .final_answer )
185+ _ask_and_print (session , user_input , workflow_key = current_workflow )
142186
143187 return 0
144188
145189
190+ def _ask_and_print (session : AgentSession , message : str , workflow_key : str | None = None ) -> None :
191+ try :
192+ result = session .ask (message , workflow_key = workflow_key )
193+ except Exception as exc :
194+ print (f"model error: { exc } " )
195+ return
196+ print (result .final_answer )
197+
198+
146199def _print_rules (controller ) -> None :
147200 for rule in controller .rule_resolver .rules_with_state ():
148201 status = "on" if rule ["enabled" ] else "off"
@@ -154,6 +207,24 @@ def _print_workflows(controller) -> None:
154207 print (f"{ workflow .command } - { workflow .description } " )
155208
156209
210+ def _workflow_shortcut (controller , user_input : str ):
211+ command = user_input .split (maxsplit = 1 )[0 ]
212+ if not command .startswith ("/" ):
213+ return None
214+ workflow = controller .rule_resolver .workflow (command )
215+ if workflow is None or workflow .command != command :
216+ return None
217+ rest = user_input [len (command ):].strip ()
218+ return workflow , rest
219+
220+
221+ def _print_models () -> None :
222+ for item in provider_registry ():
223+ required = "model required" if item .get ("model_required" ) else "model optional"
224+ api_base = "api_base required" if item .get ("api_base_required" ) else "api_base optional"
225+ print (f"{ item ['provider' ]} : { required } , { api_base } - { item ['description' ]} " )
226+
227+
157228def _handle_rule_command (controller , user_input : str ) -> None :
158229 parts = user_input .split ()
159230 if len (parts ) != 3 or parts [1 ] not in {"on" , "off" }:
0 commit comments