Skip to content

Commit af31801

Browse files
MuggleJinxWendong-Fanhesamsheikh
authored
feat: add summarize function in agent using context util (#3149)
Co-authored-by: Wendong-Fan <w3ndong.fan@gmail.com> Co-authored-by: Hesam Sheikh <41022652+hesamsheikh@users.noreply.github.com> Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com>
1 parent 2dfcc0f commit af31801

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

camel/agents/chat_agent.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import threading
2424
import time
2525
import uuid
26+
from datetime import datetime
2627
from pathlib import Path
2728
from typing import (
2829
TYPE_CHECKING,
@@ -93,6 +94,7 @@
9394
model_from_json_schema,
9495
)
9596
from camel.utils.commons import dependencies_required
97+
from camel.utils.context_utils import ContextUtility
9698

9799
if TYPE_CHECKING:
98100
from camel.terminators import ResponseTerminator
@@ -535,6 +537,8 @@ def __init__(
535537
self.retry_attempts = max(1, retry_attempts)
536538
self.retry_delay = max(0.0, retry_delay)
537539
self.step_timeout = step_timeout
540+
self._context_utility: Optional[ContextUtility] = None
541+
self._context_summary_agent: Optional["ChatAgent"] = None
538542
self.stream_accumulate = stream_accumulate
539543

540544
def reset(self):
@@ -1042,6 +1046,149 @@ def save_memory(self, path: str) -> None:
10421046
json_store.save(to_save)
10431047
logger.info(f"Memory saved to {path}")
10441048

1049+
def summarize(
1050+
self,
1051+
filename: Optional[str] = None,
1052+
summary_prompt: Optional[str] = None,
1053+
) -> Dict[str, Any]:
1054+
r"""Summarize the agent's current conversation context and persist it
1055+
to a markdown file.
1056+
1057+
Args:
1058+
filename (Optional[str]): The base filename (without extension) to
1059+
use for the markdown file. Defaults to a timestamped name when
1060+
not provided.
1061+
summary_prompt (Optional[str]): Custom prompt for the summarizer.
1062+
When omitted, a default prompt highlighting key decisions,
1063+
action items, and open questions is used.
1064+
1065+
Returns:
1066+
Dict[str, Any]: A dictionary containing the summary text, file
1067+
path, and status message.
1068+
"""
1069+
1070+
result: Dict[str, Any] = {
1071+
"summary": "",
1072+
"file_path": None,
1073+
"status": "",
1074+
}
1075+
1076+
try:
1077+
if self._context_utility is None:
1078+
self._context_utility = ContextUtility()
1079+
1080+
# Get conversation directly from agent's memory
1081+
messages, _ = self.memory.get_context()
1082+
1083+
if not messages:
1084+
status_message = (
1085+
"No conversation context available to summarize."
1086+
)
1087+
result["status"] = status_message
1088+
return result
1089+
1090+
# Convert messages to conversation text
1091+
conversation_lines = []
1092+
for message in messages:
1093+
role = message.get('role', 'unknown')
1094+
content = message.get('content', '')
1095+
if content:
1096+
conversation_lines.append(f"{role}: {content}")
1097+
1098+
conversation_text = "\n".join(conversation_lines).strip()
1099+
1100+
if not conversation_text:
1101+
status_message = (
1102+
"Conversation context is empty; skipping summary."
1103+
)
1104+
result["status"] = status_message
1105+
return result
1106+
1107+
if self._context_summary_agent is None:
1108+
self._context_summary_agent = ChatAgent(
1109+
system_message=(
1110+
"You are a helpful assistant that summarizes "
1111+
"conversations into concise markdown bullet lists."
1112+
),
1113+
model=self.model_backend,
1114+
agent_id=f"{self.agent_id}_context_summarizer",
1115+
)
1116+
else:
1117+
self._context_summary_agent.reset()
1118+
1119+
prompt_text = summary_prompt or (
1120+
"Summarize the following conversation in concise markdown "
1121+
"bullet points highlighting key decisions, action items, and "
1122+
"open questions.\n\n"
1123+
f"{conversation_text}"
1124+
)
1125+
1126+
try:
1127+
response = self._context_summary_agent.step(prompt_text)
1128+
except Exception as step_exc:
1129+
error_message = (
1130+
f"Failed to generate summary using model: {step_exc}"
1131+
)
1132+
logger.error(error_message)
1133+
result["status"] = error_message
1134+
return result
1135+
1136+
if not response.msgs:
1137+
status_message = (
1138+
"Failed to generate summary from model response."
1139+
)
1140+
result["status"] = status_message
1141+
return result
1142+
1143+
summary_content = response.msgs[-1].content.strip()
1144+
if not summary_content:
1145+
status_message = "Generated summary is empty."
1146+
result["status"] = status_message
1147+
return result
1148+
1149+
base_filename = (
1150+
filename
1151+
if filename
1152+
else f"context_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}" # noqa: E501
1153+
)
1154+
base_filename = Path(base_filename).with_suffix("").name
1155+
1156+
metadata = self._context_utility.get_session_metadata()
1157+
metadata.update(
1158+
{
1159+
"agent_id": self.agent_id,
1160+
"message_count": len(messages),
1161+
}
1162+
)
1163+
1164+
save_status = self._context_utility.save_markdown_file(
1165+
base_filename,
1166+
summary_content,
1167+
title="Conversation Summary",
1168+
metadata=metadata,
1169+
)
1170+
1171+
file_path = (
1172+
self._context_utility.get_working_directory()
1173+
/ f"{base_filename}.md"
1174+
)
1175+
1176+
result.update(
1177+
{
1178+
"summary": summary_content,
1179+
"file_path": str(file_path),
1180+
"status": save_status,
1181+
}
1182+
)
1183+
logger.info("Conversation summary saved to %s", file_path)
1184+
return result
1185+
1186+
except Exception as exc:
1187+
error_message = f"Failed to summarize conversation context: {exc}"
1188+
logger.error(error_message)
1189+
result["status"] = error_message
1190+
return result
1191+
10451192
def clear_memory(self) -> None:
10461193
r"""Clear the agent's memory and reset to initial state.
10471194

examples/agents/agent_summarize.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
"""Example showing how to summarize an agent conversation to markdown."""
15+
16+
from __future__ import annotations
17+
18+
from camel.agents import ChatAgent
19+
from camel.models import ModelFactory
20+
from camel.toolkits import FunctionTool
21+
from camel.types import ModelPlatformType, ModelType
22+
23+
24+
def calculate_add(a: int, b: int) -> int:
25+
"""Add two integers and return the result."""
26+
27+
return a + b
28+
29+
30+
def calculate_multiply(a: int, b: int) -> int:
31+
"""Multiply two integers and return the product."""
32+
33+
return a * b
34+
35+
36+
def main() -> None:
37+
"""Run a short interaction and persist the summary to markdown."""
38+
39+
system_message = (
40+
"You are a helpful assistant that can call math tools when needed."
41+
)
42+
43+
model = ModelFactory.create(
44+
model_platform=ModelPlatformType.DEFAULT,
45+
model_type=ModelType.DEFAULT,
46+
)
47+
48+
add_tool = FunctionTool(calculate_add)
49+
multiply_tool = FunctionTool(calculate_multiply)
50+
51+
agent = ChatAgent(
52+
system_message=system_message,
53+
model=model,
54+
tools=[add_tool, multiply_tool],
55+
)
56+
57+
# Interact with the agent so it has context to summarize.
58+
agent.step("We are preparing a math practice worksheet for students.")
59+
60+
agent.step(
61+
"Compute 27 + 15 using the add tool to double-check the answer."
62+
)
63+
64+
agent.step("Now multiply the result by 3.")
65+
66+
# Summarize the conversation to a markdown file in the context directory.
67+
summary_result = agent.summarize(filename="math_planning_summary")
68+
69+
print(summary_result.get("status", ""))
70+
if summary_result.get("file_path"):
71+
print(f"Summary saved to: {summary_result['file_path']}")
72+
73+
if summary_result.get("summary"):
74+
print("Generated summary:\n")
75+
print(summary_result["summary"])
76+
77+
78+
'''
79+
Markdown file 'math_planning_summary.md' saved successfully
80+
Summary saved to: context_files/session_20250918_192642_141905/math_planning_summary.md
81+
Generated summary:
82+
83+
- Context
84+
- User is preparing a math practice worksheet for students.
85+
- Assistant offered to tailor the worksheet and asked clarifying questions (grade/age, topics, number of problems, answer key vs step-by-step, difficulty, time/layout).
86+
87+
- Key decisions made in conversation
88+
- Assistant provided a ready-to-use sample worksheet for Grade 6 (mixed skills) with 12 problems and a brief answer key.
89+
- User requested numerical checks using tools for arithmetic steps.
90+
91+
- Actions performed (completed)
92+
- Assistant used tools to verify calculations:
93+
- calculate_add(27, 15) → 42 (reported: 27 + 15 = 42)
94+
- calculate_multiply(42, 3) → 126 (reported: 42 * 3 = 126)
95+
96+
- Pending action items (who can/should act)
97+
- Assistant: prepare a customized worksheet based on user preferences (grade/topic/number of problems/difficulty).
98+
- Assistant: format the worksheet as a printable PDF or Google Doc if requested.
99+
- Assistant: provide full step-by-step solutions if the user wants them.
100+
- User: specify preferences (see open questions) so assistant can generate the customized worksheet.
101+
102+
- Open questions / user choices to confirm
103+
- What grade or age range should the worksheet target?
104+
- Which topics to include (arithmetic, fractions, decimals, ratios, pre-algebra, algebra, geometry, word problems, etc.)?
105+
- How many problems total?
106+
- Answer key only or full step-by-step solutions?
107+
- Preferred difficulty level (review, mixed, challenge)?
108+
- Any time limit, point values, or layout/format requirements?
109+
- Do you want the worksheet as a PDF or Google Doc?
110+
111+
- Next suggested step
112+
- User provides the preferences above and/or confirms whether to use the provided Grade 6 sample; assistant will generate the customized worksheet (with chosen format and solution detail).
113+
''' # noqa: E501
114+
115+
116+
if __name__ == "__main__":
117+
main()

0 commit comments

Comments
 (0)