Skip to content

Commit 429b1e7

Browse files
MuggleJinxhesamsheikhzjrwtx
authored
feat: add agent ability to load context summary from file (#3113)
Co-authored-by: Hesam Sheikh <41022652+hesamsheikh@users.noreply.github.com> Co-authored-by: Yifeng Wang(正经人王同学) <86822589+zjrwtx@users.noreply.github.com>
1 parent 5bca9dd commit 429b1e7

File tree

2 files changed

+281
-0
lines changed

2 files changed

+281
-0
lines changed

camel/utils/context_utils.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,55 @@ def get_session_id(self) -> str:
393393
str: The session ID.
394394
"""
395395
return self.session_id
396+
397+
def load_markdown_context_to_memory(
398+
self, agent: "ChatAgent", filename: str
399+
) -> str:
400+
r"""Load context from a markdown file and append it to agent memory.
401+
Preserves existing conversation history without wiping it.
402+
403+
Args:
404+
agent (ChatAgent): The agent to append context to.
405+
filename (str): Name of the markdown file (without .md extension).
406+
407+
Returns:
408+
str: Status message indicating success or failure with details.
409+
"""
410+
try:
411+
content = self.load_markdown_file(filename)
412+
413+
if not content.strip():
414+
return f"Context file not found or empty: {filename}"
415+
416+
from camel.messages import BaseMessage
417+
from camel.types import OpenAIBackendRole
418+
419+
prefix_prompt = (
420+
"The following is the context from a previous "
421+
"session or workflow. This information might help you "
422+
"understand the background, choose which tools to use, "
423+
"and plan your next steps."
424+
)
425+
426+
# TODO: change to system message once multi-system-message
427+
# is supported
428+
context_message = BaseMessage.make_assistant_message(
429+
role_name="Assistant",
430+
content=f"{prefix_prompt}\n\n{content}",
431+
)
432+
433+
agent.update_memory(context_message, OpenAIBackendRole.USER)
434+
435+
char_count = len(content)
436+
log_msg = (
437+
f"Context appended to agent {agent.agent_id} "
438+
f"({char_count} characters)"
439+
)
440+
logger.info(log_msg)
441+
442+
return log_msg
443+
444+
except Exception as e:
445+
error_msg = f"Failed to load markdown context to memory: {e}"
446+
logger.error(error_msg)
447+
return error_msg

test/utils/test_context_utils.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14+
15+
import tempfile
16+
from pathlib import Path
17+
from unittest.mock import MagicMock
18+
19+
import pytest
20+
21+
from camel.agents import ChatAgent
22+
from camel.models import BaseModelBackend
23+
from camel.utils.context_utils import ContextUtility
24+
25+
26+
@pytest.fixture(scope="function")
27+
def temp_directory():
28+
r"""Create a temporary directory for test files."""
29+
with tempfile.TemporaryDirectory() as temp_dir:
30+
yield temp_dir
31+
32+
33+
@pytest.fixture(scope="function")
34+
def mock_agent():
35+
r"""Create a mock ChatAgent for testing."""
36+
agent = MagicMock(spec=ChatAgent)
37+
agent.agent_id = "test_agent_001"
38+
39+
# Create a mock model backend
40+
mock_model = MagicMock(spec=BaseModelBackend)
41+
agent.model_backend = mock_model
42+
43+
agent.update_memory = MagicMock()
44+
agent.clear_memory = MagicMock()
45+
return agent
46+
47+
48+
@pytest.fixture(scope="function")
49+
def context_utility_fixture(temp_directory):
50+
r"""Create a ContextUtility instance for testing."""
51+
return ContextUtility(working_directory=temp_directory)
52+
53+
54+
def test_load_markdown_context_to_memory_preserves_existing_conversation(
55+
mock_agent, context_utility_fixture
56+
):
57+
r"""Test that load_markdown_context_to_memory preserves existing memory."""
58+
context_util = context_utility_fixture
59+
60+
# Create test context content
61+
context_content = """# Previous Workflow Summary
62+
63+
## Task Completed
64+
Data analysis of customer sales using pandas and matplotlib.
65+
66+
## Key Findings
67+
- Sales increased 15% in Q4
68+
- Electronics is top category
69+
- Customer retention: 85%
70+
"""
71+
72+
# Save context file using ContextUtility
73+
context_util.save_markdown_file(
74+
filename="test_workflow",
75+
content=context_content,
76+
title="Test Workflow Context",
77+
)
78+
79+
# Load context into agent memory
80+
result = context_util.load_markdown_context_to_memory(
81+
agent=mock_agent, filename="test_workflow"
82+
)
83+
84+
# Verify successful loading
85+
assert "Context appended to agent" in result
86+
assert "characters)" in result
87+
88+
# Verify that update_memory was called (but NOT clear_memory)
89+
mock_agent.update_memory.assert_called_once()
90+
mock_agent.clear_memory.assert_not_called()
91+
92+
# Check the message content and role
93+
call_args = mock_agent.update_memory.call_args
94+
context_message = call_args[0][0] # First argument (message)
95+
backend_role = call_args[0][1] # Second argument (role)
96+
97+
# Verify the context message format
98+
assert (
99+
"The following is the context from a previous"
100+
in context_message.content
101+
)
102+
assert "Data analysis of customer sales" in context_message.content
103+
assert "Sales increased 15%" in context_message.content
104+
assert backend_role.value == "user"
105+
106+
107+
def test_load_markdown_context_to_memory_file_not_found(
108+
mock_agent, context_utility_fixture
109+
):
110+
r"""Test handling of non-existent context files."""
111+
context_util = context_utility_fixture
112+
113+
# Try to load non-existent file
114+
result = context_util.load_markdown_context_to_memory(
115+
agent=mock_agent, filename="nonexistent_file"
116+
)
117+
118+
# Verify error handling
119+
assert "Context file not found or empty" in result
120+
121+
# Verify no memory operations occurred
122+
mock_agent.update_memory.assert_not_called()
123+
mock_agent.clear_memory.assert_not_called()
124+
125+
126+
def test_load_markdown_context_to_memory_empty_file(
127+
mock_agent, context_utility_fixture
128+
):
129+
r"""Test handling of empty context files."""
130+
context_util = context_utility_fixture
131+
132+
# Create truly empty file by writing directly to filesystem
133+
empty_file = context_util.working_directory / "empty_context.md"
134+
empty_file.write_text("", encoding="utf-8")
135+
136+
# Try to load empty file
137+
result = context_util.load_markdown_context_to_memory(
138+
agent=mock_agent, filename="empty_context"
139+
)
140+
141+
# Verify error handling
142+
assert "Context file not found or empty" in result
143+
144+
# Verify no memory operations occurred
145+
mock_agent.update_memory.assert_not_called()
146+
147+
148+
def test_load_markdown_context_to_memory_with_workflow_content(
149+
mock_agent, context_utility_fixture
150+
):
151+
r"""Test loading workflow context with multiple agents and tools."""
152+
context_util = context_utility_fixture
153+
154+
# Create workflow context
155+
workflow_content = """## Multi-Agent Workflow
156+
157+
### Agents Involved
158+
- Agent A: Data collection using web_toolkit
159+
- Agent B: Analysis using pandas_toolkit
160+
- Agent C: Reporting using email_toolkit
161+
162+
### Results
163+
Successfully processed 10,000 records and sent reports.
164+
165+
### Next Steps
166+
- Automate the pipeline
167+
- Add error handling
168+
"""
169+
170+
# Save workflow context
171+
context_util.save_markdown_file(
172+
filename="multi_agent_workflow", content=workflow_content
173+
)
174+
175+
# Load into agent memory
176+
result = context_util.load_markdown_context_to_memory(
177+
agent=mock_agent, filename="multi_agent_workflow"
178+
)
179+
180+
# Verify successful loading
181+
assert "Context appended to agent" in result
182+
183+
# Check that workflow details are preserved
184+
call_args = mock_agent.update_memory.call_args
185+
context_message = call_args[0][0]
186+
187+
assert "Multi-Agent Workflow" in context_message.content
188+
assert "web_toolkit" in context_message.content
189+
assert "pandas_toolkit" in context_message.content
190+
assert "10,000 records" in context_message.content
191+
192+
193+
def test_context_utility_initialization(temp_directory):
194+
r"""Test ContextUtility initialization."""
195+
context_util = ContextUtility(working_directory=temp_directory)
196+
197+
assert (
198+
context_util.working_directory.parent.resolve()
199+
== Path(temp_directory).resolve()
200+
)
201+
assert context_util.session_id is not None
202+
assert context_util.working_directory.exists()
203+
204+
205+
def test_save_and_load_markdown_file(context_utility_fixture):
206+
r"""Test saving and loading markdown files."""
207+
context_util = context_utility_fixture
208+
209+
# Test content
210+
test_content = "This is test content for markdown file."
211+
212+
# Save file
213+
result = context_util.save_markdown_file(
214+
filename="test_file", content=test_content, title="Test Title"
215+
)
216+
217+
assert "saved successfully" in result
218+
219+
# Load file
220+
loaded_content = context_util.load_markdown_file("test_file")
221+
222+
assert "Test Title" in loaded_content
223+
assert test_content in loaded_content
224+
225+
226+
if __name__ == "__main__":
227+
import sys
228+
229+
pytest.main([sys.argv[0]])

0 commit comments

Comments
 (0)