@@ -243,6 +243,56 @@ def conversation_question(message: str) -> bool:
243243 )
244244
245245
246+ def conversational_response (
247+ root : Path , client , message : str , mission : dict , plans : dict , responder : dict
248+ ) -> dict :
249+ """One actual model request with bounded factual context and no tool routing."""
250+ goal = (plans .get ("long" ) or {}).get ("text" , "No recorded goal" )
251+ facts = {
252+ "mission_running" : mission .get ("running" ),
253+ "phase" : mission .get ("state" , {}).get ("phase" ),
254+ "learning" : mission .get ("learning" ),
255+ "goal" : goal [:3000 ],
256+ }
257+ client .timeout = min (client .timeout , 20 )
258+ client .request_deadline = min (
259+ getattr (client , "request_deadline" , time .monotonic () + 40 ), time .monotonic () + 40
260+ )
261+ reply = client .request (
262+ "/v1/chat/completions" ,
263+ {
264+ "model" : client .model_name ,
265+ "messages" : [
266+ {
267+ "role" : "system" ,
268+ "content" : "You are Synta. Answer the user's question directly in Polish, briefly. This is read-only chat: do not claim to execute work or update plans. Only the supplied status is verified. Missing optimizer/profit counters are unknown. Explain how the recorded goal relates to the question, without changing it or promising profit. State missing evidence honestly. Verified current facts: "
269+ + json .dumps (facts , ensure_ascii = False ),
270+ },
271+ {"role" : "user" , "content" : message },
272+ ],
273+ "max_tokens" : 512 ,
274+ "temperature" : 0.0 ,
275+ "stream" : False ,
276+ ** client .template_args (),
277+ },
278+ )
279+ choice = reply ["choices" ][0 ]
280+ answer = choice ["message" ].get ("content" )
281+ if (
282+ choice .get ("finish_reason" ) not in ("stop" , "eos" )
283+ or not isinstance (answer , str )
284+ or not answer .strip ()
285+ ):
286+ raise ValueError ("Conversational model response incomplete; no invented answer accepted" )
287+ return {
288+ "answer" : answer .strip (),
289+ "actions" : [],
290+ "applied" : [],
291+ "responder" : responder ,
292+ "scope" : "Single model generation; read-only question, no tool execution or control changes" ,
293+ }
294+
295+
246296def respond (root : Path , directory : Path , request : dict , accepted_cpu = None ) -> dict :
247297 from rlm .v100 .agent import native_turn
248298 from rlm .v100 .competition import helper_client
@@ -255,6 +305,14 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
255305 # Authorization comes only from the current authenticated local user message,
256306 # never from a model-generated action or text retrieved from the internet.
257307 message = request ["message" ].strip ()
308+ quick_question = conversation_question (message ) or message .casefold () in (
309+ "hej" ,
310+ "cześć" ,
311+ "czesc" ,
312+ "hello" ,
313+ "hi" ,
314+ "odpowiedz" ,
315+ )
258316 if re .search (
259317 r"\b(?:co potrafisz|co umiesz|twoje możliwości|twoje mozliwosci|what can you do)\b" ,
260318 message .casefold (),
@@ -388,6 +446,9 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
388446 client .activity_actor = "chat"
389447 # The endpoint is reused for candidate evaluation. Chat waits rather than
390448 # talking to an unaccepted candidate or opening another GPU server.
449+ client .request_deadline = request .get ("deadline" , time .monotonic () + 180 )
450+ generation_timeout = min (getattr (client , "timeout" , 120 ), 60 )
451+ client .timeout = min (generation_timeout , 10 )
391452 delegated = False
392453 cpu_master = False
393454 cpu_detail = None
@@ -398,9 +459,14 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
398459 except (requests .RequestException , OSError ):
399460 if not profile .get ("resources" , {}).get ("interactive_lab" ):
400461 raise
401- if accepted_cpu is not None and profile .get ("resources" , {}).get ("accepted_cpu_chat" ):
462+ if (
463+ accepted_cpu is not None
464+ and not quick_question
465+ and profile .get ("resources" , {}).get ("accepted_cpu_chat" )
466+ ):
402467 try :
403468 client = accepted_cpu .get (profile )
469+ generation_timeout = min (client .timeout , 60 )
404470 cpu_master = True
405471 except (ValueError , OSError , RuntimeError , requests .RequestException ) as error :
406472 cpu_detail = str (error )[:300 ]
@@ -417,23 +483,39 @@ def respond(root: Path, directory: Path, request: dict, accepted_cpu=None) -> di
417483 "Master busy; no independent remote chat helper"
418484 ) from None
419485 client = helper_client (helper , root )
486+ client .request_deadline = request .get ("deadline" , time .monotonic () + 180 )
487+ generation_timeout = min (client .timeout , 60 )
488+ client .timeout = min (generation_timeout , 20 )
420489 client .identity ()
421490 client .sampling_args ["max_tokens" ] = 2048
422491 client .enable_thinking = False
423492 client .activity_actor = "chat-delegate"
424493 delegated = True
425494 client .request_deadline = request .get ("deadline" , time .monotonic () + 180 )
426- client .timeout = min ( client . timeout , 60 )
495+ client .timeout = generation_timeout
427496 client .enable_thinking = False
428497 client .sampling_args = dict (client .sampling_args )
429498 client .sampling_args ["max_tokens" ] = min (2048 , client .sampling_args .get ("max_tokens" , 2048 ))
430499 client .activity_context = {** client .activity_context , "chat_request_id" : request .get ("id" )}
431500 from rlm .v100 import chat_resources
432501
433502 resource_request = chat_resources .instruction (message )
434- question_only = conversation_question ( message ) and not resource_request
503+ question_only = quick_question and not resource_request
435504 if question_only :
436- client .sampling_args ["max_tokens" ] = min (768 , client .sampling_args ["max_tokens" ])
505+ return conversational_response (
506+ root ,
507+ client ,
508+ message ,
509+ mission ,
510+ read_plans (root ),
511+ {
512+ "model" : client .model_name ,
513+ "delegated_while_master_busy" : delegated ,
514+ "accepted_master_on_cpu" : cpu_master ,
515+ "accepted_model_sha256" : accepted_cpu .sha256 if cpu_master else None ,
516+ "cpu_admission_detail" : cpu_detail ,
517+ },
518+ )
437519 with connect (root ) as db :
438520 history = db .execute (
439521 "SELECT message,response FROM requests WHERE state='completed' ORDER BY rowid DESC LIMIT 3"
@@ -667,7 +749,11 @@ def service_loop(root: Path, directory: Path, stop: threading.Event, accepted_cp
667749 "decisions" , "user-command-completed" , {"id" : request ["id" ], ** response }
668750 )
669751 except (requests .ConnectionError , requests .Timeout ) as error :
670- expired = time .monotonic () >= request ["deadline" ] or isinstance (error , requests .Timeout )
752+ expired = (
753+ time .monotonic () >= request ["deadline" ]
754+ or isinstance (error , requests .Timeout )
755+ or request ["attempts" ] >= 1
756+ )
671757 with connect (root ) as db :
672758 db .execute (
673759 "UPDATE requests SET state=?,attempts=attempts+1,error=?,updated=? WHERE id=? AND state='queued'" ,
@@ -678,6 +764,16 @@ def service_loop(root: Path, directory: Path, stop: threading.Event, accepted_cp
678764 request ["id" ],
679765 ),
680766 )
767+ ActivityLog (root , "controller" , "chat" ).write (
768+ "errors" ,
769+ "user-command-failed" if expired else "user-command-retry" ,
770+ {
771+ "id" : request ["id" ],
772+ "error" : str (error )[:500 ],
773+ "attempts" : request ["attempts" ] + 1 ,
774+ "terminal" : expired ,
775+ },
776+ )
681777 except Exception as error :
682778 with connect (root ) as db :
683779 db .execute (
@@ -710,7 +806,7 @@ def alongside(root: Path, directory: Path):
710806 accepted_cpu .close ()
711807
712808
713- def wait_reply (root : Path , identity : str , timeout : float = 120 , progress = None ) -> dict :
809+ def wait_reply (root : Path , identity : str , timeout : float = 200 , progress = None ) -> dict :
714810 deadline = time .monotonic () + timeout
715811 next_update = time .monotonic ()
716812 while time .monotonic () < deadline :
0 commit comments