From da1a1fb6d9ee13190793d73c4d94dba936a68b2c Mon Sep 17 00:00:00 2001 From: Georgiy Tarasov Date: Thu, 13 Nov 2025 17:42:58 +0100 Subject: [PATCH] chore(ph-ai): increase cache ttl (#41458) --- ee/hogai/graph/agent_modes/nodes.py | 7 ++-- ee/hogai/graph/agent_modes/prompts.py | 10 ++--- ee/hogai/graph/agent_modes/test/test_nodes.py | 40 +++++++++++++++++++ ee/hogai/utils/anthropic.py | 15 ++++--- ee/hogai/utils/test/test_anthropic.py | 6 +-- 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/ee/hogai/graph/agent_modes/nodes.py b/ee/hogai/graph/agent_modes/nodes.py index 88afd34e18..e6603ad2bf 100644 --- a/ee/hogai/graph/agent_modes/nodes.py +++ b/ee/hogai/graph/agent_modes/nodes.py @@ -42,9 +42,9 @@ from ee.hogai.utils.types.base import NodePath from .compaction_manager import AnthropicConversationCompactionManager from .prompts import ( + AGENT_CORE_MEMORY_PROMPT, AGENT_PROMPT, BASIC_FUNCTIONALITY_PROMPT, - CORE_MEMORY_INSTRUCTIONS_PROMPT, DOING_TASKS_PROMPT, PROACTIVENESS_PROMPT, ROLE_PROMPT, @@ -212,6 +212,7 @@ class AgentExecutable(BaseAgentExecutable): system_prompts = ChatPromptTemplate.from_messages( [ ("system", self._get_system_prompt(state, config)), + ("system", AGENT_CORE_MEMORY_PROMPT), ], template_format="mustache", ).format_messages( @@ -221,7 +222,7 @@ class AgentExecutable(BaseAgentExecutable): ) # Mark the longest default prefix as cacheable - add_cache_control(system_prompts[-1]) + add_cache_control(system_prompts[0], ttl="1h") message = await model.ainvoke(system_prompts + langchain_messages, config) assistant_message = self._process_output_message(message) @@ -263,7 +264,6 @@ class AgentExecutable(BaseAgentExecutable): - `{{{task_management}}}` - `{{{doing_tasks}}}` - `{{{tool_usage_policy}}}` - - `{{{core_memory_instructions}}}` The variables from above can have the following nested variables that will be injected: - `{{{groups}}}` – a prompt containing the description of the groups. @@ -291,7 +291,6 @@ class AgentExecutable(BaseAgentExecutable): task_management=TASK_MANAGEMENT_PROMPT, doing_tasks=DOING_TASKS_PROMPT, tool_usage_policy=TOOL_USAGE_POLICY_PROMPT, - core_memory_instructions=CORE_MEMORY_INSTRUCTIONS_PROMPT, ) async def _get_billing_prompt(self) -> str: diff --git a/ee/hogai/graph/agent_modes/prompts.py b/ee/hogai/graph/agent_modes/prompts.py index 9dd1a7e9ae..9651c03d19 100644 --- a/ee/hogai/graph/agent_modes/prompts.py +++ b/ee/hogai/graph/agent_modes/prompts.py @@ -132,11 +132,6 @@ TOOL_USAGE_POLICY_PROMPT = """ """.strip() -CORE_MEMORY_INSTRUCTIONS_PROMPT = """ -{{{core_memory}}} -New memories will automatically be added to the core memory as the conversation progresses. If users ask to save, update, or delete the core memory, say you have done it. If the '/remember [information]' command is used, the information gets appended verbatim to core memory. -""".strip() - AGENT_PROMPT = """ {{{role}}} @@ -155,8 +150,11 @@ AGENT_PROMPT = """ {{{tool_usage_policy}}} {{{billing_context}}} +""".strip() -{{{core_memory_instructions}}} +AGENT_CORE_MEMORY_PROMPT = """ +{{{core_memory}}} +New memories will automatically be added to the core memory as the conversation progresses. If users ask to save, update, or delete the core memory, say you have done it. If the '/remember [information]' command is used, the information gets appended verbatim to core memory. """.strip() # Conditional prompts diff --git a/ee/hogai/graph/agent_modes/test/test_nodes.py b/ee/hogai/graph/agent_modes/test/test_nodes.py index ef51fbf282..d8fc551614 100644 --- a/ee/hogai/graph/agent_modes/test/test_nodes.py +++ b/ee/hogai/graph/agent_modes/test/test_nodes.py @@ -445,6 +445,46 @@ class TestAgentNode(ClickhouseTestMixin, BaseTest): self.assertIn("You are currently in project ", system_content) self.assertIn("The user's name appears to be ", system_content) + async def test_node_includes_core_memory_in_system_prompt(self): + """Test that core memory content is appended to the conversation in system prompts""" + with ( + patch("os.environ", {"ANTHROPIC_API_KEY": "foo"}), + patch("langchain_anthropic.chat_models.ChatAnthropic._agenerate") as mock_generate, + patch("ee.hogai.graph.agent_modes.nodes.AgentExecutable._aget_core_memory_text") as mock_core_memory, + ): + mock_core_memory.return_value = "User prefers concise responses and technical details" + mock_generate.return_value = ChatResult( + generations=[ChatGeneration(message=AIMessage(content="Response"))], + llm_output={}, + ) + + node = _create_agent_node(self.team, self.user) + config = RunnableConfig(configurable={}) + node._config = config + + await node.arun(AssistantState(messages=[HumanMessage(content="Test")]), config) + + # Verify _agenerate was called + mock_generate.assert_called_once() + + # Get the messages passed to _agenerate + call_args = mock_generate.call_args + messages = call_args[0][0] + + # Check system messages contain core memory + system_messages = [msg for msg in messages if isinstance(msg, SystemMessage)] + self.assertGreater(len(system_messages), 0) + + content_parts = [] + for msg in system_messages: + if isinstance(msg.content, str): + content_parts.append(msg.content) + else: + content_parts.append(str(msg.content)) + system_content = "\n\n".join(content_parts) + + self.assertIn("User prefers concise responses and technical details", system_content) + @parameterized.expand( [ # (membership_level, add_context, expected_prompt) diff --git a/ee/hogai/utils/anthropic.py b/ee/hogai/utils/anthropic.py index 7150d447f0..944337daf2 100644 --- a/ee/hogai/utils/anthropic.py +++ b/ee/hogai/utils/anthropic.py @@ -1,5 +1,5 @@ from collections.abc import Mapping, Sequence -from typing import Any, cast +from typing import Any, Literal, cast from langchain_core import messages from langchain_core.messages import BaseMessage @@ -15,17 +15,22 @@ def get_anthropic_thinking_from_assistant_message(message: AssistantMessage) -> return [] -def add_cache_control(message: BaseMessage) -> BaseMessage: +def add_cache_control(message: BaseMessage, ttl: Literal["5m", "1h"] | None = None) -> BaseMessage: + ttl = ttl or "5m" if isinstance(message.content, str): message.content = [ - {"type": "text", "text": message.content, "cache_control": {"type": "ephemeral"}}, + {"type": "text", "text": message.content, "cache_control": {"type": "ephemeral", "ttl": ttl}}, ] if message.content: last_content = message.content[-1] if isinstance(last_content, str): - message.content[-1] = {"type": "text", "text": last_content, "cache_control": {"type": "ephemeral"}} + message.content[-1] = { + "type": "text", + "text": last_content, + "cache_control": {"type": "ephemeral", "ttl": ttl}, + } else: - last_content["cache_control"] = {"type": "ephemeral"} + last_content["cache_control"] = {"type": "ephemeral", "ttl": ttl} return message diff --git a/ee/hogai/utils/test/test_anthropic.py b/ee/hogai/utils/test/test_anthropic.py index 2238faaf38..c75801e887 100644 --- a/ee/hogai/utils/test/test_anthropic.py +++ b/ee/hogai/utils/test/test_anthropic.py @@ -53,7 +53,7 @@ class TestAnthropicUtils(BaseTest): assert isinstance(message.content[0], dict) self.assertEqual(message.content[0]["type"], "text") self.assertEqual(message.content[0]["text"], "Test message") - self.assertEqual(message.content[0]["cache_control"], {"type": "ephemeral"}) + self.assertEqual(message.content[0]["cache_control"], {"type": "ephemeral", "ttl": "5m"}) def test_add_cache_control_list_content_with_string_last(self): """Test adding cache control to message with list content ending in string""" @@ -79,7 +79,7 @@ class TestAnthropicUtils(BaseTest): assert isinstance(message.content[1], dict) self.assertEqual(message.content[1]["type"], "text") self.assertEqual(message.content[1]["text"], "Second part as string") - self.assertEqual(message.content[1]["cache_control"], {"type": "ephemeral"}) + self.assertEqual(message.content[1]["cache_control"], {"type": "ephemeral", "ttl": "5m"}) def test_add_cache_control_list_content_with_dict_last(self): """Test adding cache control to message with list content ending in dict""" @@ -103,4 +103,4 @@ class TestAnthropicUtils(BaseTest): assert isinstance(message.content[1], dict) self.assertEqual(message.content[1]["type"], "image") self.assertEqual(message.content[1]["url"], "http://example.com/image.jpg") - self.assertEqual(message.content[1]["cache_control"], {"type": "ephemeral"}) + self.assertEqual(message.content[1]["cache_control"], {"type": "ephemeral", "ttl": "5m"})