Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
270129e
Refactored AugmentedLLMAnthropic
janspoerer Jul 24, 2025
23f6ce6
Refactored the Google Gemini AUgmentedLLM class
janspoerer Jul 24, 2025
940b607
Ran linter
janspoerer Jul 24, 2025
d4e7f96
Minor cleanup, removed unused code
janspoerer Jul 24, 2025
8392427
Harmonized _initialized_client() method in Azure and OpenAI classes
janspoerer Jul 24, 2025
d444609
Corrected Anthropic test
janspoerer Jul 24, 2025
c0643f7
Fixed another test
janspoerer Jul 24, 2025
7b868fb
Truncation for Gemini and Anthropic using the refactored classes
janspoerer Jul 24, 2025
c10de51
Made linter happy
janspoerer Jul 24, 2025
3c8f630
Implemented truncation for Anthropic and Gemini
janspoerer Jul 25, 2025
114cabb
Merge branch 'main' into feature/compaction
janspoerer Jul 25, 2025
d44e0c2
Minor change
janspoerer Jul 25, 2025
c0019bc
Merge branch 'feature/compaction' of github.com:janspoerer/fast-agent…
janspoerer Jul 25, 2025
e0c813b
Regenerated uv.lock
janspoerer Jul 25, 2025
36022ff
Fixed ruff linter
janspoerer Jul 25, 2025
c816e31
OpenAI truncation
janspoerer Jul 26, 2025
2ba457d
OpenAI truncation draft - working stably as far as I can test right now
janspoerer Jul 29, 2025
29a555c
Added deepseek-reasoner API string to the model configuration database
janspoerer Jul 29, 2025
0df6f03
Merge branch 'main' into pr/janspoerer/311
evalstate Jul 31, 2025
643f80b
Merge branch 'feature/compaction' of github.com:janspoerer/fast-agent…
janspoerer Aug 8, 2025
8c2d134
Resolved dependency conflict opentelemetry-exporter-otlp-proto-http>=…
janspoerer Aug 8, 2025
61c3a4a
Added CLI /compaction command and tested the summarization
janspoerer Aug 9, 2025
c754031
Fixed the OpenAI provider and added more tests
janspoerer Aug 9, 2025
8ce0c51
Fixed minor issues
janspoerer Aug 9, 2025
781ff6a
Ruff linter fix
janspoerer Aug 9, 2025
39eca11
Merged and resolved conflicts
janspoerer Aug 9, 2025
168d7e8
Removed the unnecessary trio dependency from the truncation test
janspoerer Aug 9, 2025
76a0306
add trio to pyproject, rebuild lock file
evalstate Aug 9, 2025
98f96d4
Removed trio
janspoerer Aug 9, 2025
681199e
Removed trio
janspoerer Aug 10, 2025
8ddd199
configure out trio
evalstate Aug 10, 2025
2dc008e
Integratino tests for truncation running now, but trio issue still there
janspoerer Aug 10, 2025
6efc1ba
Merge branch 'feature/compaction' of github.com:janspoerer/fast-agent…
janspoerer Aug 10, 2025
1a9b21c
Fixed integration test and resolved naming clas of e2e mcp filtering …
janspoerer Aug 14, 2025
5247c81
Merge branch 'main' into feature/compaction
janspoerer Aug 14, 2025
f102aa4
Resolved ruff errors
janspoerer Aug 14, 2025
4678036
Turned off integration test that needs Google API Key
janspoerer Aug 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies = [
"fastapi>=0.115.6",
"mcp==1.12.4",
"opentelemetry-distro>=0.55b0",
"opentelemetry-exporter-otlp-proto-http>=1.7.0",
"opentelemetry-exporter-otlp-proto-http>=1.34.1",
"pydantic-settings>=2.7.0",
"pydantic>=2.10.4",
"pyyaml>=6.0.2",
Expand All @@ -38,7 +38,7 @@ dependencies = [
"deprecated>=1.2.18",
"a2a-sdk>=0.3.0",
"email-validator>=2.2.0",
"pyperclip>=1.9.0",
"pyperclip>=1.9.0"
]

# For Azure OpenAI with DefaultAzureCredential support, install with: pip install fast-agent-mcp[azure]
Expand All @@ -53,6 +53,7 @@ dev = [
"ruff>=0.8.4",
"tomli>=2.2.1",
"pytest>=7.4.0",
"pytest-mock",
"pytest-asyncio>=0.21.1",
"pytest-cov",
]
Expand Down Expand Up @@ -103,7 +104,9 @@ dev = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.1",
"pytest-cov>=6.1.1",
"pytest-mock",
"ipdb>=0.13.13",
"trio>=0.30.0",
]

[project.scripts]
Expand Down
2 changes: 1 addition & 1 deletion scripts/event_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def load_events(path: Path) -> List[Event]:
events.append(event)
except Exception as e:
print(f"Error on line {line_num}: {e}")
print(f"Line content: {line.strip()}")
print(f"Line content: {line.strip()[:500]}")
raise
except Exception as e:
print(f"Error loading file: {e}")
Expand Down
12 changes: 12 additions & 0 deletions src/mcp_agent/core/agent_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ class AgentType(Enum):
ITERATIVE_PLANNER = "iterative_planner"


class ContextTruncationMode(str, Enum):
"""Enumeration of supported context truncation strategies."""

NONE = None
SUMMARIZE = "summarize"
REMOVE = "remove"


@dataclass
class AgentConfig:
"""Configuration for an Agent instance"""
Expand All @@ -37,6 +45,10 @@ class AgentConfig:
prompts: Optional[Dict[str, List[str]]] = None
model: str | None = None
use_history: bool = True

context_truncation_mode: Optional[ContextTruncationMode] = None
context_truncation_length_limit: Optional[int] = None

default_request_params: RequestParams | None = None
human_input: bool = False
agent_type: AgentType = AgentType.BASIC
Expand Down
7 changes: 7 additions & 0 deletions src/mcp_agent/core/enhanced_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,8 @@ def pre_process_input(text):
return "SHOW_USAGE"
elif cmd == "markdown":
return "MARKDOWN"
elif cmd == "compact":
return "COMPACT_HISTORY"
elif cmd == "prompt":
# Handle /prompt with no arguments as interactive mode
if len(cmd_parts) > 1:
Expand Down Expand Up @@ -858,6 +860,7 @@ async def handle_special_commands(command, agent_app=None):
rich_print(" /agents - List available agents")
rich_print(" /prompt <name> - Apply a specific prompt by name")
rich_print(" /usage - Show current usage statistics")
rich_print(" /compact - Compact conversation history using summarization")
rich_print(" /markdown - Show last assistant message without markdown formatting")
rich_print(" @agent_name - Switch to agent")
rich_print(" STOP - Return control back to the workflow")
Expand Down Expand Up @@ -897,6 +900,10 @@ async def handle_special_commands(command, agent_app=None):
# Return a dictionary to signal that markdown display should be shown
return {"show_markdown": True}

elif command == "COMPACT_HISTORY":
# Return a dictionary to signal that history compaction should be performed
return {"compact_history": True}

elif command == "SELECT_PROMPT" or (
isinstance(command, str) and command.startswith("SELECT_PROMPT:")
):
Expand Down
104 changes: 104 additions & 0 deletions src/mcp_agent/core/interactive_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ async def prompt_loop(
# Handle markdown display
await self._show_markdown(prompt_provider, agent)
continue
elif "compact_history" in command_result:
# Handle history compaction
await self._compact_history(prompt_provider, agent)
continue

# Skip further processing if:
# 1. The command was handled (command_result is truthy)
Expand Down Expand Up @@ -772,3 +776,103 @@ async def _show_markdown(self, prompt_provider: PromptProvider, agent_name: str)

except Exception as e:
rich_print(f"[red]Error showing markdown: {e}[/red]")

async def _compact_history(self, prompt_provider: PromptProvider, agent_name: str) -> None:
"""
Compact the conversation history using summarization.

Args:
prompt_provider: Provider that has access to agents
agent_name: Name of the current agent
"""
try:
# Get agent to compact history for
if hasattr(prompt_provider, "_agent"):
# This is an AgentApp - get the specific agent
agent = prompt_provider._agent(agent_name)
else:
# This is a single agent
agent = prompt_provider

# Check if agent has message history
if not hasattr(agent, "_llm") or not agent._llm:
rich_print("[yellow]No message history available to compact[/yellow]")
return

message_history = agent._llm.message_history
if not message_history or len(message_history) <= 2:
rich_print("[yellow]Insufficient message history to compact (need more than 2 messages)[/yellow]")
return

rich_print(f"[bold]Compacting conversation history for agent [cyan]{agent_name}[/cyan]...[/bold]")
rich_print(f"Current history: {len(message_history)} messages")

# Import the ContextTruncation class
from mcp_agent.core.agent_types import ContextTruncationMode
from mcp_agent.llm.context_truncation_and_summarization import ContextTruncation

# Message history is already in PromptMessageMultipart format
multipart_messages = message_history

# Apply summarization-based compaction
# Use a reasonable token limit - this could be made configurable
token_limit = 4000 # Conservative limit to trigger compaction
model_name = agent._llm.default_request_params.model or 'gpt-4'
system_prompt = "" # System prompt is part of message history, not separate

compacted_messages = await ContextTruncation.truncate_if_required(
messages=multipart_messages,
truncation_mode=ContextTruncationMode.SUMMARIZE,
limit=token_limit,
model_name=model_name,
system_prompt=system_prompt,
provider=agent._llm
)

# Update both histories to keep them in sync

# 1. Update universal history (PromptMessageMultipart format)
agent._llm._message_history = compacted_messages

# 2. Update provider-specific history
# Need to convert PromptMessageMultipart to provider format and update provider history
# This ensures next API calls work correctly
try:
# Check if this is Anthropic provider
if hasattr(agent._llm, 'history') and 'anthropic' in str(type(agent._llm)).lower():
from mcp_agent.llm.providers.multipart_converter_anthropic import (
AnthropicConverter,
)
provider_messages = AnthropicConverter.convert_from_multipart_to_anthropic_list(compacted_messages)
agent._llm.history.set(provider_messages)

# Check if this is OpenAI provider
elif hasattr(agent._llm, 'history') and 'openai' in str(type(agent._llm)).lower():
from mcp_agent.llm.providers.multipart_converter_openai import OpenAIConverter
# Convert each message individually since there's no bulk converter
provider_messages = []
for msg in compacted_messages:
openai_msg = OpenAIConverter.convert_to_openai(msg)
provider_messages.append(openai_msg)
agent._llm.history.set(provider_messages)

# Check if this is Google provider
elif hasattr(agent._llm, 'history') and 'google' in str(type(agent._llm)).lower():
from mcp_agent.llm.providers.google_converter import GoogleConverter
provider_messages = GoogleConverter.convert_to_google_content(compacted_messages)
agent._llm.history.set(provider_messages)

# Add other providers as needed
else:
rich_print("[yellow]Warning: Unknown provider type, only universal history updated[/yellow]")

except Exception as provider_error:
rich_print(f"[yellow]Warning: Could not update provider-specific history: {provider_error}[/yellow]")
rich_print("[yellow]Universal history was updated, but next API calls might have issues[/yellow]")

rich_print(f"[green]✓ History compacted from {len(message_history)} to {len(compacted_messages)} messages[/green]")

except Exception as e:
import traceback
rich_print(f"[red]Error compacting history: {e}[/red]")
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
19 changes: 17 additions & 2 deletions src/mcp_agent/core/request_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Request parameters definitions for LLM interactions.
"""

from typing import Any, Dict, List
from typing import Any, Dict, List, Optional

from mcp import SamplingMessage
from mcp.types import CreateMessageRequestParams
Expand Down Expand Up @@ -53,7 +53,22 @@ class RequestParams(CreateMessageRequestParams):
Optional dictionary of template variables for dynamic templates. Currently only works for TensorZero inference backend
"""

context_truncation_mode: Optional[str] = None
"""
The mode to use for context truncation: 'summarize', 'remove', or None
"""

context_truncation_length_limit: Optional[int] = None
"""
The token limit for context truncation. When exceeded, truncation will be applied.
"""

request_delay_seconds: Optional[float] = None
"""
Optional delay in seconds between requests to prevent rate limiting.
"""

mcp_metadata: Dict[str, Any] | None = None
"""
Metadata to pass through to MCP tool calls via the _meta field.
"""
"""
Loading
Loading