diff --git a/.gitignore b/.gitignore index 653fd83af..54c644a2f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ __pycache__/ # C extensions *.so - +.env # Distribution / packaging .Python build/ diff --git a/README.md b/README.md index 148bd20c4..3d924013b 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,79 @@ -

- 中文  |  English  -

- -# OpenManus 🙋 -Manus is incredible, but OpenManus can achieve any ideas without an Invite Code 🛫! - -Our team members [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) from [@MetaGPT](https://github.com/geekan/MetaGPT) built it within 3 hours! - -It's a simple implementation, so we welcome any suggestions, contributions, and feedback! - -Enjoy your own agent with OpenManus! - -## Project Demo -[Demo Video](https://github.com/mannaandpoem/OpenManus/blob/main/demo/seo_website.mp4) - - -## Installation - -1. Create a new conda environment: - -```bash -conda create -n open_manus python=3.12 -conda activate open_manus -``` - -2. Clone the repository: - -```bash -git clone https://github.com/mannaandpoem/OpenManus.git -cd OpenManus -``` - -3. Install dependencies: - -```bash -pip install -r requirements.txt -``` - -## Configuration - -OpenManus requires configuration for the LLM APIs it uses. Follow these steps to set up your configuration: - -1. Create a `config.toml` file in the `config` directory (you can copy from the example): - -```bash -cp config/config.example.toml config/config.toml -``` - -2. Edit `config/config.toml` to add your API keys and customize settings: - -```toml -# Global LLM configuration -[llm] -model = "gpt-4o" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." # Replace with your actual API key -max_tokens = 4096 -temperature = 0.0 - -# Optional configuration for specific LLM models -[llm.vision] -model = "gpt-4o" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." # Replace with your actual API key -``` - -## Quick Start -One line for run OpenManus: - -```bash -python main.py -``` - -Then input your idea via terminal! - -For unstable version, you also can run: - -```bash -python run_flow.py -``` - -## How to contribute -We welcome any friendly suggestions and helpful contributions! Just create issues or submit pull requests. - -Or contact @mannaandpoem via 📧email: mannaandpoem@gmail.com - -## Roadmap -- [ ] Better Planning -- [ ] Live Demos -- [ ] Replay -- [ ] RL Fine-tuned Models -- [ ] Comprehensive Benchmarks - -## Community Group -Join our networking group and share your experience with other developers! -
- OpenManus Community Group -
- -## Acknowledgement - -Thanks to [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) and [broswer-use](https://github.com/browser-use/browser-use) for providing basic support for this project! - -OpenManus is built by contributors from MetaGPT. Huge thanks to this agent community! +

+ 中文  |  English  +

+ +This project fork from: +https://github.com/mannaandpoem/OpenManus + + +# OpenManus 🙋 +Manus is incredible, but OpenManus can achieve any ideas without an Invite Code 🛫! + +Our team members [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) from [@MetaGPT](https://github.com/geekan/MetaGPT) built it within 3 hours! + +It's a simple implementation, so we welcome any suggestions, contributions, and feedback! + +Enjoy your own agent with OpenManus! + +## Project Demo +![截屏2025-03-07 16 49 18](https://github.com/user-attachments/assets/3b7f425a-3849-4e27-aaa4-2ff1c3d307d6) +![截屏2025-03-07 16 49 32](https://github.com/user-attachments/assets/fef9e0b7-6b85-498a-bf8c-6985771e9428) + + +## Installation + +1. Create a new conda environment: + +```bash +conda create -n open_manus python=3.12 +conda activate open_manus +``` + +2. Clone the repository: + +```bash +git clone https://github.com/mannaandpoem/OpenManus.git +cd OpenManus +``` + +3. Install dependencies: + +```bash +pip install -r requirements.txt +``` + +## Configuration + +OpenManus requires configuration for the LLM APIs it uses. Follow these steps to set up your configuration: + +1. Create a `.env` file in the root directory (you can copy from the example): + +```bash +cp env_example .env +``` + +2. Edit `.env` to add your API keys and customize settings: + +```.env +AZURE_OPENAI_API_KEY=xxxxx +AZURE_OPENAI_ENDPOINT=https://xxxx.openai.azure.com/ +AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o +AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-large +AZURE_OPENAI_API_VERSION=2023-05-15 +``` + +## Quick Start +One line for run OpenManus: + +```bash +python main.py +``` + +Then input your idea via terminal! + +For unstable version, you also can run: + +```bash +python run_flow.py +``` + diff --git a/README_zh.md b/README_zh.md index 37d9f7fb1..e59f2460f 100644 --- a/README_zh.md +++ b/README_zh.md @@ -13,7 +13,6 @@ Manus 非常棒,但 OpenManus 无需邀请码即可实现任何创意 🛫! 用 OpenManus 开启你的智能体之旅吧! ## 项目演示 -[演示视频](https://github.com/mannaandpoem/OpenManus/blob/main/demo/seo_website.mp4) ## 安装指南 @@ -41,28 +40,20 @@ pip install -r requirements.txt OpenManus 需要配置使用的 LLM API,请按以下步骤设置: -1. 在 `config` 目录创建 `config.toml` 文件(可从示例复制): +1. 在根目录创建 `.env` 文件(可从示例复制): ```bash -cp config/config.example.toml config/config.toml +cp env_example .env ``` -2. 编辑 `config/config.toml` 添加 API 密钥和自定义设置: - -```toml -# 全局 LLM 配置 -[llm] -model = "gpt-4o" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." # 替换为真实 API 密钥 -max_tokens = 4096 -temperature = 0.0 - -# 可选特定 LLM 模型配置 -[llm.vision] -model = "gpt-4o" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." # 替换为真实 API 密钥 +2. 编辑 `.env` 添加 API 密钥和自定义设置: + +```.env +AZURE_OPENAI_API_KEY=xxxxx +AZURE_OPENAI_ENDPOINT=https://xxxx.openai.azure.com/ +AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o +AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-large +AZURE_OPENAI_API_VERSION=2023-05-15 ``` ## 快速启动 diff --git a/app/agent/base.py b/app/agent/base.py index 74d6845f3..2daa0e103 100644 --- a/app/agent/base.py +++ b/app/agent/base.py @@ -10,44 +10,44 @@ class BaseAgent(BaseModel, ABC): - """Abstract base class for managing agent state and execution. - - Provides foundational functionality for state transitions, memory management, - and a step-based execution loop. Subclasses must implement the `step` method. + """抽象基类,用于管理代理状态和执行流程。 + + 提供基础功能:状态转换、记忆管理和基于步骤的执行循环。 + 子类必须实现 `step` 方法定义具体行为。 """ - # Core attributes - name: str = Field(..., description="Unique name of the agent") - description: Optional[str] = Field(None, description="Optional agent description") + # 核心属性 + name: str = Field(..., description="代理的唯一名称") + description: Optional[str] = Field(None, description="代理的可选描述") - # Prompts + # 提示词 system_prompt: Optional[str] = Field( - None, description="System-level instruction prompt" + None, description="系统级指令提示词" ) next_step_prompt: Optional[str] = Field( - None, description="Prompt for determining next action" + None, description="确定下一步行动的提示词" ) - # Dependencies - llm: LLM = Field(default_factory=LLM, description="Language model instance") - memory: Memory = Field(default_factory=Memory, description="Agent's memory store") + # 依赖项 + llm: LLM = Field(default_factory=LLM, description="语言模型实例") + memory: Memory = Field(default_factory=Memory, description="代理的记忆存储") state: AgentState = Field( - default=AgentState.IDLE, description="Current agent state" + default=AgentState.IDLE, description="代理当前状态" ) - # Execution control - max_steps: int = Field(default=10, description="Maximum steps before termination") - current_step: int = Field(default=0, description="Current step in execution") + # 执行控制 + max_steps: int = Field(default=10, description="终止前的最大步骤数") + current_step: int = Field(default=0, description="执行中的当前步骤") - duplicate_threshold: int = 2 + duplicate_threshold: int = 2 # 检测重复响应的阈值 class Config: arbitrary_types_allowed = True - extra = "allow" # Allow extra fields for flexibility in subclasses + extra = "allow" # 允许子类中的额外字段以提高灵活性 @model_validator(mode="after") def initialize_agent(self) -> "BaseAgent": - """Initialize agent with default settings if not provided.""" + """如果未提供,则使用默认设置初始化代理。""" 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): @@ -56,29 +56,29 @@ def initialize_agent(self) -> "BaseAgent": @asynccontextmanager async def state_context(self, new_state: AgentState): - """Context manager for safe agent state transitions. - + """代理状态转换的上下文管理器。 + Args: - new_state: The state to transition to during the context. - + new_state: 上下文期间要转换到的状态。 + Yields: - None: Allows execution within the new state. - + None: 允许在新状态下执行。 + Raises: - ValueError: If the new_state is invalid. + ValueError: 如果new_state无效。 """ if not isinstance(new_state, AgentState): - raise ValueError(f"Invalid state: {new_state}") + raise ValueError(f"无效状态: {new_state}") previous_state = self.state self.state = new_state try: yield except Exception as e: - self.state = AgentState.ERROR # Transition to ERROR on failure + self.state = AgentState.ERROR # 失败时转换到ERROR状态 raise e finally: - self.state = previous_state # Revert to previous state + self.state = previous_state # 恢复到之前的状态 def update_memory( self, @@ -86,15 +86,15 @@ def update_memory( content: str, **kwargs, ) -> None: - """Add a message to the agent's memory. - + """向代理的记忆中添加消息。 + Args: - role: The role of the message sender (user, system, assistant, tool). - content: The message content. - **kwargs: Additional arguments (e.g., tool_call_id for tool messages). - + role: 消息发送者的角色(user, system, assistant, tool)。 + content: 消息内容。 + **kwargs: 附加参数(例如tool消息的tool_call_id)。 + Raises: - ValueError: If the role is unsupported. + ValueError: 如果角色不受支持。 """ message_map = { "user": Message.user_message, @@ -104,26 +104,26 @@ def update_memory( } if role not in message_map: - raise ValueError(f"Unsupported message role: {role}") + raise ValueError(f"不支持的消息角色: {role}") msg_factory = message_map[role] msg = msg_factory(content, **kwargs) if role == "tool" else msg_factory(content) self.memory.add_message(msg) async def run(self, request: Optional[str] = None) -> str: - """Execute the agent's main loop asynchronously. - + """异步执行代理的主循环。 + Args: - request: Optional initial user request to process. - + request: 要处理的可选初始用户请求。 + Returns: - A string summarizing the execution results. - + 一个总结执行结果的字符串。 + Raises: - RuntimeError: If the agent is not in IDLE state at start. + RuntimeError: 如果代理在开始时不处于IDLE状态。 """ if self.state != AgentState.IDLE: - raise RuntimeError(f"Cannot run agent from state: {self.state}") + raise RuntimeError(f"无法从状态运行代理: {self.state}") if request: self.update_memory("user", request) @@ -134,36 +134,36 @@ async def run(self, request: Optional[str] = None) -> str: self.current_step < self.max_steps and self.state != AgentState.FINISHED ): self.current_step += 1 - logger.info(f"Executing step {self.current_step}/{self.max_steps}") + logger.info(f"执行步骤 {self.current_step}/{self.max_steps}") step_result = await self.step() - # Check for stuck state + # 检查卡住状态 if self.is_stuck(): self.handle_stuck_state() - results.append(f"Step {self.current_step}: {step_result}") + results.append(f"步骤 {self.current_step}: {step_result}") if self.current_step >= self.max_steps: - results.append(f"Terminated: Reached max steps ({self.max_steps})") + results.append(f"终止: 达到最大步骤 ({self.max_steps})") - return "\n".join(results) if results else "No steps executed" + return "\n".join(results) if results else "未执行任何步骤" @abstractmethod async def step(self) -> str: - """Execute a single step in the agent's workflow. - - Must be implemented by subclasses to define specific behavior. + """执行代理工作流中的单个步骤。 + + 必须由子类实现以定义特定行为。 """ def handle_stuck_state(self): - """Handle stuck state by adding a prompt to change strategy""" + """通过添加提示来改变策略以处理卡住状态""" stuck_prompt = "\ - Observed duplicate responses. Consider new strategies and avoid repeating ineffective paths already attempted." + 检测到重复响应。考虑新策略,避免重复已尝试过的无效路径。" self.next_step_prompt = f"{stuck_prompt}\n{self.next_step_prompt}" - logger.warning(f"Agent detected stuck state. Added prompt: {stuck_prompt}") + logger.warning(f"代理检测到卡住状态。添加提示: {stuck_prompt}") def is_stuck(self) -> bool: - """Check if the agent is stuck in a loop by detecting duplicate content""" + """通过检测重复内容判断代理是否陷入循环""" if len(self.memory.messages) < 2: return False @@ -171,7 +171,7 @@ def is_stuck(self) -> bool: if not last_message.content: return False - # Count identical content occurrences + # 计算相同内容出现次数 duplicate_count = sum( 1 for msg in reversed(self.memory.messages[:-1]) @@ -182,10 +182,10 @@ def is_stuck(self) -> bool: @property def messages(self) -> List[Message]: - """Retrieve a list of messages from the agent's memory.""" + """获取代理记忆中的消息列表。""" return self.memory.messages @messages.setter def messages(self, value: List[Message]): - """Set the list of messages in the agent's memory.""" + """设置代理记忆中的消息列表。""" self.memory.messages = value diff --git a/app/agent/toolcall.py b/app/agent/toolcall.py index 374a6eb04..4bfd28529 100644 --- a/app/agent/toolcall.py +++ b/app/agent/toolcall.py @@ -38,6 +38,7 @@ async def think(self) -> bool: user_msg = Message.user_message(self.next_step_prompt) self.messages += [user_msg] + logger.info(f"Providing {len(self.messages)} messages to LLM as context.") # Get response with tool options response = await self.llm.ask_tool( messages=self.messages, diff --git a/app/azure_openai.py b/app/azure_openai.py new file mode 100644 index 000000000..8665a7282 --- /dev/null +++ b/app/azure_openai.py @@ -0,0 +1,69 @@ +from typing import List, Optional, Dict, Any +from openai import AzureOpenAI +from ..config.settings import Settings + +class AzureOpenAIProvider: + """Azure OpenAI服务提供者""" + + def __init__(self): + """初始化Azure OpenAI客户端""" + Settings.validate_settings() # 验证配置 + + # self.client = AzureOpenAI( + # api_key=Settings.AZURE_OPENAI_API_KEY, + # api_version=Settings.AZURE_OPENAI_API_VERSION, + # azure_endpoint=Settings.AZURE_OPENAI_ENDPOINT, + # timeout=30.0, + # max_retries=3 + # ) + + + self.client = AzureOpenAI( + api_key=Settings.AZURE_OPENAI_API_KEY, + api_version="2023-05-15", + azure_endpoint=Settings.AZURE_OPENAI_ENDPOINT + ) + self.deployment_name = Settings.AZURE_OPENAI_DEPLOYMENT_NAME + + + def _call_azure_openai(self, prompt: str) -> Any: + """调用Azure OpenAI API""" + return self.client.chat.completions.create( + model=self.deployment_name, + messages=[ + {"role": "system", "content": self._get_system_prompt()}, + {"role": "user", "content": prompt} + ], + temperature=0.7, + max_tokens=800 + ) + + def get_embedding(self, text: str) -> Optional[List[float]]: + """获取文本的embedding向量""" + try: + if not text or not text.strip(): + return None + + response = self.client.embeddings.create( + input=text, + model=Settings.AZURE_OPENAI_EMBEDDING_DEPLOYMENT + ) + return response.data[0].embedding if response.data else None + except Exception as e: + print(f"获取embedding失败: {str(e)}") + return None + + def generate_completion(self, + messages: List[Dict[str, str]], + temperature: float = 0.7, + max_tokens: int = 800) -> Optional[str]: + """生成文本补全""" + try: + response = self._call_azure_openai(messages) + return response.choices[0].message.content if response.choices else None + except Exception as e: + print(f"生成补全失败: {str(e)}") + return None + +# 创建全局实例 +azure_openai = AzureOpenAIProvider() \ No newline at end of file diff --git a/app/config.py b/app/config.py index 2275fb4ba..add8aea01 100644 --- a/app/config.py +++ b/app/config.py @@ -2,37 +2,55 @@ import tomllib from pathlib import Path from typing import Dict +from config.settings import Settings from pydantic import BaseModel, Field def get_project_root() -> Path: - """Get the project root directory""" + """获取项目根目录 + + Returns: + 项目根目录的Path对象 + """ return Path(__file__).resolve().parent.parent -PROJECT_ROOT = get_project_root() -WORKSPACE_ROOT = PROJECT_ROOT / "workspace" +PROJECT_ROOT = get_project_root() # 项目根目录 +WORKSPACE_ROOT = PROJECT_ROOT / "workspace" # 工作空间目录 class LLMSettings(BaseModel): - model: str = Field(..., description="Model name") - base_url: str = Field(..., description="API base URL") - api_key: str = Field(..., description="API key") - max_tokens: int = Field(4096, description="Maximum number of tokens per request") - temperature: float = Field(1.0, description="Sampling temperature") + """LLM配置模型 + + 存储与语言模型相关的配置参数。 + """ + model: str = Field("default_model", description="模型名称") + base_url: str = Field("http://localhost:8000", description="API基础URL") + api_key: str = Field("default_key", description="API密钥") + max_tokens: int = Field(4096, description="每个请求的最大令牌数") + temperature: float = Field(1.0, description="采样温度") class AppConfig(BaseModel): - llm: Dict[str, LLMSettings] + """应用程序配置模型 + + 存储整个应用程序的配置。 + """ + llm: Dict[str, LLMSettings] # LLM配置字典 class Config: + """配置管理类 + + 实现单例模式,管理应用程序配置的加载和访问。 + """ _instance = None _lock = threading.Lock() _initialized = False def __new__(cls): + """创建单例实例""" if cls._instance is None: with cls._lock: if cls._instance is None: @@ -40,6 +58,7 @@ def __new__(cls): return cls._instance def __init__(self): + """初始化配置管理器""" if not self._initialized: with self._lock: if not self._initialized: @@ -49,6 +68,14 @@ def __init__(self): @staticmethod def _get_config_path() -> Path: + """获取配置文件路径 + + Returns: + 配置文件的Path对象 + + Raises: + FileNotFoundError: 在配置目录中找不到配置文件 + """ root = PROJECT_ROOT config_path = root / "config" / "config.toml" if config_path.exists(): @@ -56,24 +83,49 @@ def _get_config_path() -> Path: example_path = root / "config" / "config.example.toml" if example_path.exists(): return example_path - raise FileNotFoundError("No configuration file found in config directory") + raise FileNotFoundError("在配置目录中找不到配置文件") def _load_config(self) -> dict: + """加载配置文件 + + Returns: + 配置数据字典 + """ config_path = self._get_config_path() with config_path.open("rb") as f: return tomllib.load(f) def _load_initial_config(self): - raw_config = self._load_config() - base_llm = raw_config.get("llm", {}) + """加载初始配置 + + 处理原始配置数据并转换为结构化配置对象。 + """ + + try: + # 尝试从配置文件加载 + raw_config = self._load_config() + # 从配置文件中获取 llm 配置 + base_llm = raw_config.get("llm", {}) + except Exception: + # 如果加载失败,使用 Settings 类 + raw_config = Settings() + # 假设 Settings 类有 llm 属性,如果没有则使用空字典 + base_llm = getattr(raw_config, "llm", {}) + + # 检查 base_llm 是否为字典类型 + if not isinstance(base_llm, dict): + base_llm = {} + + # 获取 llm 覆盖配置 llm_overrides = { - k: v for k, v in raw_config.get("llm", {}).items() if isinstance(v, dict) + k: v for k, v in base_llm.items() if isinstance(v, dict) } + # 设置默认值,确保必填字段有值 default_settings = { - "model": base_llm.get("model"), - "base_url": base_llm.get("base_url"), - "api_key": base_llm.get("api_key"), + "model": base_llm.get("model", "default_model"), + "base_url": base_llm.get("base_url", "http://localhost:8000"), + "api_key": base_llm.get("api_key", "default_key"), "max_tokens": base_llm.get("max_tokens", 4096), "temperature": base_llm.get("temperature", 1.0), } @@ -92,7 +144,12 @@ def _load_initial_config(self): @property def llm(self) -> Dict[str, LLMSettings]: + """获取LLM配置 + + Returns: + LLM配置字典 + """ return self._config.llm -config = Config() +config = Config() # 全局配置单例实例 diff --git a/app/exceptions.py b/app/exceptions.py index 57a01487b..10efcd35c 100644 --- a/app/exceptions.py +++ b/app/exceptions.py @@ -1,5 +1,16 @@ class ToolError(Exception): - """Raised when a tool encounters an error.""" + """当工具遇到错误时引发 + + 用于工具执行过程中的错误处理和传递错误信息。 + + Attributes: + message: 错误消息 + """ def __init__(self, message): + """初始化ToolError实例 + + Args: + message: 描述错误的消息 + """ self.message = message diff --git a/app/flow/base.py b/app/flow/base.py index 13b3e17e7..c934a1736 100644 --- a/app/flow/base.py +++ b/app/flow/base.py @@ -8,23 +8,33 @@ class FlowType(str, Enum): - PLANNING = "planning" + """流程类型枚举,定义系统支持的不同流程类型""" + PLANNING = "planning" # 规划类型流程 class BaseFlow(BaseModel, ABC): - """Base class for execution flows supporting multiple agents""" + """流程基类,支持多代理执行 + + 提供多代理管理和执行的基础功能架构。子类必须实现execute方法定义具体流程。 + """ - agents: Dict[str, BaseAgent] - tools: Optional[List] = None - primary_agent_key: Optional[str] = None + agents: Dict[str, BaseAgent] # 代理字典,键为代理名称 + tools: Optional[List] = None # 可用工具列表 + primary_agent_key: Optional[str] = None # 主代理的键名 class Config: - arbitrary_types_allowed = True + arbitrary_types_allowed = True # 允许任意类型 def __init__( self, agents: Union[BaseAgent, List[BaseAgent], Dict[str, BaseAgent]], **data ): - # Handle different ways of providing agents + """初始化流程对象 + + Args: + agents: 代理对象,可以是单个代理、代理列表或代理字典 + **data: 其他配置参数 + """ + # 处理不同方式提供的代理 if isinstance(agents, BaseAgent): agents_dict = {"default": agents} elif isinstance(agents, list): @@ -32,31 +42,54 @@ def __init__( else: agents_dict = agents - # If primary agent not specified, use first agent + # 如果未指定主代理,使用第一个代理 primary_key = data.get("primary_agent_key") if not primary_key and agents_dict: primary_key = next(iter(agents_dict)) data["primary_agent_key"] = primary_key - # Set the agents dictionary + # 设置代理字典 data["agents"] = agents_dict - # Initialize using BaseModel's init + # 使用BaseModel的init初始化 super().__init__(**data) @property def primary_agent(self) -> Optional[BaseAgent]: - """Get the primary agent for the flow""" + """获取流程的主代理 + + Returns: + 主代理对象,如果不存在则返回None + """ return self.agents.get(self.primary_agent_key) def get_agent(self, key: str) -> Optional[BaseAgent]: - """Get a specific agent by key""" + """根据键获取特定代理 + + Args: + key: 代理键名 + + Returns: + 指定的代理对象,如果不存在则返回None + """ return self.agents.get(key) def add_agent(self, key: str, agent: BaseAgent) -> None: - """Add a new agent to the flow""" + """向流程添加新代理 + + Args: + key: 代理的键名 + agent: 要添加的代理对象 + """ self.agents[key] = agent @abstractmethod async def execute(self, input_text: str) -> str: - """Execute the flow with given input""" + """执行流程处理输入文本 + + Args: + input_text: 要处理的输入文本 + + Returns: + 处理结果字符串 + """ diff --git a/app/flow/flow_factory.py b/app/flow/flow_factory.py index 727228295..0f7904651 100644 --- a/app/flow/flow_factory.py +++ b/app/flow/flow_factory.py @@ -6,7 +6,10 @@ class FlowFactory: - """Factory for creating different types of flows with support for multiple agents""" + """流程工厂类,用于创建不同类型的流程 + + 实现工厂模式,根据流程类型创建相应的流程实例。 + """ @staticmethod def create_flow( @@ -14,12 +17,25 @@ def create_flow( agents: Union[BaseAgent, List[BaseAgent], Dict[str, BaseAgent]], **kwargs, ) -> BaseFlow: + """创建指定类型的流程实例 + + Args: + flow_type: 流程类型,来自FlowType枚举 + agents: 代理,可以是单个代理、代理列表或代理字典 + **kwargs: 传递给流程构造函数的其他参数 + + Returns: + 创建的流程实例 + + Raises: + ValueError: 如果指定了未知的流程类型 + """ flows = { FlowType.PLANNING: PlanningFlow, } flow_class = flows.get(flow_type) if not flow_class: - raise ValueError(f"Unknown flow type: {flow_type}") + raise ValueError(f"未知的流程类型: {flow_type}") return flow_class(agents, **kwargs) diff --git a/app/flow/planning.py b/app/flow/planning.py index 24fc9eb8f..39fc3651c 100644 --- a/app/flow/planning.py +++ b/app/flow/planning.py @@ -13,111 +13,140 @@ class PlanningFlow(BaseFlow): - """A flow that manages planning and execution of tasks using agents.""" + """规划流程类,管理任务的规划和执行 + + 通过规划和执行两个阶段,协调多个代理完成复杂任务。 + """ - llm: LLM = Field(default_factory=lambda: LLM()) - planning_tool: PlanningTool = Field(default_factory=PlanningTool) - executor_keys: List[str] = Field(default_factory=list) - active_plan_id: str = Field(default_factory=lambda: f"plan_{int(time.time())}") - current_step_index: Optional[int] = None + llm: LLM = Field(default_factory=lambda: LLM()) # 语言模型 + planning_tool: PlanningTool = Field(default_factory=PlanningTool) # 规划工具 + executor_keys: List[str] = Field(default_factory=list) # 执行者代理的键列表 + active_plan_id: str = Field(default_factory=lambda: f"plan_{int(time.time())}") # 活动计划ID + current_step_index: Optional[int] = None # 当前执行的步骤索引 def __init__( self, agents: Union[BaseAgent, List[BaseAgent], Dict[str, BaseAgent]], **data ): - # Set executor keys before super().__init__ + """初始化规划流程 + + Args: + agents: 代理,可以是单个代理、代理列表或代理字典 + **data: 其他配置参数 + """ + # 在super().__init__之前设置executor_keys if "executors" in data: data["executor_keys"] = data.pop("executors") - # Set plan ID if provided + # 如果提供了计划ID则设置 if "plan_id" in data: data["active_plan_id"] = data.pop("plan_id") - # Initialize the planning tool if not provided + # 如果未提供,则初始化规划工具 if "planning_tool" not in data: planning_tool = PlanningTool() data["planning_tool"] = planning_tool - # Call parent's init with the processed data + # 使用处理后的数据调用父类的初始化方法 super().__init__(agents, **data) - # Set executor_keys to all agent keys if not specified + # 如果未指定executor_keys,则使用所有代理键 if not self.executor_keys: self.executor_keys = list(self.agents.keys()) def get_executor(self, step_type: Optional[str] = None) -> BaseAgent: + """获取当前步骤的适当执行代理 + + 根据步骤类型或需求选择代理。 + + Args: + step_type: 步骤类型,可选 + + Returns: + 选择的执行代理 """ - Get an appropriate executor agent for the current step. - Can be extended to select agents based on step type/requirements. - """ - # If step type is provided and matches an agent key, use that agent + # 如果提供了步骤类型并且匹配代理键,则使用该代理 if step_type and step_type in self.agents: return self.agents[step_type] - # Otherwise use the first available executor or fall back to primary agent + # 否则使用第一个可用的执行者或回退到主代理 for key in self.executor_keys: if key in self.agents: return self.agents[key] - # Fallback to primary agent + # 回退到主代理 return self.primary_agent async def execute(self, input_text: str) -> str: - """Execute the planning flow with agents.""" + """执行规划流程 + + Args: + input_text: 要处理的输入文本 + + Returns: + 执行结果字符串 + + Raises: + ValueError: 如果没有主代理可用 + """ try: if not self.primary_agent: - raise ValueError("No primary agent available") + raise ValueError("没有可用的主代理") - # Create initial plan if input provided + # 如果提供了输入,则创建初始计划 if input_text: await self._create_initial_plan(input_text) - # Verify plan was created successfully + # 验证计划创建成功 if self.active_plan_id not in self.planning_tool.plans: logger.error( - f"Plan creation failed. Plan ID {self.active_plan_id} not found in planning tool." + f"计划创建失败。规划工具中找不到计划ID {self.active_plan_id}。" ) - return f"Failed to create plan for: {input_text}" + return f"为以下内容创建计划失败: {input_text}" result = "" while True: - # Get current step to execute + # 获取要执行的当前步骤 self.current_step_index, step_info = await self._get_current_step_info() - # Exit if no more steps or plan completed + # 如果没有更多步骤或计划完成则退出 if self.current_step_index is None: result += await self._finalize_plan() break - # Execute current step with appropriate agent + # 使用适当的代理执行当前步骤 step_type = step_info.get("type") if step_info else None executor = self.get_executor(step_type) step_result = await self._execute_step(executor, step_info) result += step_result + "\n" - # Check if agent wants to terminate + # 检查代理是否想要终止 if hasattr(executor, "state") and executor.state == AgentState.FINISHED: break return result except Exception as e: - logger.error(f"Error in PlanningFlow: {str(e)}") - return f"Execution failed: {str(e)}" + logger.error(f"PlanningFlow错误: {str(e)}") + return f"执行失败: {str(e)}" async def _create_initial_plan(self, request: str) -> None: - """Create an initial plan based on the request using the flow's LLM and PlanningTool.""" - logger.info(f"Creating initial plan with ID: {self.active_plan_id}") + """根据请求创建初始计划 + + Args: + request: 用户请求文本 + """ + logger.info(f"创建ID为 {self.active_plan_id} 的初始计划") - # Create a system message for plan creation + # 为计划创建创建系统消息 system_message = Message.system_message( - "You are a planning assistant. Your task is to create a detailed plan with clear steps." + "你是一个规划助手。你的任务是创建一个具有明确步骤的详细计划。" ) - # Create a user message with the request + # 使用请求创建用户消息 user_message = Message.user_message( - f"Create a detailed plan to accomplish this task: {request}" + f"创建一个详细计划来完成这个任务: {request}" ) - # Call LLM with PlanningTool + # 使用PlanningTool调用LLM response = await self.llm.ask_tool( messages=[user_message], system_msgs=[system_message], @@ -125,60 +154,64 @@ async def _create_initial_plan(self, request: str) -> None: tool_choice="required", ) - # Process tool calls if present + # 如果存在工具调用则处理 if response.tool_calls: for tool_call in response.tool_calls: if tool_call.function.name == "planning": - # Parse the arguments + # 解析参数 args = tool_call.function.arguments if isinstance(args, str): try: args = json.loads(args) except json.JSONDecodeError: - logger.error(f"Failed to parse tool arguments: {args}") + logger.error(f"解析工具参数失败: {args}") continue - # Ensure plan_id is set correctly and execute the tool + # 确保计划ID正确设置并执行工具 args["plan_id"] = self.active_plan_id - # Execute the tool via ToolCollection instead of directly + # 通过ToolCollection而不是直接执行工具 result = await self.planning_tool.execute(**args) - logger.info(f"Plan creation result: {str(result)}") + logger.info(f"计划创建结果: {str(result)}") return - # If execution reached here, create a default plan - logger.warning("Creating default plan") + # 如果执行到这里,创建默认计划 + logger.warning("创建默认计划") - # Create default plan using the ToolCollection + # 使用ToolCollection创建默认计划 await self.planning_tool.execute( **{ "command": "create", "plan_id": self.active_plan_id, - "title": f"Plan for: {request[:50]}{'...' if len(request) > 50 else ''}", - "steps": ["Analyze request", "Execute task", "Verify results"], + "title": f"计划: {request[:50]}{'...' if len(request) > 50 else ''}", + "steps": ["分析请求", "执行任务", "验证结果"], } ) async def _get_current_step_info(self) -> tuple[Optional[int], Optional[dict]]: - """ - Parse the current plan to identify the first non-completed step's index and info. - Returns (None, None) if no active step is found. + """获取当前步骤信息 + + 解析当前计划以识别第一个未完成步骤的索引和信息。 + 如果没有找到活动步骤,则返回(None, None)。 + + Returns: + 包含步骤索引和步骤信息的元组 """ if ( not self.active_plan_id or self.active_plan_id not in self.planning_tool.plans ): - logger.error(f"Plan with ID {self.active_plan_id} not found") + logger.error(f"找不到ID为 {self.active_plan_id} 的计划") return None, None try: - # Direct access to plan data from planning tool storage + # 从规划工具存储直接访问计划数据 plan_data = self.planning_tool.plans[self.active_plan_id] steps = plan_data.get("steps", []) step_statuses = plan_data.get("step_statuses", []) - # Find first non-completed step + # 查找第一个未完成的步骤 for i, step in enumerate(steps): if i >= len(step_statuses): status = "not_started" @@ -186,17 +219,17 @@ async def _get_current_step_info(self) -> tuple[Optional[int], Optional[dict]]: status = step_statuses[i] if status in ["not_started", "in_progress"]: - # Extract step type/category if available + # 提取步骤类型/类别(如果可用) step_info = {"text": step} - # Try to extract step type from the text (e.g., [SEARCH] or [CODE]) + # 尝试从文本中提取步骤类型(例如[SEARCH]或[CODE]) import re type_match = re.search(r"\[([A-Z_]+)\]", step) if type_match: step_info["type"] = type_match.group(1).lower() - # Mark current step as in_progress + # 将当前步骤标记为进行中 try: await self.planning_tool.execute( command="mark_step", @@ -205,8 +238,8 @@ async def _get_current_step_info(self) -> tuple[Optional[int], Optional[dict]]: step_status="in_progress", ) except Exception as e: - logger.warning(f"Error marking step as in_progress: {e}") - # Update step status directly if needed + logger.warning(f"将步骤标记为in_progress时出错: {e}") + # 如果需要,直接更新步骤状态 if i < len(step_statuses): step_statuses[i] = "in_progress" else: @@ -218,48 +251,56 @@ async def _get_current_step_info(self) -> tuple[Optional[int], Optional[dict]]: return i, step_info - return None, None # No active step found + return None, None # 未找到活动步骤 except Exception as e: - logger.warning(f"Error finding current step index: {e}") + logger.warning(f"查找当前步骤索引时出错: {e}") return None, None async def _execute_step(self, executor: BaseAgent, step_info: dict) -> str: - """Execute the current step with the specified agent using agent.run().""" - # Prepare context for the agent with current plan status + """使用指定代理执行当前步骤 + + Args: + executor: 执行代理 + step_info: 步骤信息字典 + + Returns: + 步骤执行结果 + """ + # 为代理准备当前计划状态的上下文 plan_status = await self._get_plan_text() - step_text = step_info.get("text", f"Step {self.current_step_index}") + step_text = step_info.get("text", f"步骤 {self.current_step_index}") - # Create a prompt for the agent to execute the current step + # 为代理创建执行当前步骤的提示 step_prompt = f""" - CURRENT PLAN STATUS: + 当前计划状态: {plan_status} - YOUR CURRENT TASK: - You are now working on step {self.current_step_index}: "{step_text}" + 你的当前任务: + 你现在正在处理步骤 {self.current_step_index}: "{step_text}" - Please execute this step using the appropriate tools. When you're done, provide a summary of what you accomplished. + 请使用适当的工具执行此步骤。完成后,提供你完成的内容的摘要。 """ - # Use agent.run() to execute the step + # 使用agent.run()执行步骤 try: step_result = await executor.run(step_prompt) - # Mark the step as completed after successful execution + # 成功执行后将步骤标记为已完成 await self._mark_step_completed() return step_result except Exception as e: - logger.error(f"Error executing step {self.current_step_index}: {e}") - return f"Error executing step {self.current_step_index}: {str(e)}" + logger.error(f"执行步骤 {self.current_step_index} 时出错: {e}") + return f"执行步骤 {self.current_step_index} 时出错: {str(e)}" async def _mark_step_completed(self) -> None: - """Mark the current step as completed.""" + """将当前步骤标记为已完成""" if self.current_step_index is None: return try: - # Mark the step as completed + # 将步骤标记为已完成 await self.planning_tool.execute( command="mark_step", plan_id=self.active_plan_id, @@ -267,53 +308,61 @@ async def _mark_step_completed(self) -> None: step_status="completed", ) logger.info( - f"Marked step {self.current_step_index} as completed in plan {self.active_plan_id}" + f"在计划 {self.active_plan_id} 中将步骤 {self.current_step_index} 标记为已完成" ) except Exception as e: - logger.warning(f"Failed to update plan status: {e}") - # Update step status directly in planning tool storage + logger.warning(f"更新计划状态失败: {e}") + # 直接在规划工具存储中更新步骤状态 if self.active_plan_id in self.planning_tool.plans: plan_data = self.planning_tool.plans[self.active_plan_id] step_statuses = plan_data.get("step_statuses", []) - # Ensure the step_statuses list is long enough + # 确保step_statuses列表足够长 while len(step_statuses) <= self.current_step_index: step_statuses.append("not_started") - # Update the status + # 更新状态 step_statuses[self.current_step_index] = "completed" plan_data["step_statuses"] = step_statuses async def _get_plan_text(self) -> str: - """Get the current plan as formatted text.""" + """获取当前计划的格式化文本 + + Returns: + 计划文本 + """ try: result = await self.planning_tool.execute( command="get", plan_id=self.active_plan_id ) return result.output if hasattr(result, "output") else str(result) except Exception as e: - logger.error(f"Error getting plan: {e}") + logger.error(f"获取计划时出错: {e}") return self._generate_plan_text_from_storage() def _generate_plan_text_from_storage(self) -> str: - """Generate plan text directly from storage if the planning tool fails.""" + """如果规划工具失败,直接从存储生成计划文本 + + Returns: + 计划文本 + """ try: if self.active_plan_id not in self.planning_tool.plans: - return f"Error: Plan with ID {self.active_plan_id} not found" + return f"错误: 找不到ID为 {self.active_plan_id} 的计划" plan_data = self.planning_tool.plans[self.active_plan_id] - title = plan_data.get("title", "Untitled Plan") + title = plan_data.get("title", "无标题计划") steps = plan_data.get("steps", []) step_statuses = plan_data.get("step_statuses", []) step_notes = plan_data.get("step_notes", []) - # Ensure step_statuses and step_notes match the number of steps + # 确保step_statuses和step_notes与步骤数量匹配 while len(step_statuses) < len(steps): step_statuses.append("not_started") while len(step_notes) < len(steps): step_notes.append("") - # Count steps by status + # 按状态计数步骤 status_counts = { "completed": 0, "in_progress": 0, @@ -329,15 +378,15 @@ def _generate_plan_text_from_storage(self) -> str: total = len(steps) progress = (completed / total) * 100 if total > 0 else 0 - plan_text = f"Plan: {title} (ID: {self.active_plan_id})\n" + plan_text = f"计划: {title} (ID: {self.active_plan_id})\n" plan_text += "=" * len(plan_text) + "\n\n" plan_text += ( - f"Progress: {completed}/{total} steps completed ({progress:.1f}%)\n" + f"进度: {completed}/{total} 步骤已完成 ({progress:.1f}%)\n" ) - plan_text += f"Status: {status_counts['completed']} completed, {status_counts['in_progress']} in progress, " - plan_text += f"{status_counts['blocked']} blocked, {status_counts['not_started']} not started\n\n" - plan_text += "Steps:\n" + plan_text += f"状态: {status_counts['completed']} 已完成, {status_counts['in_progress']} 进行中, " + plan_text += f"{status_counts['blocked']} 阻塞, {status_counts['not_started']} 未开始\n\n" + plan_text += "步骤:\n" for i, (step, status, notes) in enumerate( zip(steps, step_statuses, step_notes) @@ -353,47 +402,51 @@ def _generate_plan_text_from_storage(self) -> str: plan_text += f"{i}. {status_mark} {step}\n" if notes: - plan_text += f" Notes: {notes}\n" + plan_text += f" 备注: {notes}\n" return plan_text except Exception as e: - logger.error(f"Error generating plan text from storage: {e}") - return f"Error: Unable to retrieve plan with ID {self.active_plan_id}" + logger.error(f"从存储生成计划文本时出错: {e}") + return f"错误: 无法检索ID为 {self.active_plan_id} 的计划" async def _finalize_plan(self) -> str: - """Finalize the plan and provide a summary using the flow's LLM directly.""" + """完成计划并使用流程的LLM直接提供摘要 + + Returns: + 计划完成摘要 + """ plan_text = await self._get_plan_text() - # Create a summary using the flow's LLM directly + # 使用流程的LLM直接创建摘要 try: system_message = Message.system_message( - "You are a planning assistant. Your task is to summarize the completed plan." + "你是一个规划助手。你的任务是总结已完成的计划。" ) user_message = Message.user_message( - f"The plan has been completed. Here is the final plan status:\n\n{plan_text}\n\nPlease provide a summary of what was accomplished and any final thoughts." + f"计划已完成。以下是最终计划状态:\n\n{plan_text}\n\n请提供已完成内容的摘要和任何最终想法。" ) response = await self.llm.ask( messages=[user_message], system_msgs=[system_message] ) - return f"Plan completed:\n\n{response}" + return f"计划已完成:\n\n{response}" except Exception as e: - logger.error(f"Error finalizing plan with LLM: {e}") + logger.error(f"使用LLM完成计划时出错: {e}") - # Fallback to using an agent for the summary + # 回退到使用代理进行摘要 try: agent = self.primary_agent summary_prompt = f""" - The plan has been completed. Here is the final plan status: + 计划已完成。以下是最终计划状态: {plan_text} - Please provide a summary of what was accomplished and any final thoughts. + 请提供已完成内容的摘要和任何最终想法。 """ summary = await agent.run(summary_prompt) - return f"Plan completed:\n\n{summary}" + return f"计划已完成:\n\n{summary}" except Exception as e2: - logger.error(f"Error finalizing plan with agent: {e2}") - return "Plan completed. Error generating summary." + logger.error(f"使用代理完成计划时出错: {e2}") + return "计划已完成。生成摘要时出错。" diff --git a/app/llm.py b/app/llm.py index 91a4b8f40..ebc1bde72 100644 --- a/app/llm.py +++ b/app/llm.py @@ -12,7 +12,11 @@ from app.config import LLMSettings, config from app.logger import logger # Assuming a logger is set up in your app from app.schema import Message - +from openai import AzureOpenAI +from config.settings import Settings +import ipdb; +import os +import asyncio class LLM: _instances: Dict[str, "LLM"] = {} @@ -35,9 +39,95 @@ def __init__( self.model = llm_config.model self.max_tokens = llm_config.max_tokens self.temperature = llm_config.temperature - self.client = AsyncOpenAI( - api_key=llm_config.api_key, base_url=llm_config.base_url + + try: + # 确保先加载环境变量 + Settings.load_env() + + # 从 Settings 获取 Azure OpenAI 凭据 + api_key = Settings.AZURE_OPENAI_API_KEY + endpoint = Settings.AZURE_OPENAI_ENDPOINT + self.deployment_name = Settings.AZURE_OPENAI_DEPLOYMENT_NAME + + # 检查 API 密钥是否存在 + if not api_key: + raise ValueError("Azure OpenAI API 密钥未设置") + + # 初始化 Azure OpenAI 客户端 + self.client = AzureOpenAI( + api_key=api_key, + api_version="2023-05-15", + azure_endpoint=endpoint + ) + logger.info("成功初始化 Azure OpenAI 客户端") + + except (AttributeError, ValueError) as e: + # 如果 Azure 凭据不可用,回退到标准 OpenAI 客户端 + logger.warning(f"Azure OpenAI 初始化失败: {e},回退到标准 OpenAI 客户端") + self.client = AsyncOpenAI( + api_key=llm_config.api_key, + base_url=llm_config.base_url + ) + + + + + + + # def _call_azure_openai(self, prompt: str) -> Any: + # """调用Azure OpenAI API""" + # return self.client.chat.completions.create( + # model=self.deployment_name, + # messages=[ + # {"role": "system", "content": self._get_system_prompt()}, + # {"role": "user", "content": prompt} + # ], + # temperature=0.7, + # max_tokens=800 + # ) + + + #Azure OpenAI 的官方客户端(azure-openai 包中的 AzureOpenAI)​默认是同步客户端,不支持直接使用 await + #所以需要使用异步线程池来包装同步调用 + async def _call_azure_openai(self, messages, temperature, max_tokens, tools, tool_choice=None, timeout=None, **kwargs): + # 将同步调用包装到异步线程池中 + loop = asyncio.get_event_loop() + + response = await loop.run_in_executor( + None, # 使用默认线程池 + lambda: self.client.chat.completions.create( + model=self.deployment_name, + messages=messages, + temperature=temperature or self.temperature, + max_tokens=self.max_tokens, + tools=tools, + tool_choice=tool_choice, + timeout=timeout, + **kwargs, + ) + ) + return response + # return response.choices[0].message.content + + async def _call_azure_openai_stream(self, messages, temperature, stream, **kwargs): + # 将同步调用包装到异步线程池中 + loop = asyncio.get_event_loop() + + response = await loop.run_in_executor( + None, # 使用默认线程池 + lambda: self.client.chat.completions.create( + model=self.deployment_name, + messages=messages, + temperature=temperature, + max_tokens=self.max_tokens, + stream=stream, + **kwargs, ) + ) + return response + # return response.choices[0].message.content + + @staticmethod def format_messages(messages: List[Union[dict, Message]]) -> List[dict]: @@ -125,11 +215,16 @@ async def ask( if not stream: # Non-streaming request - response = await self.client.chat.completions.create( - model=self.model, - messages=messages, - max_tokens=self.max_tokens, - temperature=temperature or self.temperature, + # response = await self.client.chat.completions.create( + # model=self.model, + # messages=messages, + # max_tokens=self.max_tokens, + # temperature=temperature or self.temperature, + # stream=False, + # ) + response = await self._call_azure_openai( + messages, + temperature or self.temperature, stream=False, ) if not response.choices or not response.choices[0].message.content: @@ -137,14 +232,18 @@ async def ask( return response.choices[0].message.content # Streaming request - response = await self.client.chat.completions.create( - model=self.model, - messages=messages, - max_tokens=self.max_tokens, - temperature=temperature or self.temperature, + # response = await self.client.chat.completions.create( + # model=self.model, + # messages=messages, + # max_tokens=self.max_tokens, + # temperature=temperature or self.temperature, + # stream=True, + # ) + response = await self._call_azure_openai_stream( + messages, + temperature or self.temperature, stream=True, ) - collected_messages = [] async for chunk in response: chunk_message = chunk.choices[0].delta.content or "" @@ -219,17 +318,27 @@ async def ask_tool( if not isinstance(tool, dict) or "type" not in tool: raise ValueError("Each tool must be a dict with 'type' field") - # Set up the completion request - response = await self.client.chat.completions.create( - model=self.model, - messages=messages, - temperature=temperature or self.temperature, - max_tokens=self.max_tokens, - tools=tools, - tool_choice=tool_choice, - timeout=timeout, - **kwargs, - ) + + response = await self._call_azure_openai( + messages, + temperature, + self.max_tokens, + tools, + tool_choice, + timeout, + **kwargs) + + # # Set up the completion request + # response = await self.client.chat.completions.create( + # model=self.model, + # messages=messages, + # temperature=temperature or self.temperature, + # max_tokens=self.max_tokens, + # tools=tools, + # tool_choice=tool_choice, + # timeout=timeout, + # **kwargs, + # ) # Check if response is valid if not response.choices or not response.choices[0].message: diff --git a/app/prompt/toolcall.py b/app/prompt/toolcall.py index e1a3be939..42ece2580 100644 --- a/app/prompt/toolcall.py +++ b/app/prompt/toolcall.py @@ -1,4 +1,4 @@ -SYSTEM_PROMPT = "You are an agent that can execute tool calls" +SYSTEM_PROMPT = "You are an agent that can execute tool calls. Refer to the conversation history to understand the context, user's previous requests, and past actions before deciding on the next step. Your primary goal is to fulfill the user's request by leveraging the available tools and information from the history." NEXT_STEP_PROMPT = ( "If you want to stop interaction, use `terminate` tool/function call." diff --git a/assets/community_group.jpeg b/assets/community_group.jpeg deleted file mode 100644 index 9fc580436..000000000 Binary files a/assets/community_group.jpeg and /dev/null differ diff --git a/config/config.example.toml b/config/config.example.toml deleted file mode 100644 index de71832c5..000000000 --- a/config/config.example.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Global LLM configuration -[llm] -model = "claude-3-5-sonnet" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." -max_tokens = 4096 -temperature = 0.0 - -# Optional configuration for specific LLM models -[llm.vision] -model = "claude-3-5-sonnet" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 000000000..2375f3d5d --- /dev/null +++ b/config/settings.py @@ -0,0 +1,83 @@ +import os +from dotenv import load_dotenv +from pathlib import Path + +class Settings: + """应用配置类""" + + _initialized = False + + # Azure OpenAI 配置 + AZURE_OPENAI_API_KEY = None + AZURE_OPENAI_ENDPOINT = None + AZURE_OPENAI_DEPLOYMENT_NAME = None + AZURE_OPENAI_EMBEDDING_DEPLOYMENT = None + AZURE_OPENAI_API_VERSION = "2023-05-15" + + OPENMODELICA_PATHS = [ + '/Applications/OpenModelica.app/Contents/Resources', + '/opt/openmodelica', + ] + + SIMULATION_SETTINGS = { + 'startTime': 0.0, + 'stopTime': 10.0, + 'numberOfIntervals': 500, + 'tolerance': 1e-6, + 'method': 'dassl' + } + + @classmethod + def load_env(cls): + """加载环境变量""" + if cls._initialized: + return + + # 尝试多个可能的.env文件位置 + possible_paths = [ + Path(__file__).parent.parent.parent.parent / '.env', # 原始路径 + Path(__file__).parent.parent / '.env', # 项目根目录 + Path.cwd() / '.env', # 当前工作目录 + ] + + env_loaded = False + for env_path in possible_paths: + if env_path.exists(): + load_dotenv(dotenv_path=env_path) + print(f"已加载环境变量文件: {env_path}") + env_loaded = True + break + + if not env_loaded: + print("警告: 未找到.env文件,尝试从系统环境变量加载") + + # 加载环境变量到类属性 + cls.AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY") + cls.AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT") + cls.AZURE_OPENAI_DEPLOYMENT_NAME = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME") + cls.AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT") + cls.AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION", "2023-05-15") + + cls._initialized = True + + # 打印加载的值(调试用) + print(f"API_KEY: {cls.AZURE_OPENAI_API_KEY and '已设置' or '未设置'}") + print(f"ENDPOINT: {cls.AZURE_OPENAI_ENDPOINT and '已设置' or '未设置'}") + print(f"DEPLOYMENT_NAME: {cls.AZURE_OPENAI_DEPLOYMENT_NAME and '已设置' or '未设置'}") + + @classmethod + def validate_settings(cls): + """验证配置是否完整""" + # 确保环境变量已加载 + cls.load_env() + + required = [ + 'AZURE_OPENAI_API_KEY', + 'AZURE_OPENAI_ENDPOINT', + 'AZURE_OPENAI_DEPLOYMENT_NAME', + 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT' + ] + + missing = [key for key in required if not getattr(cls, key)] + if missing: + raise ValueError(f"缺少必要的配置项: {', '.join(missing)}") \ No newline at end of file diff --git a/demo/seo_website.mp4 b/demo/seo_website.mp4 deleted file mode 100644 index 7882f0f74..000000000 --- a/demo/seo_website.mp4 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc5525034f39ab39d6f3b5e4e67b303db242434386c2b967d6fddb12fb59a803 -size 19044985 diff --git a/env_example b/env_example new file mode 100644 index 000000000..1817170ef --- /dev/null +++ b/env_example @@ -0,0 +1,7 @@ +AZURE_OPENAI_API_KEY=xxxxx +AZURE_OPENAI_ENDPOINT=https://xxxx.openai.azure.com/ +AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o +AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-large + + +AZURE_OPENAI_API_VERSION=2023-05-15 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 445ccd78a..000000000 --- a/requirements.txt +++ /dev/null @@ -1,20 +0,0 @@ -pydantic~=2.10.4 -openai~=1.58.1 -tenacity~=9.0.0 -pyyaml~=6.0.2 -loguru~=0.7.3 -numpy -datasets~=3.2.0 - -html2text~=2024.2.26 -gymnasium~=1.0.0 -pillow~=10.4.0 -browsergym~=0.13.3 -uvicorn~=0.34.0 -unidiff~=0.7.5 -browser-use~=0.1.40 -googlesearch-python~=1.3.0 - -aiofiles~=24.1.0 -pydantic_core~=2.27.2 -colorama~=0.4.6 \ No newline at end of file diff --git a/run_flow.py b/run_flow.py deleted file mode 100644 index 66e2db8a9..000000000 --- a/run_flow.py +++ /dev/null @@ -1,33 +0,0 @@ -import asyncio - -from app.agent.manus import Manus -from app.flow.base import FlowType -from app.flow.flow_factory import FlowFactory - - -async def run_flow(): - agent = Manus() - - while True: - try: - prompt = input("Enter your prompt (or 'exit' to quit): ") - if prompt.lower() == "exit": - print("Goodbye!") - break - - flow = FlowFactory.create_flow( - flow_type=FlowType.PLANNING, - agents=agent, - ) - - print("Processing your request...") - result = await flow.execute(prompt) - print(result) - - except KeyboardInterrupt: - print("Goodbye!") - break - - -if __name__ == "__main__": - asyncio.run(run_flow()) diff --git a/setup.py b/setup.py deleted file mode 100644 index b9cfb03d8..000000000 --- a/setup.py +++ /dev/null @@ -1,48 +0,0 @@ -from setuptools import setup, find_packages - -with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -setup( - name="openmanus", - version="0.1.0", - author="mannaandpoem and OpenManus Team", - author_email="mannaandpoem@gmail.com", - description="A versatile agent that can solve various tasks using multiple tools", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/mannaandpoem/OpenManus", - packages=find_packages(), - install_requires=[ - "pydantic~=2.10.4", - "openai~=1.58.1", - "tenacity~=9.0.0", - "pyyaml~=6.0.2", - "loguru~=0.7.3", - "numpy", - "datasets~=3.2.0", - "html2text~=2024.2.26", - "gymnasium~=1.0.0", - "pillow~=10.4.0", - "browsergym~=0.13.3", - "uvicorn~=0.34.0", - "unidiff~=0.7.5", - "browser-use~=0.1.40", - "googlesearch-python~=1.3.0", - "aiofiles~=24.1.0", - "pydantic_core~=2.27.2", - "colorama~=0.4.6", - ], - classifiers=[ - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.12", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - python_requires=">=3.12", - entry_points={ - "console_scripts": [ - "openmanus=main:main", - ], - }, -)