c3bf08ac8d
K8s Workspace Integration Tests / k8s-workspace-tests (push) Has been cancelled
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Web UI / check (push) Has been cancelled
239 lines
7.9 KiB
Python
239 lines
7.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""AgenticMemoryMiddleware end-to-end demo.
|
|
|
|
The demo uses a single Agent with the filesystem-backed long-term memory
|
|
middleware and the built-in ``Read`` / ``Write`` tools across two turns:
|
|
|
|
1. The Agent receives mock user input that explicitly asks it to remember
|
|
durable user information. The middleware injects memory instructions and the
|
|
Agent writes Markdown files under ``demo_workspace/Memory``.
|
|
2. The same Agent is then asked to recall the earlier user information. The
|
|
answer is grounded by the Markdown files persisted on disk by the
|
|
middleware.
|
|
|
|
Requires:
|
|
pip install agentscope
|
|
export DASHSCOPE_API_KEY=sk-...
|
|
"""
|
|
import asyncio
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
from pydantic import SecretStr
|
|
|
|
from agentscope.agent import Agent
|
|
from agentscope.credential import DashScopeCredential
|
|
from agentscope.event import (
|
|
TextBlockDeltaEvent,
|
|
ToolCallDeltaEvent,
|
|
ToolCallStartEvent,
|
|
ToolResultEndEvent,
|
|
ToolResultTextDeltaEvent,
|
|
)
|
|
from agentscope.message import UserMsg
|
|
from agentscope.middleware import AgenticMemoryMiddleware
|
|
from agentscope.model import DashScopeChatModel
|
|
from agentscope.permission import AdditionalWorkingDirectory, PermissionMode
|
|
from agentscope.tool import Read, Toolkit, Write
|
|
|
|
|
|
RESET_DEMO_WORKSPACE = True
|
|
DEMO_ROOT = Path(__file__).with_name("demo_workspace")
|
|
|
|
FIRST_USER_MESSAGE = """
|
|
Please remember these durable facts for future conversations in this
|
|
workspace:
|
|
|
|
- My name is Alice Chen.
|
|
- I live in Hangzhou.
|
|
- I prefer concise Chinese answers.
|
|
- When evaluating examples, I like seeing a fresh Agent instance prove that
|
|
long-term memory was persisted outside the current conversation state.
|
|
|
|
Use the filesystem memory instructions in your system prompt: create or update
|
|
a topic Markdown memory file with frontmatter, and update MEMORY.md with a
|
|
short pointer to that file. Read MEMORY.md first if you need to update it.
|
|
""".strip()
|
|
|
|
SECOND_USER_MESSAGE = """
|
|
What do you remember about my name, location, answer style, and how I like
|
|
examples to demonstrate long-term memory? Read the relevant memory files if
|
|
you need details before answering.
|
|
""".strip()
|
|
|
|
|
|
def _configure_demo_permissions(agent: Agent, workspace_root: Path) -> None:
|
|
"""Allow the demo Agent to read and write inside the demo workspace.
|
|
|
|
Args:
|
|
agent (`Agent`):
|
|
The Agent whose permission context should be configured.
|
|
workspace_root (`Path`):
|
|
The directory containing the demo memory files.
|
|
"""
|
|
agent.state.permission_context.mode = PermissionMode.ACCEPT_EDITS
|
|
agent.state.permission_context.working_directories[
|
|
str(workspace_root)
|
|
] = AdditionalWorkingDirectory(
|
|
path=str(workspace_root),
|
|
source="file-system-memory-demo",
|
|
)
|
|
|
|
|
|
def _build_agent(model: DashScopeChatModel, workspace_root: Path) -> Agent:
|
|
"""Build a fresh Agent attached to one filesystem memory workspace.
|
|
|
|
Args:
|
|
model (`DashScopeChatModel`):
|
|
The chat model used by both the Agent and memory relevance
|
|
selection.
|
|
workspace_root (`Path`):
|
|
The directory that stores ``Memory/MEMORY.md`` and topic files.
|
|
|
|
Returns:
|
|
`Agent`:
|
|
A newly initialized Agent instance.
|
|
"""
|
|
memory = AgenticMemoryMiddleware(workdir=str(workspace_root))
|
|
agent = Agent(
|
|
name="memory_assistant",
|
|
system_prompt=(
|
|
"You are a concise assistant. When the user asks you to remember "
|
|
"durable preferences or profile facts, persist them using the "
|
|
"filesystem memory instructions. Use the Read and Write tools for "
|
|
"memory files."
|
|
),
|
|
model=model,
|
|
toolkit=Toolkit(tools=[Read(), Write()]),
|
|
middlewares=[memory],
|
|
)
|
|
_configure_demo_permissions(agent, workspace_root)
|
|
return agent
|
|
|
|
|
|
async def _run_turn(agent: Agent, text: str) -> str:
|
|
"""Run one streamed turn and print tool activity.
|
|
|
|
Args:
|
|
agent (`Agent`):
|
|
The Agent to run.
|
|
text (`str`):
|
|
The user message.
|
|
|
|
Returns:
|
|
`str`:
|
|
The concatenated assistant text response.
|
|
"""
|
|
tool_names: dict[str, str] = {}
|
|
tool_args: dict[str, str] = {}
|
|
tool_results: dict[str, str] = {}
|
|
reply_parts: list[str] = []
|
|
|
|
async for event in agent.reply_stream(UserMsg("alice", text)):
|
|
if isinstance(event, ToolCallStartEvent):
|
|
tool_names[event.tool_call_id] = event.tool_call_name
|
|
tool_args[event.tool_call_id] = ""
|
|
tool_results[event.tool_call_id] = ""
|
|
elif isinstance(event, ToolCallDeltaEvent):
|
|
tool_args[event.tool_call_id] += event.delta
|
|
elif isinstance(event, ToolResultTextDeltaEvent):
|
|
tool_results[event.tool_call_id] += event.delta
|
|
elif isinstance(event, ToolResultEndEvent):
|
|
tool_id = event.tool_call_id
|
|
name = tool_names.pop(tool_id, "<unknown>")
|
|
arguments = tool_args.pop(tool_id, "")
|
|
result = tool_results.pop(tool_id, "")
|
|
print(f"[tool] {name}({arguments}) -> {event.state}")
|
|
for line in result.splitlines():
|
|
print(f" {line}")
|
|
elif isinstance(event, TextBlockDeltaEvent):
|
|
reply_parts.append(event.delta)
|
|
|
|
return "".join(reply_parts)
|
|
|
|
|
|
def _print_memory_files(workspace_root: Path) -> None:
|
|
"""Print the Markdown files persisted by the memory middleware.
|
|
|
|
Args:
|
|
workspace_root (`Path`):
|
|
The demo workspace root.
|
|
"""
|
|
memory_root = workspace_root / "Memory"
|
|
print(f"\n[Markdown memory files] {memory_root}")
|
|
if not memory_root.exists():
|
|
print(" The Memory directory has not been created yet.")
|
|
return
|
|
|
|
for path in sorted(memory_root.rglob("*.md")):
|
|
relative = path.relative_to(workspace_root)
|
|
print(f"\n--- {relative} ---")
|
|
print(path.read_text(encoding="utf-8").strip())
|
|
|
|
|
|
def _print_soft_verification(workspace_root: Path) -> None:
|
|
"""Print a lightweight check that expected memory keywords were saved.
|
|
|
|
Args:
|
|
workspace_root (`Path`):
|
|
The demo workspace root.
|
|
"""
|
|
memory_root = workspace_root / "Memory"
|
|
combined = (
|
|
"\n".join(
|
|
path.read_text(encoding="utf-8", errors="replace")
|
|
for path in sorted(memory_root.rglob("*.md"))
|
|
)
|
|
if memory_root.exists()
|
|
else ""
|
|
)
|
|
checks = {
|
|
"MEMORY.md exists": (memory_root / "MEMORY.md").exists(),
|
|
"mentions Alice Chen": "Alice Chen" in combined,
|
|
"mentions Hangzhou": "Hangzhou" in combined,
|
|
"mentions concise Chinese answers": (
|
|
"concise Chinese" in combined or "Chinese answers" in combined
|
|
),
|
|
}
|
|
|
|
print("\n[Soft verification]")
|
|
for label, ok in checks.items():
|
|
print(f" {'PASS' if ok else 'WARN'} - {label}")
|
|
|
|
|
|
async def main() -> None:
|
|
"""Run the agentic memory demo."""
|
|
api_key = os.environ["DASHSCOPE_API_KEY"]
|
|
|
|
if RESET_DEMO_WORKSPACE:
|
|
print(f"=== resetting demo workspace: {DEMO_ROOT} ===")
|
|
shutil.rmtree(DEMO_ROOT, ignore_errors=True)
|
|
else:
|
|
print(f"=== reusing demo workspace: {DEMO_ROOT} ===")
|
|
DEMO_ROOT.mkdir(parents=True, exist_ok=True)
|
|
|
|
model = DashScopeChatModel(
|
|
credential=DashScopeCredential(api_key=SecretStr(api_key)),
|
|
model="qwen3.7-max",
|
|
stream=False,
|
|
)
|
|
|
|
print("\n=== Turn 1: ask the Agent to persist user memory ===")
|
|
agent = _build_agent(model, DEMO_ROOT)
|
|
print(f"[user]\n{FIRST_USER_MESSAGE}\n")
|
|
first_reply = await _run_turn(agent, FIRST_USER_MESSAGE)
|
|
print(f"\n[assistant]\n{first_reply}")
|
|
|
|
_print_memory_files(DEMO_ROOT)
|
|
_print_soft_verification(DEMO_ROOT)
|
|
|
|
print("\n=== Turn 2: ask the same Agent to recall memory ===")
|
|
print(f"[user]\n{SECOND_USER_MESSAGE}\n")
|
|
second_reply = await _run_turn(agent, SECOND_USER_MESSAGE)
|
|
print(f"\n[assistant]\n{second_reply}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|