diff --git a/agents/planner_node.py b/agents/planner_node.py index df64239..e3c3ada 100644 --- a/agents/planner_node.py +++ b/agents/planner_node.py @@ -3,7 +3,9 @@ from utils.language import ( contains_cyrillic, ) - +from settings.config_loader import ( + config, +) class PlannerNode(BaseNode): @@ -13,6 +15,18 @@ async def run( context, ): + if not getattr(config, "TRANSLATION_ENABLED", False): + state.current_plan = [ + "brain", + "validator", + ] + + state.translate_input = False + state.translated_input = state.user_input + + + return + state.translate_input = contains_cyrillic( state.user_input ) diff --git a/agents/translation_node.py b/agents/translation_node.py index 2867066..b8ead6a 100644 --- a/agents/translation_node.py +++ b/agents/translation_node.py @@ -24,22 +24,18 @@ async def run( state.iteration += 1 translated = await translate( - client=context.clients[ - "translator" - ], + context=context, text=state.user_input, source_language="Russian", target_language="English", ) - translated_text = translated[ - "content" - ] - - usage = translated.get( - "usage", - {}, - ) + if isinstance(translated, str): + translated_text = translated + usage = {} + else: + translated_text = translated["content"] + usage = translated.get("usage", {}) await context.logger.log_translation( translated_text diff --git a/clients/translation_client.py b/clients/translation_client.py index 7931a0b..1b07e57 100644 --- a/clients/translation_client.py +++ b/clients/translation_client.py @@ -39,12 +39,14 @@ def build_translation_system_prompt( async def translate( *, - client, + context, text: str, source_language: str, target_language: str, ): - + client=context.clients[ + "translator" + ] stage = ( f"{source_language}" f"_to_" @@ -82,23 +84,15 @@ async def translate( ) if content: - return { "content": content, - "usage": ( - result.get( - "usage", - {}, - ) - ), + "usage": result.get("usage", {}), } - return ( - ResponseExtractor - .extract_reasoning_text( - result - ) - ) + return { + "content": text, + "usage": result.get("usage", {}), + } except asyncio.CancelledError: raise diff --git a/config.example.py b/config.example.py index e677788..2fa2c21 100644 --- a/config.example.py +++ b/config.example.py @@ -1,6 +1,7 @@ # Copy this file to config.py and adjust values for your local nodes. USE_SERVICE_AS_BRAIN = False +TRANSLATION_ENABLED = False CHAT_ENDPOINT = "/v1/chat/completions" MODELS_ENDPOINT = "/v1/models" @@ -21,7 +22,7 @@ BRAIN_TEMPERATURE = 0.7 -BRAIN_MAX_TOKENS = 2048 +BRAIN_MAX_TOKENS = 8192 # --------------------------------------------------------- # SERVICE MODEL @@ -37,7 +38,7 @@ SERVICE_TEMPERATURE = 0.1 -SERVICE_MAX_TOKENS = 1024 +SERVICE_MAX_TOKENS = 4096 # --------------------------------------------------------- # SEARCH diff --git a/memory/message_memory.py b/memory/message_memory.py index f02419b..06e5ff6 100644 --- a/memory/message_memory.py +++ b/memory/message_memory.py @@ -82,7 +82,12 @@ def build_runtime_memory_system_prompt() -> str: "Avoid writing about JIN's role unless the role itself changed or matters. " "Describe assistant actions neutrally instead.\n" "Keep memory actionable: write what helps the next answer, not a recap of " - "what happened.\n" + "what happened. \n" + "If there are unresolved pending choices or open references " + "that remain relevant to the current conversation, " + "you may naturally remind the user about them.\n" + "Do not interrupt a clearly established new topic. " + "Use reminders sparingly and only when they add value.\n" "Do not merge unrelated facts into one sentence. Prefer separate lines " "over broad phrasing like 'Topic established: X, specifically Y'.\n" "Finish every bullet line completely. Never leave a line mid-phrase.\n" @@ -406,8 +411,10 @@ async def summarize_runtime_memory( None, ), "[MEMORY] runtime memory update skipped", - details=( - "Summarizer returned an incomplete memory update." + details=build_memory_update_skip_details( + reason="Summarizer returned an incomplete memory update.", + previous_memory=current_memory, + candidate_memory=updated_memory, ), ) @@ -533,14 +540,15 @@ async def summarize_runtime_memory_pending_turns( response ) - if ( - is_runtime_memory_response_truncated( - response - ) - or looks_like_incomplete_runtime_memory( - updated_memory - ) - ): + skip_reason = None + + if is_runtime_memory_response_truncated(response): + skip_reason = "Summarizer response was truncated by max_tokens." + + elif looks_like_incomplete_runtime_memory(updated_memory): + skip_reason = "Summarizer returned text that looks structurally incomplete." + + if skip_reason: await safe_call( getattr( getattr( @@ -552,8 +560,10 @@ async def summarize_runtime_memory_pending_turns( None, ), "[MEMORY] runtime memory update skipped", - details=( - "Summarizer returned an incomplete memory update." + details=build_memory_update_skip_details( + reason="Summarizer returned an incomplete memory update.", + previous_memory=initial_memory, + candidate_memory=updated_memory, ), ) @@ -756,4 +766,21 @@ async def cancel_runtime_memory_update( ): await task - context.runtime_memory_update_task = None \ No newline at end of file + context.runtime_memory_update_task = None + +def build_memory_update_skip_details( + *, + reason: str, + previous_memory: str, + candidate_memory: str, +) -> str: + + return ( + f"{reason}\n\n" + "Previous memory:\n" + "----------------\n" + f"{previous_memory.strip() or DEFAULT_RUNTIME_MEMORY}\n\n" + "Candidate memory:\n" + "-----------------\n" + f"{candidate_memory.strip() or ''}" + ) \ No newline at end of file diff --git a/tests/test_agent_routing.py b/tests/test_agent_routing.py index 14eba22..cfe424b 100644 --- a/tests/test_agent_routing.py +++ b/tests/test_agent_routing.py @@ -16,11 +16,10 @@ class AgentRoutingTests( unittest.IsolatedAsyncioTestCase ): - - async def test_cyrillic_input_routes_through_translation(self): + async def test_cyrillic_input_routes_directly_to_brain(self): state = AgentState( - user_input="\u043f\u0440\u0438\u0432\u0435\u0442" + user_input="привет" ) await PlannerNode().run( @@ -28,14 +27,18 @@ async def test_cyrillic_input_routes_through_translation(self): context=None, ) - self.assertTrue( + self.assertFalse( state.translate_input ) + self.assertEqual( + state.translated_input, + "привет", + ) + self.assertEqual( state.current_plan, [ - "translator", "brain", "validator", ], diff --git a/websocket.py b/websocket.py index 92d04fc..e1753c4 100644 --- a/websocket.py +++ b/websocket.py @@ -255,10 +255,16 @@ async def process_message( "type": "agent_runtime_end", }) + assistant_message = ( + state.final_answer + or state.brain_response + or context.runtime_turn_assistant_response + ) + schedule_runtime_memory_update( context=context, user_message=user_text, - assistant_message=state.brain_response, + assistant_message=assistant_message, ) except asyncio.CancelledError: