mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
b1778272fc
* fix: avoid persisting system prompts in ai memory Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: keep ai memory cleanup write-side only Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add bedrock prompt caching for claude Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add bedrock memory regression Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: gate bedrock prompt caching by model id Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: link bedrock caching allowlist source Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
165 lines
5.2 KiB
Python
165 lines
5.2 KiB
Python
"""
|
|
Memory tests for AI agents.
|
|
|
|
Tests that AI agents correctly handle conversation memory/history.
|
|
"""
|
|
|
|
import os
|
|
import pytest
|
|
import uuid
|
|
from typing import Any
|
|
|
|
from .conftest import AIAgentTestClient, create_ai_agent_flow
|
|
from .providers import (
|
|
ALL_PROVIDERS,
|
|
BEDROCK_API_KEY,
|
|
BEDROCK_ENV,
|
|
BEDROCK_IAM,
|
|
BEDROCK_IAM_SESSION,
|
|
get_provider_ids,
|
|
)
|
|
|
|
|
|
class TestMemory:
|
|
"""Test AI agent memory functionality."""
|
|
|
|
@staticmethod
|
|
def _pick_bedrock_provider() -> dict[str, Any]:
|
|
if os.environ.get("BEDROCK_API_KEY"):
|
|
return BEDROCK_API_KEY
|
|
|
|
if (
|
|
os.environ.get("BEDROCK_IAM_ACCESS_KEY_ID")
|
|
and os.environ.get("BEDROCK_IAM_SECRET_ACCESS_KEY")
|
|
):
|
|
return BEDROCK_IAM
|
|
|
|
if (
|
|
os.environ.get("BEDROCK_SESSION_ACCESS_KEY_ID")
|
|
and os.environ.get("BEDROCK_SESSION_SECRET_ACCESS_KEY")
|
|
and os.environ.get("BEDROCK_SESSION_TOKEN")
|
|
):
|
|
return BEDROCK_IAM_SESSION
|
|
|
|
aws_access_key_id = os.environ.get("AWS_ACCESS_KEY_ID")
|
|
aws_secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
|
|
aws_session_token = os.environ.get("AWS_SESSION_TOKEN")
|
|
|
|
if aws_access_key_id and aws_secret_access_key:
|
|
if aws_access_key_id.startswith("ASIA") and not aws_session_token:
|
|
pytest.skip(
|
|
"AWS_SESSION_TOKEN required for Bedrock env fallback when AWS_ACCESS_KEY_ID is temporary (ASIA...)"
|
|
)
|
|
return BEDROCK_ENV
|
|
|
|
pytest.skip("No Bedrock credentials available for the memory regression test")
|
|
|
|
@pytest.mark.parametrize(
|
|
"provider_config",
|
|
ALL_PROVIDERS,
|
|
ids=get_provider_ids(ALL_PROVIDERS),
|
|
)
|
|
def test_memory_two_messages(
|
|
self,
|
|
client: AIAgentTestClient,
|
|
setup_providers,
|
|
provider_config,
|
|
):
|
|
"""
|
|
Test that sending two messages with the same memory_id works correctly.
|
|
The second message should include the conversation history from the first.
|
|
"""
|
|
flow_value = create_ai_agent_flow(
|
|
provider_input_transform=provider_config["input_transform"],
|
|
system_prompt="You are a helpful assistant. Be concise.",
|
|
context_length=2,
|
|
)
|
|
|
|
# Use a shared memory_id for both messages
|
|
memory_id = str(uuid.uuid4())
|
|
|
|
# First message
|
|
result1 = client.run_preview_flow(
|
|
flow_value=flow_value,
|
|
args={"user_message": "My name is Alice. Remember this."},
|
|
memory_id=memory_id,
|
|
)
|
|
|
|
assert result1 is not None
|
|
assert "error" not in result1, f"First message failed: {result1}"
|
|
|
|
# Second message - should remember the first
|
|
result2 = client.run_preview_flow(
|
|
flow_value=flow_value,
|
|
args={"user_message": "What is my name?"},
|
|
memory_id=memory_id,
|
|
)
|
|
|
|
assert result2 is not None
|
|
assert "error" not in result2, f"Second message failed: {result2}"
|
|
|
|
# The response should mention Alice
|
|
response = str(result2.get("output", "")).lower()
|
|
assert "alice" in response, f"Expected 'Alice' in response: {result2}"
|
|
|
|
print(f"Memory test passed for {provider_config['name']}")
|
|
|
|
def test_memory_keeps_single_system_prompt_across_turns(
|
|
self,
|
|
client: AIAgentTestClient,
|
|
setup_providers,
|
|
):
|
|
"""
|
|
Test that auto memory persists conversation turns without persisting
|
|
duplicate system prompts across repeated runs.
|
|
"""
|
|
provider_config = self._pick_bedrock_provider()
|
|
|
|
system_prompt = "You are a helpful assistant. Keep responses short."
|
|
flow_value = create_ai_agent_flow(
|
|
provider_input_transform=provider_config["input_transform"],
|
|
system_prompt=system_prompt,
|
|
context_length=6,
|
|
)
|
|
|
|
memory_id = str(uuid.uuid4())
|
|
turns = [
|
|
"Turn one: say hi.",
|
|
"Turn two: say hi again.",
|
|
"Turn three: say hi one more time.",
|
|
]
|
|
|
|
result = None
|
|
for turn in turns:
|
|
result = client.run_preview_flow(
|
|
flow_value=flow_value,
|
|
args={"user_message": turn},
|
|
memory_id=memory_id,
|
|
)
|
|
assert result is not None
|
|
assert "error" not in result, f"Turn failed for {provider_config['name']}: {result}"
|
|
|
|
assert result is not None
|
|
messages = result.get("messages", [])
|
|
system_messages = [msg for msg in messages if msg.get("role") == "system"]
|
|
|
|
assert len(system_messages) == 1, (
|
|
f"Expected exactly one system message for {provider_config['name']}, got: {messages}"
|
|
)
|
|
assert system_messages[0].get("content") == system_prompt
|
|
|
|
user_messages = [
|
|
msg.get("content")
|
|
for msg in messages
|
|
if msg.get("role") == "user"
|
|
]
|
|
assert user_messages == turns, (
|
|
f"Expected all prior user turns to remain in memory for {provider_config['name']}: {messages}"
|
|
)
|
|
|
|
print(f"System prompt dedupe test passed for {provider_config['name']}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v", "-s"])
|