Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 0 additions & 37 deletions .pre-commit-config.yaml

This file was deleted.

34 changes: 34 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Dev (Hot Reload)",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/run_backend.py",
"args": ["--reload", "--log-level", "debug"],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"justMyCode": false
},
{
"name": "Prod",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/run_backend.py",
"args": ["--host", "0.0.0.0", "--port", "8000"],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"justMyCode": false
}
]
}
98 changes: 97 additions & 1 deletion app/agent/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid
from abc import ABC, abstractmethod
from contextlib import asynccontextmanager
from typing import List, Optional
Expand Down Expand Up @@ -42,17 +43,34 @@ class BaseAgent(BaseModel, ABC):

duplicate_threshold: int = 2

# Event system integration
conversation_id: Optional[str] = Field(
default=None, description="Current conversation ID for event tracking"
)
enable_events: bool = Field(
default=True, description="Whether to publish events during execution"
)

class Config:
arbitrary_types_allowed = True
extra = "allow" # Allow extra fields for flexibility in subclasses

@model_validator(mode="after")
def initialize_agent(self) -> "BaseAgent":
"""Initialize agent with default settings if not provided."""
"""Initialize agent with default settings and event system."""
if self.llm is None or not isinstance(self.llm, LLM):
self.llm = LLM(config_name=self.name.lower())
if not isinstance(self.memory, Memory):
self.memory = Memory()

# Initialize system prompt if provided
if self.system_prompt:
self.memory.add_message(Message(role="system", content=self.system_prompt))

# Generate conversation ID if not provided
if self.conversation_id is None:
self.conversation_id = str(uuid.uuid4())

return self

@asynccontextmanager
Expand Down Expand Up @@ -129,6 +147,9 @@ async def run(self, request: Optional[str] = None) -> str:
raise RuntimeError(f"Cannot run agent from state: {self.state}")

if request:
# Update memory with user input
# Note: UserInputEvent is already handled by the calling context (continue_conversation)
# so we don't need to publish it again here to avoid duplicate messages
self.update_memory("user", request)

results: List[str] = []
Expand All @@ -138,8 +159,42 @@ async def run(self, request: Optional[str] = None) -> str:
):
self.current_step += 1
logger.info(f"Executing step {self.current_step}/{self.max_steps}")

# Publish step start event
if self.enable_events:
try:
from app.event import AgentStepStartEvent, bus

event = AgentStepStartEvent(
agent_name=self.name,
agent_type=self.__class__.__name__,
step_number=self.current_step,
conversation_id=self.conversation_id,
)
await bus.publish(event)
except Exception as e:
logger.warning(f"Failed to publish agent step start event: {e}")

step_result = await self.step()

# Publish step complete event
if self.enable_events:
try:
from app.event import AgentStepCompleteEvent, bus

complete_event = AgentStepCompleteEvent(
agent_name=self.name,
agent_type=self.__class__.__name__,
step_number=self.current_step,
result=step_result,
conversation_id=self.conversation_id,
)
await bus.publish(complete_event)
except Exception as e:
logger.warning(
f"Failed to publish agent step complete event: {e}"
)

# Check for stuck state
if self.is_stuck():
self.handle_stuck_state()
Expand Down Expand Up @@ -194,3 +249,44 @@ def messages(self) -> List[Message]:
def messages(self, value: List[Message]):
"""Set the list of messages in the agent's memory."""
self.memory.messages = value

# Event system integration methods

def set_conversation_id(self, conversation_id: str) -> None:
"""Set the conversation ID for event tracking."""
self.conversation_id = conversation_id

def enable_event_publishing(self, enabled: bool = True) -> None:
"""Enable or disable event publishing."""
self.enable_events = enabled

async def publish_custom_event(self, event_type: str, data: dict) -> bool:
"""Publish a custom agent event.

Args:
event_type: Type of the event (e.g., "agent.custom.thinking")
data: Event data dictionary

Returns:
bool: True if event was published successfully
"""
if not self.enable_events:
return False

try:
from app.event import BaseEvent, bus

event = BaseEvent(
event_type=event_type,
data={
"agent_name": self.name,
"agent_type": self.__class__.__name__,
"conversation_id": self.conversation_id,
**data,
},
source=self.name,
)
return await bus.publish(event)
except Exception as e:
logger.warning(f"Failed to publish custom event {event_type}: {e}")
return False
4 changes: 2 additions & 2 deletions app/agent/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from app.prompt.browser import NEXT_STEP_PROMPT, SYSTEM_PROMPT
from app.schema import Message, ToolChoice
from app.tool import BrowserUseTool, Terminate, ToolCollection

from app.tool.crawl4ai import Crawl4aiTool

# Avoid circular import if BrowserAgent needs BrowserContextHelper
if TYPE_CHECKING:
Expand Down Expand Up @@ -98,7 +98,7 @@ class BrowserAgent(ToolCallAgent):

# Configure the available tools
available_tools: ToolCollection = Field(
default_factory=lambda: ToolCollection(BrowserUseTool(), Terminate())
default_factory=lambda: ToolCollection(BrowserUseTool(), Terminate(), Crawl4aiTool())
)

# Use Auto for tool choice to allow both tool usage and free-form responses
Expand Down
31 changes: 28 additions & 3 deletions app/agent/manus.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from datetime import datetime
from typing import Dict, List, Optional
import pytz

from pydantic import Field, model_validator

Expand All @@ -13,6 +15,7 @@
from app.tool.mcp import MCPClients, MCPClientTool
from app.tool.python_execute import PythonExecute
from app.tool.str_replace_editor import StrReplaceEditor
from app.tool.crawl4ai import Crawl4aiTool


class Manus(ToolCallAgent):
Expand All @@ -21,7 +24,8 @@ class Manus(ToolCallAgent):
name: str = "Manus"
description: str = "A versatile agent that can solve various tasks using multiple tools including MCP-based tools"

system_prompt: str = SYSTEM_PROMPT.format(directory=config.workspace_root)
# Dynamic system prompt with current time - will be set in __init__
system_prompt: str = ""
next_step_prompt: str = NEXT_STEP_PROMPT

max_observe: int = 10000
Expand All @@ -34,10 +38,9 @@ class Manus(ToolCallAgent):
available_tools: ToolCollection = Field(
default_factory=lambda: ToolCollection(
PythonExecute(),
BrowserUseTool(),
StrReplaceEditor(),
AskHuman(),
Terminate(),
Crawl4aiTool(),
)
)

Expand All @@ -50,6 +53,28 @@ class Manus(ToolCallAgent):
) # server_id -> url/command
_initialized: bool = False

@staticmethod
def _generate_system_prompt() -> str:
"""Generate system prompt with current Beijing time information."""
# Get current time in Beijing timezone
beijing_tz = pytz.timezone('Asia/Shanghai')
beijing_time = datetime.now(beijing_tz)

# Format time string
current_time_str = beijing_time.strftime("%Y-%m-%d %H:%M:%S %A")

return SYSTEM_PROMPT.format(
directory=config.workspace_root,
current_time=current_time_str,
timezone="北京时间 (UTC+8)"
)

def __init__(self, **kwargs):
"""Initialize Manus with dynamic system prompt."""
# Generate dynamic system prompt with current time and add to kwargs
kwargs['system_prompt'] = self._generate_system_prompt()
super().__init__(**kwargs)

@model_validator(mode="after")
def initialize_helper(self) -> "Manus":
"""Initialize basic components synchronously."""
Expand Down
17 changes: 16 additions & 1 deletion app/agent/toolcall.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from app.schema import TOOL_CHOICE_TYPE, AgentState, Message, ToolCall, ToolChoice
from app.tool import CreateChatCompletion, Terminate, ToolCollection


TOOL_CALL_REQUIRED = "Tool calls required but none provided"


Expand Down Expand Up @@ -82,6 +81,22 @@ async def think(self) -> bool:
logger.info(
f"🛠️ {self.name} selected {len(tool_calls) if tool_calls else 0} tools to use"
)

# 发布智能体响应事件,包含思考内容
try:
from app.event import AgentResponseEvent, bus

response_event = AgentResponseEvent(
agent_name=self.name,
agent_type=self.__class__.__name__,
response=content,
conversation_id=self.conversation_id,
response_type="thought",
)
await bus.publish(response_event)
logger.debug(f"Published agent response event with thought content")
except Exception as e:
logger.warning(f"Failed to publish agent response event: {e}")
if tool_calls:
logger.info(
f"🧰 Tools being prepared: {[call.function.name for call in tool_calls]}"
Expand Down
Loading