Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion agents/planner_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from utils.language import (
contains_cyrillic,
)

from settings.config_loader import (
config,
)

class PlannerNode(BaseNode):

Expand All @@ -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
)
Expand Down
18 changes: 7 additions & 11 deletions agents/translation_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 9 additions & 15 deletions clients/translation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_"
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions config.example.py
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -21,7 +22,7 @@

BRAIN_TEMPERATURE = 0.7

BRAIN_MAX_TOKENS = 2048
BRAIN_MAX_TOKENS = 8192

# ---------------------------------------------------------
# SERVICE MODEL
Expand All @@ -37,7 +38,7 @@

SERVICE_TEMPERATURE = 0.1

SERVICE_MAX_TOKENS = 1024
SERVICE_MAX_TOKENS = 4096

# ---------------------------------------------------------
# SEARCH
Expand Down
55 changes: 41 additions & 14 deletions memory/message_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
),
)

Expand Down Expand Up @@ -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(
Expand All @@ -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,
),
)

Expand Down Expand Up @@ -756,4 +766,21 @@ async def cancel_runtime_memory_update(
):
await task

context.runtime_memory_update_task = None
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 '<empty>'}"
)
13 changes: 8 additions & 5 deletions tests/test_agent_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,29 @@
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(
state,
context=None,
)

self.assertTrue(
self.assertFalse(
state.translate_input
)

self.assertEqual(
state.translated_input,
"привет",
)

self.assertEqual(
state.current_plan,
[
"translator",
"brain",
"validator",
],
Expand Down
8 changes: 7 additions & 1 deletion websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down