From 1a6a58729b43b863c677cbfa93df80627cad8cce Mon Sep 17 00:00:00 2001 From: "jinli.yl" Date: Fri, 18 Jul 2025 14:04:18 +0800 Subject: [PATCH 1/2] update --- README.md | 45 ++++-- TODO.md | 18 +++ cookbook/simple_agent/messages.jsonl | 153 +----------------- cookbook/simple_agent/your_own_agent.py | 119 ++------------ experiencemaker/config/config_parser.py | 3 +- experiencemaker/config/demo_config.yaml | 6 +- experiencemaker/op/__init__.py | 1 + experiencemaker/op/react/__init__.py | 0 experiencemaker/op/react/react_v1_op.py | 66 ++++++++ experiencemaker/op/react/react_v1_prompt.yaml | 7 + .../op/react/simple_react_context.py | 21 +++ experiencemaker/op/react/simple_react_op.py | 100 ++++++++++++ .../op/react/simple_react_prompt.yaml | 28 ++++ experiencemaker/schema/app_config.py | 1 + experiencemaker/schema/message.py | 4 +- experiencemaker/schema/request.py | 4 +- .../service/experience_maker_client.py | 4 +- experiencemaker/tool/__init__.py | 7 +- experiencemaker/tool/code_tool.py | 2 + experiencemaker/tool/dashscope_search_tool.py | 2 + experiencemaker/tool/mcp_tool.py | 2 + experiencemaker/tool/terminate_tool.py | 2 + 22 files changed, 313 insertions(+), 282 deletions(-) create mode 100644 TODO.md create mode 100644 experiencemaker/op/react/__init__.py create mode 100644 experiencemaker/op/react/react_v1_op.py create mode 100644 experiencemaker/op/react/react_v1_prompt.yaml create mode 100644 experiencemaker/op/react/simple_react_context.py create mode 100644 experiencemaker/op/react/simple_react_op.py create mode 100644 experiencemaker/op/react/simple_react_prompt.yaml diff --git a/README.md b/README.md index 17f9d38d..bb7fa4ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # ExperienceMaker + + + + + # 🌟 What is ExperienceMaker? ExperienceMaker provides agents with robust capabilities for experience generation and reuse. By summarizing agents' past trajectories into experiences, it enables these experiences to be applied to subsequent tasks. @@ -38,19 +43,7 @@ pip install . pip install experiencemaker ``` -## quick start - -## vector store -ExperienceMaker is equipped with a vector database. -If you just want to try out ExperienceMaker, you can use `backend=local_file` for testing purposes. Please note that this method may become time-consuming when dealing with large amounts of data. You can skip this step. -If you are planning to deploy ExperienceMaker or expect a significant QPS (queries per second), we recommend using `backend=elasticsearch`. -To set up [Elasticsearch](https://www.elastic.co/docs/solutions/search/run-elasticsearch-locally) and Kibana locally, run the start-local script in the command line: -```shell -curl -fsSL https://elastic.co/start-local | sh -``` -- Elasticsearch [quick start](../vector_store/elasticsearch.md) -- chroma - +# Quick Start ## Environment Variables ```shell @@ -59,6 +52,32 @@ export LLM_BASE_URL="xxx" export EMBEDDING_MODEL_API_KEY="sk-xxx" export EMBEDDING_MODEL_BASE_URL="xxx" ``` +If you are using Elasticsearch's vector database, you need to add the following environment variables: +```shell +ES_HOSTS=http://0.0.0.0:9200 +``` + +## Start Service +```shell +experiencemaker \ + llm.default.model_name=qwen-max-2025-01-25 \ + embedding_model.default.model_name=text-embedding-v4 \ + vector_store.default.backend=elasticsearch \ +``` + +### Start Vector Store Service(optional) +ExperienceMaker is equipped with a vector database. +If you just want to try out ExperienceMaker, you can use `backend=local_file` for testing purposes. Please note that this method may become time-consuming when dealing with large amounts of data. You can skip this step. +If you are planning to deploy ExperienceMaker or expect a significant QPS (queries per second), we recommend using `backend=elasticsearch`. +To set up [Elasticsearch](https://www.elastic.co/docs/solutions/search/run-elasticsearch-locally) and Kibana locally, run the start-local script in the command line: +```shell +curl -fsSL https://elastic.co/start-local | sh +``` +- Elasticsearch [quick start](./cookbook/) +- chroma + +## Call + diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..336e4b4a --- /dev/null +++ b/TODO.md @@ -0,0 +1,18 @@ +## TODO +1. zhaoyang建议 + 1. rerank prompt优化,去掉不相关的 @jiaji 优化 + 2. 多数据场景数据验证 @jiaji & @zouyin +2. README @jinli +3. cookbook: + 1. 基础的react agent @jinli + 2. appworld 测试 + 主实验 + 模块开关 + topk消融 + readme/code @jiaji + 3. bfcl 测试 + 主实验 + 模块开关 + topk消融 + readme/code @zouyin +4. advanced usage + 1. 全局op参数 @jinli + 2. work介绍 @jinli +5. experience store @jinli + 1. git 仓库 / hf @jinli +6. 合并beyondagent client +7. 设计理念 + RoadMAP @jiaji + 1. 金融experience + 2. making tools diff --git a/cookbook/simple_agent/messages.jsonl b/cookbook/simple_agent/messages.jsonl index b161904d..0637a088 100644 --- a/cookbook/simple_agent/messages.jsonl +++ b/cookbook/simple_agent/messages.jsonl @@ -1,152 +1 @@ -[ - { - "role": "user", - "content": "You are a helpful assistant named BeyondAgent.\nThe current time is 2025-06-12 00:29:13.\n\nPlease proactively choose the most suitable tool or combination of tools based on the user's question, including python_execute, web_search, terminate etc.\nFor complex tasks, you can break down the problem step by step and use different tools to solve it incrementally.\nPlease determine the response language based on the language of the user's question.\n\nAnalyze Xiaomi Corporation.\n", - "reasoning_content": "", - "tool_calls": [], - "timestamp": "2025-06-12 00:29:13.095977", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - }, - { - "role": "assistant", - "content": "", - "reasoning_content": "Okay, the user wants me to analyze Xiaomi Corporation. Let's see, where do I start? First, I need to gather recent information about Xiaomi. Since the current date is 2025-06-12, I should look for up-to-date data.\n\nI should use the web_search tool to find recent news, financial reports, and market position. Keywords like \"Xiaomi Corporation recent performance\", \"Xiaomi financials 2025\", \"Xiaomi market share\", and \"Xiaomi business strategy\" might be useful. Each keyword should be a separate search to get comprehensive info.\n\nAfter collecting data, maybe I can use python_execute to analyze financial metrics like revenue growth, profit margins, or compare their market share with competitors. But first, I need the data from the web searches. Let me start by performing the web searches first", - "tool_calls": [ - { - "index": 0, - "id": "call_0522a78d595947df9ab29a", - "name": "web_search", - "arguments": "{\"query\": \"Xiaomi Corporation recent performance\"}", - "type": "function" - } - ], - "timestamp": "2025-06-12 00:29:18.395192", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - }, - { - "role": "tool", - "content": "tool.0\nname=web_search\narguments={\"query\": \"Xiaomi Corporation recent performance\"}\nresult=From the provided context, the following content directly relates to Xiaomi Corporation's recent performance: \n\n1. **Estratégia, gerenciamento e progresso de implementação da Xiaomi Corporation e suas subsidiárias para 2024.** \n (Translation: Strategy, management, and implementation progress of Xiaomi Corporation and its subsidiaries for 2024.) \n\n2. **\"Experiência de usuário melhorada e a conexão inteligente perfeitamente demonstram as atualizações. Seus dispositivos, Um superpoder. Todos os seus dispositivos em um ecossistema integrado.\"** \n (Translation: Improved user experience and seamless smart connectivity perfectly demonstrate the updates. Your devices, a superpower. All your devices in an integrated ecosystem.) \n\n3. **\"Descubra o design sofisticado do Redmi Note 14 Pro+ 5G, onde a elegância encontra a inovação.\"** \n (Translation: Discover the sophisticated design of the Redmi Note 14 Pro+ 5G, where elegance meets innovation.) \n\n4. **\"Quebrando o recorde da pista e se tornando o carro quatro portas mais rápido na história de Nürburgring Nordschleife.\"** \n (Translation: Breaking the track record and becoming the fastest four-door car in the history of Nürburgring Nordschleife.) \n\nThese references highlight Xiaomi's strategic focus for 2024, product innovation (e.g., Redmi Note 14 Pro+ 5G), ecosystem integration, and achievements in automotive performance.", - "reasoning_content": "", - "tool_calls": [ - { - "index": 0, - "id": "call_0522a78d595947df9ab29a", - "name": "web_search", - "arguments": "{\"query\": \"Xiaomi Corporation recent performance\"}", - "type": "function" - } - ], - "timestamp": "2025-06-12 00:29:46.440802", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {}, - "tool_call_id": "" - }, - { - "role": "user", - "content": "User question\nAnalyze Xiaomi Corporation.\n\nPlan the most suitable tool or combination of tools based on the context and the user's question. \nFor complex tasks, break down the problem and use different tools step by step to solve it, don't give up easily. \nTry calling the same tool multiple times with different parameters to obtain information from various perspectives. \nIf the task is completed and the user's question can now be answered, use the **terminate** tool.\nPlease determine the response language based on the language of the user's question.\n", - "reasoning_content": "", - "tool_calls": [], - "timestamp": "2025-06-12 00:29:46.441389", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - }, - { - "role": "assistant", - "content": "", - "reasoning_content": "Okay, let's see. The user wants an analysis of Xiaomi Corporation. I already did a web search for \"Xiaomi Corporation recent performance\" and got some results. But the information seems a bit scattered. The first result mentions their 2024 strategy and management progress, which is good. The next ones talk about product updates, like the Redmi Note 14 Pro+ 5G and their automotive achievements. But I need more structured data to do a proper analysis.\n\nMaybe I should do more web searches with different keywords to gather more specific information. Let me think about what aspects are important when analyzing a company. Financials, market position, product lines, recent innovations, and strategic moves. I already touched on strategy and products. What else? Maybe their financial performance in 2024, market share in the smartphone industry, and any recent partnerships or expansions.\n\nSo, I'll need to perform additional web searches. Let's try \"Xiaomi financials 2024\" to get their financial data. Then \"Xiaomi market share 2025\" to see their current standing. Also, \"Xiaomi business strategy 2025\" might give more details on their plans. Each of these queries can help fill in the gaps. Once I have that data, I can use Python to analyze numbers if needed, but first, I need the information from the web searches. Let's proceed step by step", - "tool_calls": [ - { - "index": 0, - "id": "call_e2ecce4e9f5f40c9a32f50", - "name": "web_search", - "arguments": "{\"query\": \"Xiaomi financials 2024\"}", - "type": "function" - } - ], - "timestamp": "2025-06-12 00:29:55.599484", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - }, - { - "role": "tool", - "content": "tool.0\nname=web_search\narguments={\"query\": \"Xiaomi financials 2024\"}\nresult=Here is the original content related to **Xiaomi's 2024 financials** extracted directly from the provided context:\n\n---\n\n### Key Financial Highlights (2024):\n1. **Revenue**: \n - **Full-Year 2024**: ¥104.1 billion (RMB 1041亿元) total revenue. \n - **Q4 2024**: ¥92.5 billion (RMB 925亿元) revenue. \n - **Q1 2024**: ¥75.5 billion (RMB 755亿元) revenue, a **27% YoY increase**. \n\n2. **Profitability**: \n - **Adjusted Net Loss**: ¥3.21 billion (RMB 321亿元) for full-year 2024. \n - **Q1 2024**: Adjusted net profit of ¥6.5 billion (RMB 65亿元), a **100.8% YoY increase**. \n - **Gross Margin**: \n - Full-Year 2024: **15.4%** (varied across segments, e.g., smartphones at 14.8%). \n - Q4 2024: **15.6%** (noted as higher than some competitors like Midea at 18.5%). \n\n3. **Smartphone Business**: \n - **Global Shipments**: 40.6 million units in Q1 2024, a **33.7% YoY increase**. \n - **Revenue**: ¥46.5 billion (RMB 465亿元) in Q1 2024, with **consecutive four-quarter growth**. \n - **Market Share**: Ranked top 3 in 56 markets and top 5 in 67 markets globally. \n\n4. **Automotive Segment**: \n - **SU7 Delivery Volume**: 350,000 units in 2024. \n - **Unit Loss**: ¥27,300 (RMB 27300元) per car in 2024 (vs. ¥39,800 in prior periods). \n - **SU7 Ultra**: Pre-sale price of ¥814,900 (RMB 814900元), expected launch in March 2025. \n - **Gross Margin**: Negative (**-35%** in some quarters) due to high R&D and production costs. \n\n5. **Other Metrics**: \n - **IoT & Lifestyle Products**: Highlighted as Xiaomi's \"brightest\" segment, though overshadowed by automotive news. \n - **Internet Services**: Contributed significantly to profitability, with revenue growth in Q1 2024. \n\n6. **Strategic Milestones**: \n - Xiaomi SU7 Ultra prototype set a **Nürburgring lap record of 6:46.874** (fastest four-door car). \n - Launch of **Xiaomi 15 series** (starting at ¥4,499) and **Xiaomi HyperOS 2** to strengthen \"human-car-home ecosystem.\" \n\n---\n\n### Additional Notes: \n- **World 500 Ranking**: Xiaomi ranked **#397 in Fortune Global 500** (2024), marking its **6th consecutive year** on the list. \n- **Challenges**: Automotive segment faced **high unit losses** and negative gross margins, indicating ongoing investment in R&D and scaling production. \n\n--- \n\nThis data is derived directly from the context provided, including financial reports, earnings highlights, and strategic updates from 2024.", - "reasoning_content": "", - "tool_calls": [ - { - "index": 0, - "id": "call_e2ecce4e9f5f40c9a32f50", - "name": "web_search", - "arguments": "{\"query\": \"Xiaomi financials 2024\"}", - "type": "function" - } - ], - "timestamp": "2025-06-12 00:30:35.147952", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {}, - "tool_call_id": "" - }, - { - "role": "user", - "content": "User question\nAnalyze Xiaomi Corporation.\n\nPlan the most suitable tool or combination of tools based on the context and the user's question. \nFor complex tasks, break down the problem and use different tools step by step to solve it, don't give up easily. \nTry calling the same tool multiple times with different parameters to obtain information from various perspectives. \nIf the task is completed and the user's question can now be answered, use the **terminate** tool.\nPlease determine the response language based on the language of the user's question.\n", - "reasoning_content": "", - "tool_calls": [], - "timestamp": "2025-06-12 00:30:35.148491", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - }, - { - "role": "assistant", - "content": "", - "reasoning_content": "Okay, let's see. The user wants an analysis of Xiaomi Corporation. I've already done a couple of web searches. First, I looked up \"Xiaomi Corporation recent performance\" and got some info on their 2024 strategy, product updates like the Redmi Note 14 Pro+ 5G, and their automotive achievements. Then, I searched for \"Xiaomi financials 2024\" and found detailed financial data including revenue, profitability, smartphone shipments, and the automotive segment's performance.\n\nNow, I need to make sure I have all the necessary information to perform a comprehensive analysis. The user might be interested in different aspects like financial health, market position, product lines, strategic initiatives, and challenges. Let me check if there are any gaps in the data I've collected so far.\n\nThe financial data covers revenue, net loss, gross margins, and segment performance. The smartphone business is growing, but the automotive segment is still incurring losses. The IoT and internet services are contributing positively. However, I don't have information on Xiaomi's market share in specific regions, their competitors' performance, or recent partnerships or innovations beyond what's already mentioned. \n\nTo get a more complete picture, maybe I should search for \"Xiaomi market share 2025\" to see their current standing in the global smartphone market. Also, checking \"Xiaomi business strategy 2025\" could provide insights into their future plans. Additionally, looking up \"Xiaomi competitors 2025\" might help in understanding their competitive landscape.\n\nBut wait, the user's question is to analyze Xiaomi Corporation. The existing data already includes key financial metrics, strategic initiatives, and product developments. The automotive segment's performance and challenges are covered, as well as their IoT and internet services. The World 500 ranking is also mentioned, which adds to their corporate stature.\n\nIs there any critical information missing? The market share data from the first search mentioned they are top 3 in 56 markets and top 5 in 67, but not specific regions like China, Europe, or others. However, the user might not need that level of detail unless specified. The financials are solid for an analysis, showing growth in revenue and smartphone shipments, though the automotive segment is a drag on profits.\n\nConsidering the data I have, I can structure the analysis around financial performance, strategic focus areas (like the human-car-home ecosystem), product innovations, and challenges in the automotive division. The terminate tool should be used if the existing information is sufficient. Since the user hasn't asked for more specific details and the current data allows for a thorough analysis, it's probably time to conclude and use the terminate tool with a success status.", - "tool_calls": [ - { - "index": 0, - "id": "call_fcb76c57d8034bf7ac5dae", - "name": "terminate", - "arguments": "{\"status\": \"success\"}", - "type": "function" - } - ], - "timestamp": "2025-06-12 00:30:53.152119", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - }, - { - "role": "tool", - "content": "tool.0\nname=terminate\narguments={\"status\": \"success\"}\nresult=The interaction has been completed with status: success", - "reasoning_content": "", - "tool_calls": [ - { - "index": 0, - "id": "call_fcb76c57d8034bf7ac5dae", - "name": "terminate", - "arguments": "{\"status\": \"success\"}", - "type": "function" - } - ], - "timestamp": "2025-06-12 00:30:53.152832", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {}, - "tool_call_id": "" - }, - { - "role": "user", - "content": "Please integrate the context and provide a complete answer to the user's question.\nPlease determine the response language based on the language of the user's question.\n\nUser question\nAnalyze Xiaomi Corporation.\n", - "reasoning_content": "", - "tool_calls": [], - "timestamp": "2025-06-12 00:30:53.153059", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - }, - { - "role": "assistant", - "content": "**Analysis of Xiaomi Corporation (2024–2025)** \n\n### **1. Financial Performance** \n- **Revenue Growth**: \n - Total revenue reached **¥104.1 billion (RMB)** in 2024, with Q1 2024 showing a **27% YoY increase** to ¥75.5 billion. \n - Smartphone revenue grew **consecutively for four quarters**, reaching ¥46.5 billion in Q1 2024. \n- **Profitability**: \n - Adjusted net loss of **¥3.21 billion** for full-year 2024, driven by heavy investments in the automotive segment. \n - Q1 2024 saw an adjusted net profit of **¥6.5 billion**, reflecting strong performance in smartphones and internet services. \n - Gross margin stood at **15.4%** (full-year 2024), with smartphones at **14.8%**. \n\n---\n\n### **2. Market Position & Strategic Focus** \n- **Smartphone Leadership**: \n - Global shipments hit **40.6 million units in Q1 2024** (33.7% YoY growth). \n - Ranked **Top 3 in 56 markets** and **Top 5 in 67 markets** globally. \n- **Ecosystem Expansion**: \n - Strengthening the **\"human-car-home\" ecosystem** with products like the **Xiaomi 15 series** (starting at ¥4,499) and **Xiaomi HyperOS 2**. \n - IoT & lifestyle products remain a **key growth driver**, contributing significantly to profitability. \n\n---\n\n### **3. Automotive Segment** \n- **SU7 Electric Vehicle**: \n - Delivered **350,000 units in 2024**, with unit losses narrowing to **¥27,300** (down from ¥39,800 previously). \n - The **SU7 Ultra** (priced at ¥814,900) is set to launch in March 2025, targeting premium markets. \n - Achieved a **Nürburgring lap record of 6:46.874**, positioning it as the fastest four-door car. \n- **Challenges**: \n - Negative gross margin (**-35%** in some quarters) due to high R&D and production costs. \n\n---\n\n### **4. Strengths & Opportunities** \n- **Innovation**: Flagship products like the **Redmi Note 14 Pro+ 5G** and SU7 EV showcase design and performance leadership. \n- **Global Reach**: Strong presence in emerging markets (e.g., India, Southeast Asia) and expanding into Europe/Africa. \n- **Ecosystem Synergy**: Integration of smartphones, IoT, and automotive products enhances user retention. \n\n---\n\n### **5. Risks & Challenges** \n- **Automotive Losses**: High unit costs and negative margins could strain finances if scaling slows. \n- **Competition**: Intense rivalry in smartphones (Samsung, Apple) and EVs (Tesla, BYD). \n- **Margin Pressure**: Smartphone gross margins lag behind competitors like Midea (18.5%). \n\n---\n\n### **6. Strategic Outlook** \n- Xiaomi aims to balance **aggressive innovation** (e.g., SU7 Ultra, HyperOS 2) with **profitability improvements** in core segments. \n- The **IoT and internet services** are critical for offsetting automotive losses and sustaining long-term growth. \n\n**Conclusion**: Xiaomi remains a formidable player in consumer electronics and EVs, leveraging its ecosystem strategy to drive growth. However, profitability hinges on reducing automotive costs and maintaining smartphone momentum. \n\n--- \n*Analysis based on 2024 financial reports and strategic updates.*", - "reasoning_content": "Okay, let's start by looking at the user's question: \"Analyze Xiaomi Corporation.\" The user wants a comprehensive analysis, so I need to cover various aspects like financials, market position, product lines, and strategic initiatives.\n\nFirst, from the web search results, I found that Xiaomi's 2024 financials show a total revenue of ¥104.1 billion, with Q1 2024 seeing a 27% YoY increase. However, there's an adjusted net loss of ¥3.21 billion for the full year, mainly due to the automotive segment's losses. The smartphone business is performing well with 40.6 million units shipped in Q1 2024, a 33.7% increase. Their IoT and internet services are contributing positively to profitability.\n\nNext, the market position: Xiaomi is ranked top 3 in 56 markets and top 5 in 67 globally. Their Redmi Note 14 Pro+ 5G and the SU7 electric car are notable products. The SU7 set a Nürburgring lap record, which is a significant achievement, but the automotive segment still has a negative gross margin of -35%, indicating high R&D and production costs.\n\nStrategic initiatives include the \"human-car-home ecosystem\" with the launch of Xiaomi HyperOS 2 and the SU7 Ultra. They're also expanding their IoT and lifestyle products, which are their \"brightest\" segment. However, the automotive division's unit losses are a challenge, though they've improved from previous periods.\n\nChallenges include the ongoing losses in the automotive sector and the need to balance innovation with profitability. Strengths are their strong smartphone growth, diverse product ecosystem, and expanding global presence. Opportunities lie in the EV market and IoT expansion, while threats include competition in smartphones and automotive sectors.\n\nPutting this all together, the analysis should highlight their financial performance, market strengths, product innovations, strategic moves, and the challenges they face, especially in the automotive segment. The response should be structured clearly, using the data from the web searches to back up each point.", - "tool_calls": [], - "timestamp": "2025-06-12 00:31:23.710624", - "add_reasoning_content_when_content_is_empty": true, - "metadata": {} - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/cookbook/simple_agent/your_own_agent.py b/cookbook/simple_agent/your_own_agent.py index dfc5726c..fe0cd947 100644 --- a/cookbook/simple_agent/your_own_agent.py +++ b/cookbook/simple_agent/your_own_agent.py @@ -1,121 +1,24 @@ -import datetime import json -from pathlib import Path -from typing import List +from dotenv import load_dotenv from loguru import logger -from pydantic import Field, BaseModel -from experiencemaker.utils.util_function import load_env_keys +from experiencemaker.schema.request import AgentRequest +from experiencemaker.service.experience_maker_client import ExperienceMakerClient -load_env_keys("../../.env") - -from experiencemaker.model import OpenAICompatibleBaseLLM -from experiencemaker.model.base_llm import BaseLLM -from experiencemaker.module.prompt.prompt_mixin import PromptMixin -from experiencemaker.schema.trajectory import Message, ActionMessage, ToolCall, StateMessage -from experiencemaker.tool import CodeTool, DashscopeSearchTool, TerminateTool -from experiencemaker.tool.base_tool import BaseTool - - -class AgentContext(BaseModel): - current_step: int = Field(default=-1) - query: str = Field(default="") - messages: List[Message] = Field(default_factory=list) - metadata: dict = Field(default_factory=dict) - has_terminate_tool: bool = Field(default=False) - - -class YourOwnAgent(PromptMixin): - llm: BaseLLM | None = Field(default=None) - max_steps: int = Field(default=10) - tools: List[BaseTool] = [CodeTool(), DashscopeSearchTool(), TerminateTool()] - prompt_file_path: Path = Path(__file__).parent / "your_own_agent_prompt.yaml" - add_reasoning_content_when_content_is_empty: bool = Field(default=True) - - def think(self, context: AgentContext): - now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - tool_names = [x.name for x in self.tools] - - if context.current_step == 0: - user_prompt = self.prompt_format(prompt_name="role_prompt", - time=now_time, - tools=", ".join(tool_names), - query=context.query) - - elif context.has_terminate_tool: - user_prompt = self.prompt_format(prompt_name="final_prompt", query=context.query) - - else: - user_prompt = self.prompt_format(prompt_name="next_prompt", query=context.query) - - context.messages.append(Message(content=user_prompt, - add_reasoning_content_when_content_is_empty=self.add_reasoning_content_when_content_is_empty)) - logger.info(f"step.{context.current_step} user_prompt={user_prompt}") - - if context.has_terminate_tool: - action_msg: ActionMessage = self.llm.chat(context.messages) - - else: - action_msg: ActionMessage = self.llm.chat(context.messages, tools=self.tools) - for tool in action_msg.tool_calls: - if tool.name == "terminate": - context.has_terminate_tool = True - break - - action_msg.add_reasoning_content_when_content_is_empty = self.add_reasoning_content_when_content_is_empty - context.messages.append(action_msg) - action_msg_context: str = action_msg.content + "\n\n" + action_msg.reasoning_content - logger.info(f"step.{context.current_step} action_msg_context={action_msg_context} " - f"tool_calls={action_msg.tool_calls}") - return True if action_msg.tool_calls else False - - def act(self, context: AgentContext): - action_msg = context.messages[-1] - assert isinstance(action_msg, ActionMessage) - - tool_dict = {tool.name: tool for tool in self.tools} - - new_tool_calls: List[ToolCall] = [] - for tool_call in action_msg.tool_calls: - if tool_call.name not in tool_dict: - continue - - new_tool_call = tool_call.model_copy(deep=True) - tool = tool_dict[tool_call.name] - new_tool_call.result = tool.execute(**tool_call.argument_dict) - new_tool_calls.append(new_tool_call) - - state_msg = StateMessage(tool_calls=new_tool_calls, - add_reasoning_content_when_content_is_empty=self.add_reasoning_content_when_content_is_empty) - state_msg.tool_result_to_content() - context.messages.append(state_msg) - logger.info(f"step.{context.current_step} state_msg_context={state_msg.content}") - - def run(self, query: str) -> List[Message]: - context: AgentContext = AgentContext(query=query) - - for i in range(self.max_steps): - context.current_step = i - - should_act: bool = self.think(context) - if should_act: - self.act(context) - else: - break - return context.messages +load_dotenv() def main(): - query = "Analyze Xiaomi Corporation." - # query = "分析一下小米公司" + # query = "Analyze Xiaomi Corporation." + query = "分析一下小米公司" - agent = YourOwnAgent(llm=OpenAICompatibleBaseLLM(model_name="qwen3-32b", temperature=0.000001)) - messages = agent.run(query=query) - answer = messages[-1].content - logger.info(answer) + client = ExperienceMakerClient() + request = AgentRequest(query=query) + response = client.call_agent(request) + logger.info(response.answer) with open("messages.jsonl", "w") as f: - f.write(json.dumps([x.model_dump() for x in messages], indent=2, ensure_ascii=False)) + f.write(json.dumps([x.model_dump() for x in response.messages], indent=2, ensure_ascii=False)) if __name__ == "__main__": main() diff --git a/experiencemaker/config/config_parser.py b/experiencemaker/config/config_parser.py index 77179fec..18847dff 100644 --- a/experiencemaker/config/config_parser.py +++ b/experiencemaker/config/config_parser.py @@ -8,7 +8,6 @@ from experiencemaker.schema.app_config import AppConfig class ConfigParser: - default_config_name: str = "demo_config.yaml" def __init__(self, args: list): # step1: default config @@ -20,7 +19,7 @@ class ConfigParser: if config_path: config_path = Path(config_path) else: - config_path = Path(__file__).parent / self.default_config_name + config_path = Path(__file__).parent / (AppConfig().default_config + ".yaml") logger.info(f"load config from path={config_path}") yaml_config = OmegaConf.load(config_path) self.app_config = OmegaConf.merge(self.app_config, yaml_config) diff --git a/experiencemaker/config/demo_config.yaml b/experiencemaker/config/demo_config.yaml index a17d632c..6a0deb20 100644 --- a/experiencemaker/config/demo_config.yaml +++ b/experiencemaker/config/demo_config.yaml @@ -13,6 +13,7 @@ api: retriever: build_query_op->recall_vector_store_op->merge_experience_op summarizer: simple_summary_op->update_vector_store_op vector_store: vector_store_action_op + agent: react_op op: build_query_op: @@ -36,11 +37,14 @@ op: vector_store_action_op: backend: vector_store_action_op vector_store: default + react_op: + backend: react_v1_op + llm: default llm: default: backend: openai_compatible - model_name: qwen3-32b + model_name: qwen-max-2025-01-25 params: temperature: 0.6 diff --git a/experiencemaker/op/__init__.py b/experiencemaker/op/__init__.py index c8c85319..69bd4574 100644 --- a/experiencemaker/op/__init__.py +++ b/experiencemaker/op/__init__.py @@ -9,3 +9,4 @@ from experiencemaker.op.summarizer.simple_summary_op import SimpleSummaryOp from experiencemaker.op.vector_store.update_vector_store_op import UpdateVectorStoreOp from experiencemaker.op.vector_store.recall_vector_store_op import RecallVectorStoreOp from experiencemaker.op.vector_store.vector_store_action_op import VectorStoreActionOp +from experiencemaker.op.react.react_v1_op import ReactV1Op diff --git a/experiencemaker/op/react/__init__.py b/experiencemaker/op/react/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/experiencemaker/op/react/react_v1_op.py b/experiencemaker/op/react/react_v1_op.py new file mode 100644 index 00000000..b5ad05f2 --- /dev/null +++ b/experiencemaker/op/react/react_v1_op.py @@ -0,0 +1,66 @@ +import datetime +import time +from typing import List, Dict + +from loguru import logger + +from experiencemaker.enumeration.role import Role +from experiencemaker.op import OP_REGISTRY +from experiencemaker.op.base_op import BaseOp +from experiencemaker.schema.message import Message +from experiencemaker.schema.request import AgentRequest +from experiencemaker.schema.response import AgentResponse +from experiencemaker.tool import TOOL_REGISTRY +from experiencemaker.tool.base_tool import BaseTool + + +@OP_REGISTRY.register() +class ReactV1Op(BaseOp): + current_path: str = __file__ + + def execute(self): + request: AgentRequest = self.context.request + response: AgentResponse = self.context.response + + max_steps: int = int(self.op_params.get("max_steps", 10)) + tool_names = self.op_params.get("tool_names", "code_tool,dashscope_search_tool") + tools: List[BaseTool] = [TOOL_REGISTRY[x.strip()]() for x in tool_names.split(",") if x] + tool_dict: Dict[str, BaseTool] = {x.name: x for x in tools} + now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + system_prompt = self.prompt_format(prompt_name="role_prompt", + time=now_time, + tools=",".join([x.name for x in tools])) + logger.info(f"system_prompt={system_prompt}") + + messages: List[Message] = [ + Message(role=Role.SYSTEM, content=system_prompt), + Message(role=Role.USER, content=request.query) + ] + + for i in range(max_steps): + assistant_message: Message = self.llm.chat(messages, tools=tools) + messages.append(assistant_message) + logger.info(f"assistant.{i}.content={assistant_message.content}") + + if not assistant_message.tool_calls: + break + + for j, tool_call in enumerate(assistant_message.tool_calls): + logger.info(f"submit step={i} tool_calls.name={tool_call.name} argument_dict={tool_call.argument_dict}") + + if tool_call.name not in tool_dict: + continue + + self.submit_task(tool_dict[tool_call.name].execute, **tool_call.argument_dict) + time.sleep(1) + + for tool_result, tool_call in zip(self.join_task(), assistant_message.tool_calls): + logger.info(f"submit step={i} tool_calls.name={tool_call.name} tool_result={tool_result}") + + assert isinstance(tool_result, str) + tool_message = Message(role=Role.TOOL, content=tool_result, tool_call_id=tool_call.id) + messages.append(tool_message) + + response.messages = messages + response.answer = response.messages[-1].content diff --git a/experiencemaker/op/react/react_v1_prompt.yaml b/experiencemaker/op/react/react_v1_prompt.yaml new file mode 100644 index 00000000..09f091b4 --- /dev/null +++ b/experiencemaker/op/react/react_v1_prompt.yaml @@ -0,0 +1,7 @@ +role_prompt: | + You are a helpful assistant named BeyondAgent. + The current time is {time}. + + Please proactively choose the most suitable tool or combination of tools based on the user's question, including {tools} etc. + For complex tasks, you can break down the problem step by step and use different tools to solve it incrementally. + Please determine the response language based on the language of the user's question. diff --git a/experiencemaker/op/react/simple_react_context.py b/experiencemaker/op/react/simple_react_context.py new file mode 100644 index 00000000..25f9dc9c --- /dev/null +++ b/experiencemaker/op/react/simple_react_context.py @@ -0,0 +1,21 @@ +from typing import List + +from pydantic import Field, BaseModel + +from experiencemaker.schema.message import Message +from experiencemaker.tool.base_tool import BaseTool + + +class ReactContext(BaseModel): + current_step: int = Field(default=-1) + max_steps: int = Field(default=-1) + query: str = Field(default="") + messages: List[Message] = Field(default_factory=list) + has_terminate_tool: bool = Field(default=False) + tools: List[BaseTool] = Field(default_factory=list) + now_time: str = Field(default="") + metadata: dict = Field(default_factory=dict) + + @property + def tool_names(self) -> List[str]: + return [tool.name for tool in self.tools] diff --git a/experiencemaker/op/react/simple_react_op.py b/experiencemaker/op/react/simple_react_op.py new file mode 100644 index 00000000..8eaedd4e --- /dev/null +++ b/experiencemaker/op/react/simple_react_op.py @@ -0,0 +1,100 @@ +import datetime +from typing import List + +from loguru import logger + +from experiencemaker.enumeration.role import Role +from experiencemaker.op import OP_REGISTRY +from experiencemaker.op.base_op import BaseOp +from experiencemaker.op.react.simple_react_context import ReactContext +from experiencemaker.schema.message import Message +from experiencemaker.schema.request import AgentRequest +from experiencemaker.schema.response import AgentResponse +from experiencemaker.tool import TOOL_REGISTRY +from experiencemaker.tool.base_tool import BaseTool + + +@OP_REGISTRY.register() +class SimpleReactOp(BaseOp): + current_path: str = __file__ + + def execute(self): + request: AgentRequest = self.context.request + response: AgentResponse = self.context.response + + max_steps: int = int(self.op_params.get("max_steps", 10)) + tool_names = self.op_params.get("tool_names", "code_tool,dashscope_search_tool,terminate_tool") + tools: List[BaseTool] = [TOOL_REGISTRY[x.strip()]() for x in tool_names.split(",") if x] + now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + context: ReactContext = ReactContext(max_steps=max_steps, + query=request.query, + tools=tools, + now_time=now_time) + + for i in range(max_steps): + context.current_step = i + should_act: bool = self.think(context) + + if should_act: + self.act(context) + + else: + break + + response.messages = context.messages + response.answer = response.messages[-1].content + + def think(self, context: ReactContext): + next_prompt_flag: bool = False + if context.current_step == 0: + user_prompt = self.prompt_format(prompt_name="role_prompt", + time=context.now_time, + tools=",".join(context.tool_names), + query=context.query) + + elif context.has_terminate_tool: + user_prompt = self.prompt_format(prompt_name="final_prompt", query=context.query) + else: + user_prompt = self.prompt_format(prompt_name="next_prompt", query=context.query) + next_prompt_flag = True + + user_message = Message(content=user_prompt, metadata={"next_prompt": next_prompt_flag}) + context.messages = [m for m in context.messages if not m.metadata.get("next_prompt", False)] + [user_message] + logger.info(f"step.{context.current_step} user_prompt={user_prompt}") + + if context.has_terminate_tool: + assistant_messages: Message = self.llm.chat(context.messages) + + else: + assistant_messages: Message = self.llm.chat(context.messages, tools=context.tools) + for tool in assistant_messages.tool_calls: + if tool.name == "terminate": + context.has_terminate_tool = True + break + + context.messages.append(assistant_messages) + + assistant_content: str = assistant_messages.content + "\n\n" + assistant_messages.reasoning_content + logger.info(f"step.{context.current_step} " + f"assistant_content={assistant_content} " + f"tool_calls={assistant_messages.tool_calls}") + + return True if assistant_messages.tool_calls else False + + def act(self, context: ReactContext): + assistant_message = context.messages[-1] + tool_dict = {tool.name: tool for tool in context.tools} + + tool_messages: List[Message] = [] + for tool_call in assistant_message.tool_calls: + if tool_call.name not in tool_dict: + continue + + tool = tool_dict[tool_call.name] + tool_result = tool.execute(**tool_call.argument_dict) + assert isinstance(tool_result, str) + tool_messages.append(Message(role=Role.TOOL, content=tool_result, tool_call_id=tool_call.id)) + logger.info(f"step.{context.current_step}.{tool_call.name} content={tool_result}") + + context.messages.extend(tool_messages) diff --git a/experiencemaker/op/react/simple_react_prompt.yaml b/experiencemaker/op/react/simple_react_prompt.yaml new file mode 100644 index 00000000..c854f4d6 --- /dev/null +++ b/experiencemaker/op/react/simple_react_prompt.yaml @@ -0,0 +1,28 @@ +role_prompt: | + You are a helpful assistant named BeyondAgent. + The current time is {time}. + + Please proactively choose the most suitable tool or combination of tools based on the user's question, including {tools} etc. + For complex tasks, you can break down the problem step by step and use different tools to solve it incrementally. + Please determine the response language based on the language of the user's question. + + User question + {query} + +next_prompt: | + User question + {query} + + Plan the most suitable tool or combination of tools based on the context and the user's question. + For complex tasks, break down the problem and use different tools step by step to solve it, don't give up easily. + Try calling the same tool multiple times with different parameters to obtain information from various perspectives. + If the task is completed and the user's question can now be answered, use the **terminate** tool. + Please determine the response language based on the language of the user's question. + +final_prompt: | + Please integrate the context and provide a complete answer to the user's question. + Please determine the response language based on the language of the user's question. + + User question + {query} + diff --git a/experiencemaker/schema/app_config.py b/experiencemaker/schema/app_config.py index 407431c6..1ccc4015 100644 --- a/experiencemaker/schema/app_config.py +++ b/experiencemaker/schema/app_config.py @@ -57,6 +57,7 @@ class VectorStoreConfig: @dataclass class AppConfig: + default_config: str = field(default="demo_config") config_path: str = field(default="") http_service: HttpServiceConfig = field(default_factory=HttpServiceConfig) thread_pool: ThreadPoolConfig = field(default_factory=ThreadPoolConfig) diff --git a/experiencemaker/schema/message.py b/experiencemaker/schema/message.py index 186bc9a3..9f42a248 100644 --- a/experiencemaker/schema/message.py +++ b/experiencemaker/schema/message.py @@ -1,5 +1,5 @@ import json -from typing import List +from typing import List, Any from pydantic import BaseModel, Field, model_validator @@ -39,12 +39,12 @@ class ToolCall(BaseModel): "index": self.index, } - class Message(BaseModel): role: Role = Field(default=Role.USER) content: str | bytes = Field(default="") reasoning_content: str = Field(default="") tool_calls: List[ToolCall] = Field(default_factory=list) + tool_call_id: str = Field(default="") metadata: dict = Field(default_factory=dict) def simple_dump(self, add_reason_when_empty: bool = True) -> dict: diff --git a/experiencemaker/schema/request.py b/experiencemaker/schema/request.py index 988ebccf..4e35df87 100644 --- a/experiencemaker/schema/request.py +++ b/experiencemaker/schema/request.py @@ -6,7 +6,7 @@ from experiencemaker.schema.message import Message, Trajectory class BaseRequest(BaseModel): - workspace_id: str = Field(default=...) + workspace_id: str = Field(default="default") config: dict = Field(default_factory=dict) @@ -28,3 +28,5 @@ class VectorStoreRequest(BaseRequest): class AgentRequest(BaseRequest): query: str = Field(default="") + messages: List[Message] = Field(default_factory=list) + diff --git a/experiencemaker/service/experience_maker_client.py b/experiencemaker/service/experience_maker_client.py index 565b6b90..1cfd175b 100644 --- a/experiencemaker/service/experience_maker_client.py +++ b/experiencemaker/service/experience_maker_client.py @@ -7,7 +7,7 @@ from experiencemaker.utils.http_client import HttpClient class ExperienceMakerClient(HttpClient): - base_url: str = Field(default=...) + base_url: str = Field(default="http://0.0.0.0:8001") def call_retriever(self, request: RetrieverRequest): self.url = self.base_url + "/retriever" @@ -27,7 +27,7 @@ class ExperienceMakerClient(HttpClient): if __name__ == "__main__": - client = ExperienceMakerClient(base_url="http://0.0.0.0:8001") + client = ExperienceMakerClient() workspace_id = "t123" response = client.call_summarizer( SummarizerRequest(workspace_id=workspace_id, diff --git a/experiencemaker/tool/__init__.py b/experiencemaker/tool/__init__.py index f5d80aba..803a16e5 100644 --- a/experiencemaker/tool/__init__.py +++ b/experiencemaker/tool/__init__.py @@ -1,3 +1,8 @@ from experiencemaker.utils.registry import Registry -TOOL_REGISTRY = Registry() \ No newline at end of file +TOOL_REGISTRY = Registry() + +from experiencemaker.tool.code_tool import CodeTool +from experiencemaker.tool.dashscope_search_tool import DashscopeSearchTool +from experiencemaker.tool.terminate_tool import TerminateTool +from experiencemaker.tool.mcp_tool import MCPTool diff --git a/experiencemaker/tool/code_tool.py b/experiencemaker/tool/code_tool.py index 8b61b85f..21c9b265 100644 --- a/experiencemaker/tool/code_tool.py +++ b/experiencemaker/tool/code_tool.py @@ -1,9 +1,11 @@ import sys from io import StringIO +from experiencemaker.tool import TOOL_REGISTRY from experiencemaker.tool.base_tool import BaseTool +@TOOL_REGISTRY.register() class CodeTool(BaseTool): name: str = "python_execute" description: str = "Execute python code can be used in scenarios such as analysis or calculation, and the final result can be printed using the `print` function." diff --git a/experiencemaker/tool/dashscope_search_tool.py b/experiencemaker/tool/dashscope_search_tool.py index c0deaf58..04a8746f 100644 --- a/experiencemaker/tool/dashscope_search_tool.py +++ b/experiencemaker/tool/dashscope_search_tool.py @@ -7,9 +7,11 @@ from dotenv import load_dotenv from loguru import logger from pydantic import Field +from experiencemaker.tool import TOOL_REGISTRY from experiencemaker.tool.base_tool import BaseTool +@TOOL_REGISTRY.register() class DashscopeSearchTool(BaseTool): name: str = "web_search" description: str = "Use search keywords to retrieve relevant information from the internet. " \ diff --git a/experiencemaker/tool/mcp_tool.py b/experiencemaker/tool/mcp_tool.py index 21315da4..96c1ba9f 100644 --- a/experiencemaker/tool/mcp_tool.py +++ b/experiencemaker/tool/mcp_tool.py @@ -5,9 +5,11 @@ from mcp import ClientSession from mcp.client.sse import sse_client from pydantic import Field, model_validator +from experiencemaker.tool import TOOL_REGISTRY from experiencemaker.tool.base_tool import BaseTool +@TOOL_REGISTRY.register() class MCPTool(BaseTool): server_url: str = Field(..., description="MCP server URL") tool_name_list: List[str] = Field(default_factory=list) diff --git a/experiencemaker/tool/terminate_tool.py b/experiencemaker/tool/terminate_tool.py index fb2412e7..fcb777cd 100644 --- a/experiencemaker/tool/terminate_tool.py +++ b/experiencemaker/tool/terminate_tool.py @@ -1,6 +1,8 @@ +from experiencemaker.tool import TOOL_REGISTRY from experiencemaker.tool.base_tool import BaseTool +@TOOL_REGISTRY.register() class TerminateTool(BaseTool): name: str = "terminate" description: str = "If you can answer the user's question based on the context, be sure to use the **terminate** tool." From 19d17bef85f6baf9740c4fe6c97bce0b34084151 Mon Sep 17 00:00:00 2001 From: "jinli.yl" Date: Fri, 18 Jul 2025 16:00:06 +0800 Subject: [PATCH 2/2] bugfix --- experiencemaker/config/config_parser.py | 3 ++- experiencemaker/schema/app_config.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/experiencemaker/config/config_parser.py b/experiencemaker/config/config_parser.py index 18847dff..5a0418a3 100644 --- a/experiencemaker/config/config_parser.py +++ b/experiencemaker/config/config_parser.py @@ -19,7 +19,8 @@ class ConfigParser: if config_path: config_path = Path(config_path) else: - config_path = Path(__file__).parent / (AppConfig().default_config + ".yaml") + pre_defined_config = cli_config.get("pre_defined_config") + config_path = Path(__file__).parent / (pre_defined_config + ".yaml") logger.info(f"load config from path={config_path}") yaml_config = OmegaConf.load(config_path) self.app_config = OmegaConf.merge(self.app_config, yaml_config) diff --git a/experiencemaker/schema/app_config.py b/experiencemaker/schema/app_config.py index 1ccc4015..8a4cf8ab 100644 --- a/experiencemaker/schema/app_config.py +++ b/experiencemaker/schema/app_config.py @@ -57,7 +57,7 @@ class VectorStoreConfig: @dataclass class AppConfig: - default_config: str = field(default="demo_config") + pre_defined_config: str = field(default="demo_config") config_path: str = field(default="") http_service: HttpServiceConfig = field(default_factory=HttpServiceConfig) thread_pool: ThreadPoolConfig = field(default_factory=ThreadPoolConfig)