diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..0c2fdb7 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,24 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "parserOptions": { + "ecmaVersion": 2021, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + "semi": ["error", "always"], + "quotes": ["error", "double"], + "indent": ["error", 2], + "linebreak-style": ["error", "unix"], + "brace-style": ["error", "1tbs"], + "curly": ["error", "all"], + "no-eval": ["error"], + "prefer-const": ["error"], + "arrow-spacing": ["error", { "before": true, "after": true }] + } +} \ No newline at end of file diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index f9de56d..763cda4 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -13,9 +13,27 @@ jobs: OS: ${{ matrix.os }} PYTHON: '3.10' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.10' + - name: Update setuptools and wheel + run: | + pip install setuptools==68.2.2 wheel==0.41.2 + - name: Install pre-commit + run: | + pip install pre-commit + - name: Install pre-commit hooks + run: | + pre-commit install + - name: Run pre-commit + run: | + pre-commit run --all-files > pre-commit.log 2>&1 || true + cat pre-commit.log + if grep -q Failed pre-commit.log; then + echo -e "\e[41m [**FAIL**] Please install pre-commit and format your code first. \e[0m" + exit 1 + fi + echo -e "\e[46m ********************************Passed******************************** \e[0m" diff --git a/.github/workflows/test_agent_deep_research.yml b/.github/workflows/test_agent_deep_research.yml index 831569b..43c94c4 100644 --- a/.github/workflows/test_agent_deep_research.yml +++ b/.github/workflows/test_agent_deep_research.yml @@ -1,8 +1,5 @@ name: deep_research_runtime_test -on: - schedule: - - cron: '0 0 */3 * *' - workflow_dispatch: +on: [push, pull_request] jobs: test: @@ -33,5 +30,7 @@ jobs: pip install pytest pytest-asyncio pytest-mock - name: Run tests + env: + PYTHONPATH: ${{ github.workspace }}/deep_research/agent_deep_research run: | python -m pytest tests/agent_deep_research_test.py -v \ No newline at end of file diff --git a/.github/workflows/test_browser_agent_test.yml b/.github/workflows/test_browser_agent_test.yml index 67b8114..3316dd2 100644 --- a/.github/workflows/test_browser_agent_test.yml +++ b/.github/workflows/test_browser_agent_test.yml @@ -1,9 +1,6 @@ name: BrowserAgent Tests -on: - schedule: - - cron: '0 0 */3 * *' - workflow_dispatch: +on: [push] jobs: test: @@ -33,13 +30,14 @@ jobs: - name: Install Dependencies run: | - cd browser_agent/agent_browser + cd browser_use/agent_browser python -m pip install --upgrade pip pip install pytest pytest-asyncio pip install -r requirements.txt - name: Run Tests env: + PYTHONPATH: ${{ github.workspace }}/browser_use/agent_browser DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} run: | # ✅ Ensure test-results directory exists diff --git a/.github/workflows/test_browser_use_fullstack_runtime.yml b/.github/workflows/test_browser_use_fullstack_runtime.yml index 6ce10fc..a23bab0 100644 --- a/.github/workflows/test_browser_use_fullstack_runtime.yml +++ b/.github/workflows/test_browser_use_fullstack_runtime.yml @@ -1,8 +1,5 @@ name: browser_use_fullstack_runtime_test -on: - schedule: - - cron: '0 0 */3 * *' - workflow_dispatch: +on: [push] jobs: test: @@ -37,6 +34,7 @@ jobs: - name: Run tests env: DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} + PYTHONPATH: ${{ github.workspace }}/browser_use/browser_use_fullstack_runtime/backend run: | # ✅ Use validated path from debug output python -m pytest tests/browser_use_fullstack_runtime_test.py -v \ No newline at end of file diff --git a/.github/workflows/test_conversational_agents_chatbot.yml b/.github/workflows/test_conversational_agents_chatbot.yml index fe2f352..ac59f67 100644 --- a/.github/workflows/test_conversational_agents_chatbot.yml +++ b/.github/workflows/test_conversational_agents_chatbot.yml @@ -1,8 +1,5 @@ name: Conversational Agents Chatbot Test -on: - schedule: - - cron: '0 0 */3 * *' - workflow_dispatch: +on: [push] jobs: test: @@ -30,6 +27,7 @@ jobs: - name: Run tests env: + PYTHONPATH: ${{ github.workspace }}/conversational_agents/chatbot DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} run: | # ✅ Use correct relative path diff --git a/.github/workflows/test_conversational_agents_chatbot_fullstack_runtime_webserver.yml b/.github/workflows/test_conversational_agents_chatbot_fullstack_runtime_webserver.yml index 1b8da3a..b7bb254 100644 --- a/.github/workflows/test_conversational_agents_chatbot_fullstack_runtime_webserver.yml +++ b/.github/workflows/test_conversational_agents_chatbot_fullstack_runtime_webserver.yml @@ -1,8 +1,5 @@ name: Flask API Runtime Test -on: - schedule: - - cron: '0 0 */3 * *' - workflow_dispatch: +on: [push] jobs: test: @@ -33,5 +30,7 @@ jobs: pip install pytest pytest-asyncio - name: Run tests + env: + PYTHONPATH: ${{ github.workspace }}/conversational_agents/chatbot_fullstack_runtime run: | python -m pytest tests/conversational_agents_chatbot_fullstack_runtime_webserver_test.py -v \ No newline at end of file diff --git a/.github/workflows/test_evaluation.yml b/.github/workflows/test_evaluation.yml index 7e00dd8..99cee2c 100644 --- a/.github/workflows/test_evaluation.yml +++ b/.github/workflows/test_evaluation.yml @@ -1,8 +1,5 @@ name: ACE Benchmark Evaluation Test -on: - schedule: - - cron: '0 0 */3 * *' - workflow_dispatch: +on: [push] jobs: test: @@ -33,6 +30,7 @@ jobs: - name: Run tests env: + PYTHONPATH: ${{ env.GITHUB_WORKSPACE }}/evaluation/ace_bench DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} run: | python -m pytest tests/evaluation_test.py -v \ No newline at end of file diff --git a/.github/workflows/test_game.yml b/.github/workflows/test_game.yml index e83b6fb..cd8292c 100644 --- a/.github/workflows/test_game.yml +++ b/.github/workflows/test_game.yml @@ -1,9 +1,6 @@ name: Run test_game.py -on: - schedule: - - cron: '0 0 */3 * *' - workflow_dispatch: +on: [push] jobs: test: @@ -21,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: | @@ -32,7 +29,7 @@ jobs: - name: Run game_test.py env: DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} - PYTHONPATH: ${{ env.GITHUB_WORKSPACE }}/games/game_werewolves + PYTHONPATH: $GITHUB_WORKSPACE/games/game_werewolves run: | # ✅ Ensure correct working directory - python -m pytest tests/game_test.py -v \ No newline at end of file + PYTHONPATH=$GITHUB_WORKSPACE/games/game_werewolves python -m pytest tests/game_test.py -v \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..dfd45a5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,121 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-ast + - id: sort-simple-yaml + - id: check-yaml + exclude: | + (?x)^( + meta.yaml + )$ + - id: check-xml + - id: check-toml + - id: check-docstring-first + - id: check-json + - id: fix-encoding-pragma + - id: detect-private-key + - id: trailing-whitespace + - repo: https://github.com/asottile/add-trailing-comma + rev: v3.1.0 + hooks: + - id: add-trailing-comma + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.7.0 + hooks: + - id: mypy + exclude: + (?x)( + pb2\.py$ + | grpc\.py$ + | ^docs + | \.html$ + ) + args: [ + --ignore-missing-imports, + --disable-error-code=var-annotated, + --disable-error-code=union-attr, + --disable-error-code=assignment, + --disable-error-code=attr-defined, + --disable-error-code=import-untyped, + --disable-error-code=truthy-function, + --follow-imports=skip, + --explicit-package-bases, + ] + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + args: [ --line-length=79 ] + - repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + args: [ "--extend-ignore=E203"] + - repo: https://github.com/pylint-dev/pylint + rev: v3.0.2 + hooks: + - id: pylint + exclude: + (?x)( + ^docs + | pb2\.py$ + | grpc\.py$ + | \.demo$ + | \.md$ + | \.html$ + ) + args: [ + --disable=W0511, + --disable=W0718, + --disable=W0122, + --disable=C0103, + --disable=R0913, + --disable=E0401, + --disable=E1101, + --disable=C0415, + --disable=W0603, + --disable=R1705, + --disable=R0914, + --disable=E0601, + --disable=W0602, + --disable=W0604, + --disable=R0801, + --disable=R0902, + --disable=R0903, + --disable=C0123, + --disable=W0231, + --disable=W1113, + --disable=W0221, + --disable=R0401, + --disable=W0632, + --disable=W0123, + --disable=C3001, + --disable=W0201, + --disable=C0302, + --disable=W1203, + --disable=C2801, + --disable=C0114, # Disable missing module docstring for quick dev + --disable=C0115, # Disable missing class docstring for quick dev + --disable=C0116, # Disable missing function or method docstring for quick dev + ] + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v7.32.0 + hooks: + - id: eslint + files: \.(js|jsx)$ + exclude: '.*js_third_party.*' + args: [ '--fix' ] + - repo: https://github.com/thibaudcolas/pre-commit-stylelint + rev: v14.4.0 + hooks: + - id: stylelint + files: \.(css)$ + exclude: '.*css_third_party.*' + args: [ '--fix' ] + - repo: https://github.com/pre-commit/mirrors-prettier + rev: 'v3.0.0' + hooks: + - id: prettier + additional_dependencies: [ 'prettier@3.0.0' ] + files: \.(tsx?)$ \ No newline at end of file diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000..a5d2a39 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,6 @@ +{ + "rules": { + "indentation": 2, + "string-quotes": "double" + } +} \ No newline at end of file diff --git a/README_zh.md b/README_zh.md index f1aaa5d..c3ec44f 100644 --- a/README_zh.md +++ b/README_zh.md @@ -137,9 +137,9 @@ AgentScope Runtime 是一个**全面的运行时框架**,主要解决部署和 如果你: -- 需要安装帮助 -- 遇到问题 -- 想了解某个示例的工作方式 +- 需要安装帮助 +- 遇到问题 +- 想了解某个示例的工作方式 请: @@ -157,10 +157,10 @@ AgentScope Runtime 是一个**全面的运行时框架**,主要解决部署和 欢迎提交: -- Bug 报告 -- 新功能请求 -- 文档改进 -- 代码贡献 +- Bug 报告 +- 新功能请求 +- 文档改进 +- 代码贡献 详情见 [Contributing](https://github.com/agentscope-ai/agentscope-samples/blob/main/CONTRIBUTING_zh.md) 文档。 diff --git a/browser_use/agent_browser/browser_agent.py b/browser_use/agent_browser/browser_agent.py index b0db72f..d9d5630 100644 --- a/browser_use/agent_browser/browser_agent.py +++ b/browser_use/agent_browser/browser_agent.py @@ -4,20 +4,25 @@ import re import uuid -from typing import Any, Optional +from typing import Optional, Any from agentscope.agent import ReActAgent from agentscope.formatter import FormatterBase from agentscope.memory import MemoryBase -from agentscope.message import Msg, TextBlock, ToolUseBlock +from agentscope.message import ( + Msg, + ToolUseBlock, + TextBlock, +) from agentscope.model import ChatModelBase -from agentscope.token import OpenAITokenCounter, TokenCounterBase from agentscope.tool import Toolkit +from agentscope.token import TokenCounterBase, OpenAITokenCounter _BROWSER_AGENT_DEFAULT_SYS_PROMPT = ( "You are a helpful browser automation assistant. " "You can navigate websites, take screenshots, and interact with web pages." - "Always describe what you see and meta_planner_agent your next steps clearly. " + "Always describe what you see and meta_planner_agent" + " your next steps clearly. " "When taking actions, explain what you're doing and why." ) _BROWSER_AGENT_REASONING_PROMPT = ( @@ -318,7 +323,7 @@ class BrowserAgent(ReActAgent): ) # Format the prompt for the model - prompt = self.formatter.format( + prompt = await self.formatter.format( msgs=[ Msg("system", self.sys_prompt, "system"), *memory_msgs, diff --git a/browser_use/agent_browser/main.py b/browser_use/agent_browser/main.py index bb1e297..e2dcd25 100644 --- a/browser_use/agent_browser/main.py +++ b/browser_use/agent_browser/main.py @@ -10,7 +10,7 @@ from agentscope.memory import InMemoryMemory from agentscope.model import DashScopeChatModel from agentscope.tool import Toolkit -from .browser_agent import BrowserAgent # pylint: disable=C0411 +from browser_agent import BrowserAgent # pylint: disable=C0411 async def main() -> None: diff --git a/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py b/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py index 4510c24..9017746 100644 --- a/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py +++ b/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py @@ -3,8 +3,8 @@ import asyncio import os from agentscope.agent import ReActAgent -from agentscope_runtime.engine import LocalDeployManager, Runner from agentscope.model import DashScopeChatModel +from agentscope_runtime.engine import LocalDeployManager, Runner from agentscope_runtime.engine.agents.agentscope_agent import AgentScopeAgent from agentscope_runtime.engine.services.context_manager import ContextManager @@ -23,12 +23,13 @@ async def _local_deploy(): model = DashScopeChatModel( model_name="qwen-turbo", api_key=os.getenv("DASHSCOPE_API_KEY"), - ) agent = AgentScopeAgent( name="Friday", model=model, - agent_config={"sys_prompt": "A simple LLM agent to generate a short response"}, + agent_config={ + "sys_prompt": "A simple LLM agent to generate a short response", + }, agent_builder=ReActAgent, ) diff --git a/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py b/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py index 5124a8f..9ce54db 100644 --- a/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py +++ b/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py @@ -3,14 +3,13 @@ import json import logging import os from datetime import datetime -from typing import Tuple, Optional, Union, Dict, Any, Generator import requests from dotenv import load_dotenv -from flask import Flask, jsonify, request +from flask import Flask, request, jsonify from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy -from werkzeug.security import check_password_hash, generate_password_hash +from werkzeug.security import generate_password_hash, check_password_hash logging.basicConfig( level=logging.INFO, @@ -48,10 +47,10 @@ class User(db.Model): cascade="all, delete-orphan", ) - def set_password(self, password: str) -> None: + def set_password(self, password): self.password_hash = generate_password_hash(password) - def check_password(self, password: str) -> bool: + def check_password(self, password): return check_password_hash(self.password_hash, password) @@ -90,7 +89,7 @@ class Message(db.Model): # Create database tables -def create_tables() -> None: +def create_tables(): db.create_all() # Create sample users (if none exist) @@ -105,9 +104,7 @@ def create_tables() -> None: # functions -def parse_sse_line( - line: bytes, -) -> Tuple[Optional[str], Optional[Union[str, int]]]: +def parse_sse_line(line): line = line.decode("utf-8").strip() if line.startswith("data: "): return "data", line[6:] @@ -120,10 +117,7 @@ def parse_sse_line( return None, None -def sse_client( - url: str, - data: Optional[Dict[str, Any]] = None, -) -> Generator[str, None, None]: +def sse_client(url, data=None): headers = { "Accept": "text/event-stream", "Cache-Control": "no-cache", @@ -158,17 +152,13 @@ def sse_client( pass -def call_runner( - query: str, - query_user_id: str, - query_session_id: str, -) -> Generator[str, None, None]: +def call_runner(query, query_user_id, query_session_id): server_port = int(os.environ.get("SERVER_PORT", "8090")) server_endpoint = os.environ.get("SERVER_ENDPOINT", "agent") server_host = os.environ.get("SERVER_HOST", "localhost") url = f"http://{server_host}:{server_port}/{server_endpoint}" - data_arg: Dict[str, Any] = { + data_arg = { "input": [ { "role": "user", diff --git a/deep_research/agent_deep_research/built_in_prompt/promptmodule.py b/deep_research/agent_deep_research/built_in_prompt/promptmodule.py index ffe319d..af21193 100644 --- a/deep_research/agent_deep_research/built_in_prompt/promptmodule.py +++ b/deep_research/agent_deep_research/built_in_prompt/promptmodule.py @@ -95,14 +95,14 @@ class ReflectFailure(BaseModel): "properties": { "need_rephrase": { "type": "boolean", - "description": "Set to 'true' if the failed subtask " - "needs to be rephrased due to a design " + "description": "Set to 'true' if the failed" + " subtask needs to be rephrased due to a design " "flaw or misunderstanding; otherwise, 'false'.", }, "rephrased_plan": { "type": "string", - "description": "The modified working meta_planner_agent " - "with only the inappropriate " + "description": "The modified working " + "meta_planner_agent with only the inappropriate " "subtask replaced by its improved version. If no " "rephrasing is needed, provide an empty string.", }, diff --git a/deep_research/agent_deep_research/deep_research_agent.py b/deep_research/agent_deep_research/deep_research_agent.py index 522b104..c24c9e8 100644 --- a/deep_research/agent_deep_research/deep_research_agent.py +++ b/deep_research/agent_deep_research/deep_research_agent.py @@ -1,35 +1,46 @@ # -*- coding: utf-8 -*- """Deep Research Agent""" # pylint: disable=too-many-lines, no-name-in-module -import asyncio -import json import os -from copy import deepcopy +import json +import asyncio + +from typing import Type, Optional, Any, Tuple from datetime import datetime -from typing import Any, Optional, Tuple, Type +from copy import deepcopy import shortuuid -from agentscope import logger, setup_logger -from agentscope.agent import ReActAgent -from agentscope.formatter import FormatterBase -from agentscope.mcp import StatefulClientBase -from agentscope.memory import MemoryBase -from agentscope.message import Msg, TextBlock, ToolResultBlock, ToolUseBlock -from agentscope.model import ChatModelBase -from agentscope.tool import ToolResponse, view_text_file, write_text_file + from pydantic import BaseModel -from ..agent_deep_research.utils import ( - get_dynamic_tool_call_json, - get_structure_output, - load_prompt_dict, - truncate_search_result, -) -from .built_in_prompt.promptmodule import ( - FollowupJudge, - ReflectFailure, +from built_in_prompt.promptmodule import ( SubtasksDecomposition, WebExtraction, + FollowupJudge, + ReflectFailure, +) +from utils import ( + truncate_search_result, + load_prompt_dict, + get_dynamic_tool_call_json, + get_structure_output, +) +from agentscope import logger, setup_logger +from agentscope.mcp import StatefulClientBase +from agentscope.agent import ReActAgent +from agentscope.model import ChatModelBase +from agentscope.formatter import FormatterBase +from agentscope.memory import MemoryBase +from agentscope.tool import ( + ToolResponse, + view_text_file, + write_text_file, +) +from agentscope.message import ( + Msg, + ToolUseBlock, + TextBlock, + ToolResultBlock, ) _DEEP_RESEARCH_AGENT_DEFAULT_SYS_PROMPT = "You're a helpful assistant." @@ -149,7 +160,7 @@ class DeepResearchAgent(ReActAgent): # register all necessary tools for deep research agent self.toolkit.register_tool_function(view_text_file) self.toolkit.register_tool_function(write_text_file) - asyncio.create_task( + asyncio.get_running_loop().create_task( self.toolkit.register_mcp_client(search_mcp_client), ) @@ -213,16 +224,12 @@ class DeepResearchAgent(ReActAgent): reasoning_prompt = self.prompt_dict["reasoning_prompt"].format_map( { "objective": self.current_subtask[-1].objective, - "meta_planner_agent": ( - cur_plan - if cur_plan - else "There is no working meta_planner_agent now." - ), - "knowledge_gap": ( - f"## Knowledge Gaps:\n {cur_know_gap}" - if cur_know_gap - else "" - ), + "plan": cur_plan + if cur_plan + else "There is no working plan now.", + "knowledge_gap": f"## Knowledge Gaps:\n {cur_know_gap}" + if cur_know_gap + else "", "depth": len(self.current_subtask), }, ) @@ -300,7 +307,9 @@ class DeepResearchAgent(ReActAgent): # Async generator handling async for chunk in tool_res: # Turn into a tool result block - tool_res_msg.content[0]["output"] = chunk.content # type: ignore[index] + tool_res_msg.content[0][ # type: ignore[index] + "output" + ] = chunk.content # Skip the printing of the finish function call if ( @@ -488,13 +497,14 @@ class DeepResearchAgent(ReActAgent): async def decompose_and_expand_subtask(self) -> ToolResponse: """Identify the knowledge gaps of the current subtask and generate a - working meta_planner_agent by subtask decomposition. The working meta_planner_agent includes + working meta_planner_agent by subtask decomposition. + The working meta_planner_agent includes necessary steps for task completion and expanded steps. Returns: ToolResponse: - The knowledge gaps and working meta_planner_agent of the current subtask - in JSON format. + The knowledge gaps and working meta_planner_agent + of the current subtask in JSON format. """ if len(self.current_subtask) <= self.max_depth: decompose_sys_prompt = self.prompt_dict["decompose_sys_prompt"] @@ -947,7 +957,8 @@ class DeepResearchAgent(ReActAgent): Returns: ToolResponse: - The reflection about meta_planner_agent rephrasing and subtask decomposition. + The reflection about meta_planner_agent + rephrasing and subtask decomposition. """ reflect_sys_prompt = self.prompt_dict["reflect_sys_prompt"] conversation_history = "" diff --git a/deep_research/agent_deep_research/main.py b/deep_research/agent_deep_research/main.py index 06d0173..5db2e10 100644 --- a/deep_research/agent_deep_research/main.py +++ b/deep_research/agent_deep_research/main.py @@ -10,7 +10,7 @@ from agentscope.memory import InMemoryMemory from agentscope.message import Msg from agentscope.model import DashScopeChatModel -from .deep_research_agent import DeepResearchAgent +from deep_research_agent import DeepResearchAgent async def main(user_query: str) -> None: diff --git a/deep_research/agent_deep_research/utils.py b/deep_research/agent_deep_research/utils.py index 16eb8d3..921829f 100644 --- a/deep_research/agent_deep_research/utils.py +++ b/deep_research/agent_deep_research/utils.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """The utilities for deep research agent""" -import json import os +import json import re -from typing import Any, Sequence, Type, Union +from typing import Union, Sequence, Any, Type +from pydantic import BaseModel from agentscope.tool import Toolkit, ToolResponse -from pydantic import BaseModel TOOL_RESULTS_MAX_WORDS = 5000 @@ -281,11 +281,14 @@ def load_prompt_dict() -> dict: prompt_dict["summarize_hint"] = ( "Based on your work history above, examine which step in the " - "following working meta_planner_agent has been completed. Mark the completed " + "following working meta_planner_agent has been " + "completed. Mark the completed " "step with [DONE] at the end of its line (e.g., k. step k [DONE]) " "and leave the uncompleted steps unchanged. You MUST return only " - "the updated meta_planner_agent, preserving exactly the same format as the " - "original meta_planner_agent. Do not include any explanations, reasoning, " + "the updated meta_planner_agent, preserving exactly " + "the same format as the " + "original meta_planner_agent. Do not include any " + "explanations, reasoning, " "or section headers such as '## Working Plan:', just output the" "updated meta_planner_agent itself." "\n\n## Working Plan:\n{meta_planner_agent}" @@ -304,11 +307,13 @@ def load_prompt_dict() -> dict: "following report that consolidates and summarizes the essential " "findings:\n {intermediate_report}\n\n" "Such report has been saved to the {report_path}. " - "I will now **proceed to the next item** in the working meta_planner_agent." + "I will now **proceed to the next item** " + "in the working meta_planner_agent." ) prompt_dict["save_report_hint"] = ( - "The milestone results of the current item in working meta_planner_agent " + "The milestone results of the current " + "item in working meta_planner_agent " "are summarized into the following report:\n{intermediate_report}" ) diff --git a/deep_research/qwen_langgraph_search_fullstack_runtime/src/custom_search_tool.py b/deep_research/qwen_langgraph_search_fullstack_runtime/src/custom_search_tool.py index 91a7bb3..5c97129 100644 --- a/deep_research/qwen_langgraph_search_fullstack_runtime/src/custom_search_tool.py +++ b/deep_research/qwen_langgraph_search_fullstack_runtime/src/custom_search_tool.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- import os +import uuid import random import string import time -import uuid from base64 import b64encode from hashlib import sha256 from hmac import new as hmac_new -from typing import Any, Dict, List +from typing import List, Dict, Any import requests -from utils import format_time +from .utils import format_time class CustomSearchTool: diff --git a/deep_research/qwen_langgraph_search_fullstack_runtime/src/graph_openai_compatible.py b/deep_research/qwen_langgraph_search_fullstack_runtime/src/graph_openai_compatible.py index 9c404d6..c28c4b7 100644 --- a/deep_research/qwen_langgraph_search_fullstack_runtime/src/graph_openai_compatible.py +++ b/deep_research/qwen_langgraph_search_fullstack_runtime/src/graph_openai_compatible.py @@ -3,22 +3,25 @@ import asyncio import json import os import time -from typing import Any, Dict, List, Optional -from agentscope_runtime.engine.agents.langgraph_agent import LangGraphAgent -from agentscope_runtime.engine.helpers.helper import simple_call_agent_direct -from configuration import Configuration -from custom_search_tool import CustomSearchTool +from typing import List, Dict, Any, Optional + from dotenv import load_dotenv from langchain_core.messages import AIMessage, HumanMessage from langchain_core.runnables import RunnableConfig -from langgraph.graph import END, START, StateGraph +from langgraph.graph import START, END +from langgraph.graph import StateGraph from langgraph.types import Send +from agentscope_runtime.engine.agents.langgraph_agent import LangGraphAgent +from agentscope_runtime.engine.helpers.helper import simple_call_agent_direct + +from configuration import Configuration +from custom_search_tool import CustomSearchTool from llm_prompts import ( - answer_instructions, query_writer_instructions, - reflection_instructions, web_searcher_instructions, + reflection_instructions, + answer_instructions, ) from llm_utils import call_dashscope, extract_json_from_qwen from state import ( @@ -27,12 +30,12 @@ from state import ( ReflectionState, WebSearchState, ) -from utils import ( - custom_get_citations, - custom_resolve_urls, - get_current_date, +from .utils import ( get_research_topic, insert_citation_markers, + custom_resolve_urls, + custom_get_citations, + get_current_date, ) load_dotenv("../.env") diff --git a/deep_research/qwen_langgraph_search_fullstack_runtime/src/utils.py b/deep_research/qwen_langgraph_search_fullstack_runtime/src/utils.py index 451eafd..e8c1e4f 100644 --- a/deep_research/qwen_langgraph_search_fullstack_runtime/src/utils.py +++ b/deep_research/qwen_langgraph_search_fullstack_runtime/src/utils.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- import time -from datetime import datetime from typing import Any, Dict, List - -from langchain_core.messages import AIMessage, AnyMessage, HumanMessage +from datetime import datetime +from langchain_core.messages import AnyMessage, AIMessage, HumanMessage -def get_current_date() -> str: +def get_current_date(): return datetime.now().strftime("%B %d, %Y") @@ -39,7 +38,7 @@ def get_research_topic(messages: List[AnyMessage]) -> str: return research_topic -def insert_citation_markers(text: str, citations_list: List[Dict]) -> str: +def insert_citation_markers(text, citations_list): """ Inserts citation markers into a text string based on start and end indices. diff --git a/evaluation/ace_bench/main.py b/evaluation/ace_bench/main.py index 930e50f..ece93fb 100644 --- a/evaluation/ace_bench/main.py +++ b/evaluation/ace_bench/main.py @@ -21,8 +21,8 @@ from agentscope.tool import Toolkit async def react_agent_solution( - ace_task: Task, - pre_hook: Callable, + ace_task: Task, + pre_hook: Callable, ) -> SolutionOutput: """Run ReAct agent with the given task in ACEBench. @@ -42,8 +42,8 @@ async def react_agent_solution( agent = ReActAgent( name="Friday", sys_prompt="You are a helpful assistant named Friday. " - "Your target is to solve the given task with your tools." - "Try to solve the task as best as you can.", + "Your target is to solve the given task with your tools." + "Try to solve the task as best as you can.", model=DashScopeChatModel( api_key=os.environ.get("DASHSCOPE_API_KEY"), model_name="qwen-max", diff --git a/games/game_werewolves/structured_model.py b/games/game_werewolves/structured_model.py index bb0a020..a5f89e1 100644 --- a/games/game_werewolves/structured_model.py +++ b/games/game_werewolves/structured_model.py @@ -2,8 +2,8 @@ """The structured output models used in the werewolf game.""" from typing import Literal -from agentscope.agent import AgentBase from pydantic import BaseModel, Field +from agentscope.agent import AgentBase class DiscussionModel(BaseModel): @@ -44,7 +44,9 @@ def get_poison_model(agents: list[AgentBase]) -> type[BaseModel]: poison: bool = Field( description="Do you want to use the poison potion", ) - name: Literal[tuple(_.name for _ in agents)] | None = Field( # type: ignore + name: Literal[ # type: ignore + tuple(_.name for _ in agents) + ] | None = Field( description="The name of the player you want to poison, if you " "don't want to poison anyone, just leave it empty", default=None, @@ -75,7 +77,9 @@ def get_hunter_model(agents: list[AgentBase]) -> type[BaseModel]: shoot: bool = Field( description="Whether you want to use the shooting ability or not", ) - name: Literal[tuple(_.name for _ in agents)] | None = Field( # type: ignore + name: Literal[ # type: ignore + tuple(_.name for _ in agents) + ] | None = Field( description="The name of the player you want to shoot, if you " "don't want to the ability, just leave it empty", default=None, diff --git a/sample_template/main.py b/sample_template/main.py index 90ac3de..99063da 100644 --- a/sample_template/main.py +++ b/sample_template/main.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ [Your Sample Name] - Entry Point @@ -5,6 +6,7 @@ This example demonstrates [brief description]. """ import agentscope + def main(): """Main function to run the example.""" print(agentscope.__version__) diff --git a/tests/agent_deep_research_test.py b/tests/agent_deep_research_test.py index 381460e..f4aa9f8 100644 --- a/tests/agent_deep_research_test.py +++ b/tests/agent_deep_research_test.py @@ -1,9 +1,8 @@ -# tests/agent_deep_research_test.py -import logging +# -*- coding: utf-8 -*- import os import shutil import tempfile -from unittest.mock import Mock, AsyncMock, patch +from unittest.mock import Mock, AsyncMock, patch, MagicMock import pytest from agentscope.formatter import DashScopeChatFormatter @@ -12,7 +11,9 @@ from agentscope.memory import InMemoryMemory from agentscope.message import Msg from agentscope.model import DashScopeChatModel -from deep_research.agent_deep_research.deep_research_agent import DeepResearchAgent +from deep_research.agent_deep_research.deep_research_agent import ( + DeepResearchAgent, +) from deep_research.agent_deep_research.main import main @@ -70,12 +71,15 @@ class TestDeepResearchAgent: def test_agent_initialization( self, - mock_model, - mock_tavily_client, - temp_working_dir, + mock_model, # pylint: disable=redefined-outer-name + mock_tavily_client, # pylint: disable=redefined-outer-name + temp_working_dir, # pylint: disable=redefined-outer-name ): """Test agent initialization with valid parameters""" - with patch("asyncio.create_task"): + mock_loop = MagicMock() + mock_task = AsyncMock() + mock_loop.create_task = MagicMock(return_value=mock_task) + with patch("asyncio.get_running_loop", return_value=mock_loop): agent = DeepResearchAgent( name="Friday", sys_prompt="You are a helpful assistant named Friday.", @@ -87,17 +91,17 @@ class TestDeepResearchAgent: ) assert agent.name == "Friday" - assert agent.sys_prompt.startswith("You are a helpful assistant named Friday.") + assert agent.sys_prompt.startswith( + "You are a helpful assistant named Friday.", + ) assert agent.tmp_file_storage_dir == temp_working_dir assert os.path.exists(temp_working_dir) @pytest.mark.asyncio async def test_main_function_success( self, - mock_env_vars, - mock_tavily_client, - mock_model, - temp_working_dir, + mock_tavily_client, # pylint: disable=redefined-outer-name + temp_working_dir, # pylint: disable=redefined-outer-name ): """Test main function with successful execution""" with patch( @@ -109,17 +113,26 @@ class TestDeepResearchAgent: autospec=True, ) as mock_agent_class: mock_agent = AsyncMock() - mock_agent.return_value = Msg("Friday", "Test response", "assistant") + mock_agent.return_value = Msg( + "Friday", + "Test response", + "assistant", + ) mock_agent_class.return_value = mock_agent with patch("os.makedirs") as mock_makedirs: - with patch.dict(os.environ, {"AGENT_OPERATION_DIR": temp_working_dir}): + with patch.dict( + os.environ, + {"AGENT_OPERATION_DIR": temp_working_dir}, + ): test_query = "Test research question" - msg = Msg("Bob", test_query, "user") await main(test_query) - mock_makedirs.assert_called_once_with(temp_working_dir, exist_ok=True) + mock_makedirs.assert_called_once_with( + temp_working_dir, + exist_ok=True, + ) mock_agent_class.assert_called_once() # ✅ Use assert_called_once() + manual argument check @@ -138,8 +151,7 @@ class TestDeepResearchAgent: @pytest.mark.asyncio async def test_agent_cleanup( self, - mock_env_vars, - mock_tavily_client, + mock_tavily_client, # pylint: disable=redefined-outer-name ): """Test proper cleanup of resources""" with patch( @@ -151,7 +163,10 @@ class TestDeepResearchAgent: mock_tavily_client.close.assert_called_once() - def test_working_directory_creation(self, temp_working_dir): + def test_working_directory_creation( + self, + temp_working_dir, # pylint: disable=redefined-outer-name + ): """Test working directory is created correctly""" test_dir = os.path.join(temp_working_dir, "test_subdir") os.makedirs(test_dir, exist_ok=True) @@ -161,17 +176,28 @@ class TestDeepResearchAgent: class TestErrorHandling: """Test suite for error handling scenarios""" + @pytest.mark.asyncio - async def test_filesystem_errors(self, mock_env_vars, mock_tavily_client): + async def test_filesystem_errors( + self, + mock_tavily_client, # pylint: disable=redefined-outer-name + ): """Test handling of filesystem errors""" with patch( - "deep_research.agent_deep_research.main.StdIOStatefulClient", - return_value=mock_tavily_client, + "deep_research.agent_deep_research.main.StdIOStatefulClient", + return_value=mock_tavily_client, ): - with patch.dict(os.environ, {"AGENT_OPERATION_DIR": "/invalid/path"}): - with patch("os.makedirs", side_effect=PermissionError("Permission denied")): + with patch.dict( + os.environ, + {"AGENT_OPERATION_DIR": "/invalid/path"}, + ): + with patch( + "os.makedirs", + side_effect=PermissionError("Permission denied"), + ): with pytest.raises(PermissionError): await main("Test query") + if __name__ == "__main__": - pytest.main(["-v", __file__]) \ No newline at end of file + pytest.main(["-v", __file__]) diff --git a/tests/browser_agent_test.py b/tests/browser_agent_test.py index 094365c..cfc3fc7 100644 --- a/tests/browser_agent_test.py +++ b/tests/browser_agent_test.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- -import pytest -import asyncio -from typing import Dict, Any, AsyncGenerator +from typing import Dict from unittest.mock import AsyncMock, MagicMock, patch +import pytest from agentscope.message import Msg from agentscope.tool import Toolkit from agentscope.memory import MemoryBase @@ -22,7 +21,10 @@ def mock_dependencies() -> Dict[str, MagicMock]: @pytest.fixture -def agent(mock_dependencies: Dict[str, MagicMock]) -> BrowserAgent: +def agent( + # pylint: disable=redefined-outer-name + mock_dependencies: Dict[str, MagicMock], +) -> BrowserAgent: return BrowserAgent( name="TestBot", model=mock_dependencies["model"], @@ -36,17 +38,28 @@ def agent(mock_dependencies: Dict[str, MagicMock]) -> BrowserAgent: # ----------------------------- # ✅ Hook registration verification (adapted for ReActAgentBase) # ----------------------------- -def test_hooks_registered(agent: BrowserAgent) -> None: - # Verify instance-level hooks - assert hasattr(agent, "_instance_pre_reply_hooks") +def test_hooks_registered( + agent: BrowserAgent, # pylint: disable=redefined-outer-name +) -> None: + """Verify instance-level hooks are registered""" + # Disable pylint warning for protected member access + assert hasattr( + agent, + "_instance_pre_reply_hooks", + ) # pylint: disable=protected-access assert ( "browser_agent_default_url_pre_reply" + # pylint: disable=protected-access in agent._instance_pre_reply_hooks ) - assert hasattr(agent, "_instance_pre_reasoning_hooks") + assert hasattr( + agent, + "_instance_pre_reasoning_hooks", + ) # pylint: disable=protected-access assert ( "browser_agent_observe_pre_reasoning" + # pylint: disable=protected-access in agent._instance_pre_reasoning_hooks ) @@ -55,15 +68,20 @@ def test_hooks_registered(agent: BrowserAgent) -> None: # ✅ Navigation hook test (direct hook invocation) # ----------------------------- @pytest.mark.asyncio -async def test_pre_reply_hook_navigation(agent: BrowserAgent) -> None: +async def test_pre_reply_hook_navigation( + agent: BrowserAgent, # pylint: disable=redefined-outer-name +) -> None: + # pylint: disable=protected-access agent._has_initial_navigated = False # Get instance-level hook function + # pylint: disable=protected-access hook_func = agent._instance_pre_reply_hooks[ "browser_agent_default_url_pre_reply" ] await hook_func(agent) # Directly invoke hook function + # pylint: disable=protected-access assert agent._has_initial_navigated is True assert agent.toolkit.call_tool_function.called @@ -72,13 +90,17 @@ async def test_pre_reply_hook_navigation(agent: BrowserAgent) -> None: # ✅ Snapshot hook test (fix content attribute access issue) # ----------------------------- @pytest.mark.asyncio -async def test_observe_pre_reasoning(agent: BrowserAgent) -> None: +async def test_observe_pre_reasoning( + agent: BrowserAgent, # pylint: disable=redefined-outer-name +) -> None: # Mock tool response (fix: use Msg object with content attribute) mock_response = AsyncMock() mock_response.__aiter__.return_value = [ Msg("system", [{"text": "Snapshot content"}], "system"), ] - agent.toolkit.call_tool_function = AsyncMock(return_value=mock_response) + agent.toolkit.call_tool_function = AsyncMock( + return_value=mock_response, + ) # Replace memory add method with patch.object( @@ -87,6 +109,7 @@ async def test_observe_pre_reasoning(agent: BrowserAgent) -> None: new_callable=AsyncMock, ) as mock_add: # Get instance-level hook function + # pylint: disable=protected-access hook_func = agent._instance_pre_reasoning_hooks[ "browser_agent_observe_pre_reasoning" ] @@ -100,7 +123,9 @@ async def test_observe_pre_reasoning(agent: BrowserAgent) -> None: # ----------------------------- # ✅ Text filtering test (improved regex) # ----------------------------- -def test_filter_execution_text(agent: BrowserAgent) -> None: +def test_filter_execution_text( + agent: BrowserAgent, # pylint: disable=redefined-outer-name +) -> None: text = """ ### New console messages Some console output @@ -112,6 +137,7 @@ def test_filter_execution_text(agent: BrowserAgent) -> None: ``` Regular text content """ + # pylint: disable=protected-access filtered = agent._filter_execution_text(text) assert "console output" not in filtered @@ -124,7 +150,9 @@ def test_filter_execution_text(agent: BrowserAgent) -> None: # ✅ Memory summarization test (already passing) # ----------------------------- @pytest.mark.asyncio -async def test_memory_summarizing(agent: BrowserAgent) -> None: +async def test_memory_summarizing( + agent: BrowserAgent, # pylint: disable=redefined-outer-name +) -> None: agent.memory.get_memory = AsyncMock( return_value=[MagicMock(role="user", content="Original question")] * 25, @@ -136,6 +164,7 @@ async def test_memory_summarizing(agent: BrowserAgent) -> None: content=[MagicMock(text="Summary text")], ) + # pylint: disable=protected-access await agent._memory_summarizing() assert agent.memory.clear.called diff --git a/tests/browser_use_fullstack_runtime_test.py b/tests/browser_use_fullstack_runtime_test.py index 7f3e68c..8c276e7 100644 --- a/tests/browser_use_fullstack_runtime_test.py +++ b/tests/browser_use_fullstack_runtime_test.py @@ -1,20 +1,23 @@ # -*- coding: utf-8 -*- -import pytest import asyncio -from unittest.mock import AsyncMock, MagicMock, patch from types import SimpleNamespace - +from unittest.mock import AsyncMock, MagicMock, patch +import pytest import pytest_asyncio -from browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent import ( - AgentscopeBrowseruseAgent, - RunStatus, -) -from browser_use.browser_use_fullstack_runtime.backend.async_quart_service import ( - app, -) from quart.testing import QuartClient +from browser_use.browser_use_fullstack_runtime.backend import ( + agentscope_browseruse_agent as agent_module, +) +from browser_use.browser_use_fullstack_runtime.backend import ( + async_quart_service as service, +) + +AgentscopeBrowseruseAgent = agent_module.AgentscopeBrowseruseAgent +RunStatus = agent_module.RunStatus +app = service.app + # ----------------------------- # 🧪 Singleton Test Configuration @@ -31,13 +34,17 @@ def event_loop(): async def agent_singleton(): """Session-scoped single instance of AgentscopeBrowseruseAgent""" with patch( - "browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent.SandboxService", + "browser_use.browser_use_fullstack_runtime." + "backend.agentscope_browseruse_agent.SandboxService", ) as MockSandboxService, patch( - "browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent.InMemoryMemoryService", + "browser_use.browser_use_fullstack_runtime." + "backend.agentscope_browseruse_agent.InMemoryMemoryService", ) as MockMemoryService, patch( - "browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent.InMemorySessionHistoryService", + "browser_use.browser_use_fullstack_runtime." + "backend.agentscope_browseruse_agent.InMemorySessionHistoryService", ) as MockHistoryService, patch( - "agentscope_runtime.sandbox.manager.container_clients.docker_client.docker", + "agentscope_runtime.sandbox.manager." + "container_clients.docker_client.docker", ) as mock_docker, patch( "agentscope_runtime.sandbox.manager.sandbox_manager.SandboxManager", ) as MockSandboxManager: @@ -88,16 +95,20 @@ async def test_app(): # ✅ AgentscopeBrowseruseAgent Singleton Tests # ----------------------------- @pytest.mark.asyncio -async def test_agent_singleton_initialization(agent_singleton): +async def agent_singleton_singleton_initialization( + agent_singleton, # pylint: disable=redefined-outer-name +): """Test agent singleton initialization""" - agent = agent_singleton + agent = agent_singleton # pylint: disable=redefined-outer-name assert isinstance(agent, AgentscopeBrowseruseAgent) assert hasattr(agent, "agent") assert hasattr(agent, "runner") @pytest.mark.asyncio -async def test_chat_method(agent_singleton): +async def test_chat_method( + agent_singleton, +): # pylint: disable=redefined-outer-name """Test chat method handles messages""" mock_request = { "messages": [ @@ -108,20 +119,28 @@ async def test_chat_method(agent_singleton): # ✅ Create mock object with object/status properties mock_event = SimpleNamespace( object="message", - status=RunStatus.Completed, + status=agent_module.RunStatus.Completed, content=[{"type": "text", "text": "Test response"}], ) - with patch.object(agent_singleton.runner, "stream_query") as mock_stream: + with patch.object( + agent_singleton.runner, # pylint: disable=redefined-outer-name + "stream_query", + ) as mock_stream: # ✅ Return object with properties - async def mock_stream_query(*args, **kwargs): + async def mock_stream_query(*_args, **_kwargs): yield mock_event mock_stream.side_effect = mock_stream_query responses = [] - async for response in agent_singleton.chat(mock_request["messages"]): + async for response in agent_singleton.chat( + # pylint: disable=redefined-outer-name + mock_request["messages"], + ): responses.append(response) assert len(responses) == 1 - assert responses[0][0]["text"] == "Test response" # ✅ Fix property access \ No newline at end of file + assert ( + responses[0][0]["text"] == "Test response" + ) # ✅ Fix property access diff --git a/tests/conversational_agents_chatbot_fullstack_runtime_webserver_test.py b/tests/conversational_agents_chatbot_fullstack_runtime_webserver_test.py index 9b39921..1e6443d 100644 --- a/tests/conversational_agents_chatbot_fullstack_runtime_webserver_test.py +++ b/tests/conversational_agents_chatbot_fullstack_runtime_webserver_test.py @@ -1,264 +1,61 @@ -from datetime import datetime, timezone +# -*- coding: utf-8 -*- +import os +import time +import tempfile import pytest -from unittest.mock import MagicMock, patch -from flask import Flask, request, jsonify -from flask_sqlalchemy import SQLAlchemy -from werkzeug.security import generate_password_hash, check_password_hash - -# Initialize db instance -db = SQLAlchemy() +import conversational_agents.chatbot_fullstack_runtime.backend.web_server as ws -# Define model classes (defined once) -class User(db.Model): - __tablename__ = "user" - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(80), unique=True, nullable=False) - password_hash = db.Column(db.String(120), nullable=False) - name = db.Column(db.String(100), nullable=False) - created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) - - def set_password(self, password): - self.password_hash = generate_password_hash(password) - - def check_password(self, password): - return check_password_hash(self.password_hash, password) +app = ws.app +_db = ws.db +User = ws.User -class Conversation(db.Model): - __tablename__ = "conversation" - id = db.Column(db.Integer, primary_key=True) - title = db.Column(db.String(200), nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) - created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) - updated_at = db.Column( - db.DateTime, - default=lambda: datetime.now(timezone.utc), - onupdate=lambda: datetime.now(timezone.utc), +def generate_unique_username(): + return f"testuser_{int(time.time())}" + + +@pytest.fixture +def client_and_username(): + """Create an Isolated Test Client and Username""" + db_fd, db_path = tempfile.mkstemp(suffix=".db") + app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" + app.config["TESTING"] = True + client = app.test_client() + + with app.app_context(): + _db.drop_all() + _db.create_all() + + # Generate Unique Username + username = generate_unique_username() + password = "testpass" + user = User(username=username, name="Test User") + user.set_password(password) + _db.session.add(user) + _db.session.commit() + + yield client, username, password + + os.close(db_fd) + os.unlink(db_path) + + +def test_user_login_success( + # pylint: disable=redefined-outer-name + client_and_username, +): + """Test Successful User Login""" + client, username, password = client_and_username + + response = client.post( + "/api/login", + json={ + "username": username, + "password": password, + }, ) - messages = db.relationship("Message", backref="conversation", lazy=True) - - -class Message(db.Model): - __tablename__ = "message" - id = db.Column(db.Integer, primary_key=True) - text = db.Column(db.Text, nullable=False) - sender = db.Column(db.String(20), nullable=False) - conversation_id = db.Column(db.Integer, db.ForeignKey("conversation.id"), nullable=False) - created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) - - -# Thoroughly isolated test Flask application -@pytest.fixture -def app(): - """Create a fresh Flask application instance""" - app = Flask(__name__) - app.config.update({ - "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:", - "SQLALCHEMY_TRACK_MODIFICATIONS": False, - "TESTING": True, - }) - - # Initialize db - db.init_app(app) - - # Define routes - @app.route("/api/login", methods=["POST"]) - def login(): - data = request.get_json() - username = data.get("username") - password = data.get("password") - - if not username or not password: - return jsonify({"error": "Username and password cannot be empty"}), 400 - - user = User.query.filter_by(username=username).first() - if user and user.check_password(password): - return jsonify({ - "id": user.id, - "username": user.username, - "name": user.name, - "created_at": user.created_at.isoformat(), - }), 200 - return jsonify({"error": "Invalid username or password"}), 401 - - @app.route("/api/users//conversations", methods=["POST"]) - def create_conversation(user_id): - data = request.get_json() - title = data.get("title", f"Conversation {datetime.now().strftime('%Y-%m-%d %H:%M')}") - conversation = Conversation(title=title, user_id=user_id) - db.session.add(conversation) - db.session.commit() - return jsonify({ - "id": conversation.id, - "title": conversation.title, - "user_id": conversation.user_id, - "created_at": conversation.created_at.isoformat(), - "updated_at": conversation.updated_at.isoformat(), - }), 201 - - @app.route("/api/conversations/", methods=["GET"]) - def get_conversation(conversation_id): - conversation = Conversation.query.get(conversation_id) - if not conversation: - return jsonify({"error": "Conversation not found"}), 404 - - messages = Message.query.filter_by(conversation_id=conversation_id).order_by(Message.created_at.asc()).all() - messages_data = [{ - "id": msg.id, - "text": msg.text, - "sender": msg.sender, - "created_at": msg.created_at.isoformat(), - } for msg in messages] - - return jsonify({ - "id": conversation.id, - "title": conversation.title, - "user_id": conversation.user_id, - "messages": messages_data, - "created_at": conversation.created_at.isoformat(), - "updated_at": conversation.updated_at.isoformat(), - }), 200 - - @app.route("/api/conversations//messages", methods=["POST"]) - def send_message(conversation_id): - conversation = Conversation.query.get(conversation_id) - if not conversation: - return jsonify({"error": "Conversation not found"}), 404 - - data = request.get_json() - text = data.get("text") - sender = data.get("sender", "user") - - if not text: - return jsonify({"error": "Message content cannot be empty"}), 400 - - # Create user message - user_message = Message( - text=text, - sender=sender, - conversation_id=conversation_id - ) - db.session.add(user_message) - - # Update conversation title (if this is the first user message) - if sender == "user" and len(conversation.messages) <= 1: - conversation.title = text[:20] + ("..." if len(text) > 20 else "") - - db.session.commit() - - # Simulate AI response - ai_message = Message( - text="Test response part 1 Test response part 2", - sender="ai", - conversation_id=conversation_id - ) - db.session.add(ai_message) - db.session.commit() - - return jsonify({ - "id": user_message.id, - "text": user_message.text, - "sender": user_message.sender, - "created_at": user_message.created_at.isoformat(), - }), 201 - - # Initialize database - with app.app_context(): - db.create_all() - # Create example users - if not User.query.first(): - user1 = User(username="user1", name="Bruce") - user1.set_password("password123") - db.session.add(user1) - db.session.commit() - - yield app - - with app.app_context(): - db.drop_all() - db.session.remove() - - -@pytest.fixture -def client(app): - """Flask test client""" - return app.test_client() - - -# Mock call_runner function -def mock_call_runner(query, session_id, user_id): - """Mock function for call_runner""" - yield "Test response part 1" - yield " Test response part 2" - - -def test_login_success(app, client): - """Test successful user login""" - with app.app_context(): - user = User(username="test", name="Test User") - user.set_password("testpass") - db.session.add(user) - db.session.commit() - - response = client.post("/api/login", json={ - "username": "test", - "password": "testpass", - }) - assert response.status_code == 200 data = response.get_json() - assert data["username"] == "test" - - -def test_login_invalid_credentials(app, client): - """Test login with invalid credentials""" - response = client.post("/api/login", json={ - "username": "test", - "password": "wrongpass" - }) - assert response.status_code == 401 - - -def test_conversation_crud_operations(app, client): - """Test conversation creation and retrieval""" - with app.app_context(): - user = User(username="test", name="Test User") - user.set_password("testpass") - db.session.add(user) - db.session.commit() - - create_response = client.post("/api/users/1/conversations", json={ - "title": "Test Conversation", - }) - assert create_response.status_code == 201 - conversation_id = create_response.get_json()["id"] - - get_response = client.get(f"/api/conversations/{conversation_id}") - assert get_response.status_code == 200 - assert "Test Conversation" in get_response.get_json()["title"] - - -@patch("tests.conversational_agents_chatbot_fullstack_runtime_webserver_test.db", new=db) -def test_send_message(app, client): - """Test message sending and AI response""" - with app.app_context(): - user = User(username="test", name="Test User") - user.set_password("testpass") - conversation = Conversation(title="Test", user_id=1) - db.session.add_all([user, conversation]) - db.session.commit() - - response = client.post("/api/conversations/1/messages", json={ - "text": "Hello", - "sender": "user" - }) - assert response.status_code == 201 - data = response.get_json() assert "id" in data - assert "Hello" in data["text"] - - # ✅ Move the query into the application context - with app.app_context(): - messages = Message.query.filter_by(conversation_id=1).all() - assert len(messages) == 2 # User + AI response \ No newline at end of file + assert data["username"] == username diff --git a/tests/conversational_agents_chatbot_test.py b/tests/conversational_agents_chatbot_test.py index 4706b77..0a7b76a 100644 --- a/tests/conversational_agents_chatbot_test.py +++ b/tests/conversational_agents_chatbot_test.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -import pytest from unittest.mock import AsyncMock +import pytest from agentscope.message import Msg from agentscope.agent import ReActAgent from agentscope.tool import Toolkit @@ -12,13 +12,14 @@ class TestReActAgent: @pytest.fixture def test_agent(self): - """Fixture to create a test ReAct agent with fully mocked dependencies""" + """Fixture to create a test ReAct + agent with fully mocked dependencies""" - async def model_response(*args, **kwargs): + async def model_response(): yield Msg( name="Friday", content="Mocked model response", - role="assistant" + role="assistant", ) mock_model = AsyncMock() @@ -36,10 +37,12 @@ class TestReActAgent: model=mock_model, formatter=mock_formatter, toolkit=Toolkit(), - memory=mock_memory + memory=mock_memory, ) + # pylint: disable=protected-access agent._reasoning_hint_msgs = AsyncMock() + # pylint: disable=protected-access agent._reasoning_hint_msgs.get_memory = AsyncMock(return_value=[]) return agent @@ -47,16 +50,16 @@ class TestReActAgent: async def test_exit_command(self, test_agent, monkeypatch): """Test exit command handling""" - async def exit_model_response(*args, **kwargs): + async def exit_model_response(*_args, **_kwargs): yield Msg( name="Friday", content="exit", - role="assistant" + role="assistant", ) test_agent.model.side_effect = exit_model_response - monkeypatch.setattr('builtins.input', lambda _: "exit") + monkeypatch.setattr("builtins.input", lambda _: "exit") msg = Msg(name="User", content="exit", role="user") response = await test_agent(msg) @@ -66,11 +69,13 @@ class TestReActAgent: async def test_conversation_flow(self, monkeypatch): """Test full conversation flow""" - async def model_response(*args, **kwargs): + async def model_response(*_args, **_kwargs): yield Msg( name="Friday", - content="Thought: I need to use a tool\nAction: execute_shell_command\nAction Input: echo 'Hello World'", - role="assistant" + content="Thought: I need to use a tool\n" + "Action: execute_shell_command\n" + "Action Input: echo 'Hello World'", + role="assistant", ) mock_model = AsyncMock() @@ -88,11 +93,11 @@ class TestReActAgent: model=mock_model, formatter=mock_formatter, toolkit=Toolkit(), - memory=mock_memory + memory=mock_memory, ) - monkeypatch.setattr('builtins.input', lambda _: "Test command") + monkeypatch.setattr("builtins.input", lambda _: "Test command") msg = Msg(name="User", content="Test command", role="user") response = await agent(msg) - assert "Thought:" in response.content \ No newline at end of file + assert "Thought:" in response.content diff --git a/tests/evaluation_test.py b/tests/evaluation_test.py index 7ce14d6..2b728fc 100644 --- a/tests/evaluation_test.py +++ b/tests/evaluation_test.py @@ -1,5 +1,4 @@ -# tests/evaluation_test.py -import asyncio +# -*- coding: utf-8 -*- import os from unittest.mock import Mock, AsyncMock, patch from typing import List, Dict, Any, Tuple, Callable @@ -29,19 +28,21 @@ class TestReActAgentSolution: def mock_pre_hook(self) -> Mock: """Create a mock pre-hook function that returns None""" - def pre_hook_return(*args, **kwargs): + def pre_hook_return(): """Mock function that returns None (no modifications)""" return None mock = Mock() mock.__name__ = "save_logging" - mock.side_effect = pre_hook_return # ✅ Return None to avoid parameter pollution + mock.side_effect = ( + pre_hook_return # ✅ Return None to avoid parameter pollution + ) return mock def _create_mock_tools(self) -> List[Tuple[Callable, Dict[str, Any]]]: """Create mock tool functions with schemas""" - def mock_tool(*args, **kwargs): + def mock_tool(): return "tool_response" tool_schema = { @@ -110,8 +111,15 @@ class TestMainFunction: mock_evaluator_class.return_value = mock_evaluator # ✅ Simulate _download_data and _load_data - with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._download_data"): - with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._load_data", return_value=[]): + with patch( + "agentscope.evaluate._ace_benchmark." + "_ace_benchmark.ACEBenchmark._download_data", + ): + with patch( + "agentscope.evaluate._ace_benchmark." + "_ace_benchmark.ACEBenchmark._load_data", + return_value=[], + ): # Run main function await ace_main.main() @@ -137,12 +145,19 @@ class TestMainFunction: mock_evaluator_class.return_value = mock_evaluator # ✅ Simulate _download_data and _load_data - with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._download_data"): - with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._load_data", return_value=[]): + with patch( + "agentscope.evaluate._ace_benchmark._ace_benchmark." + "ACEBenchmark._download_data", + ): + with patch( + "agentscope.evaluate._ace_benchmark." + "_ace_benchmark.ACEBenchmark._load_data", + return_value=[], + ): # Run main function await ace_main.main() # Verify evaluation execution mock_evaluator.run.assert_called_once_with( ace_main.react_agent_solution, - ) \ No newline at end of file + ) diff --git a/tests/game_test.py b/tests/game_test.py index 623c18f..8707a6d 100644 --- a/tests/game_test.py +++ b/tests/game_test.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- -import os -import asyncio -import pytest from unittest.mock import AsyncMock, patch, MagicMock +import pytest from agentscope.agent import ReActAgent from agentscope.model import ChatModelBase from agentscope.formatter import FormatterBase @@ -47,9 +45,12 @@ async def test_witch_resurrect() -> None: async def mock_model(**kwargs): return {"resurrect": kwargs.get("resurrect", False)} - with patch("games.game_werewolves.game.WitchResurrectModel", side_effect=mock_model): + with patch( + "games.game_werewolves.game.WitchResurrectModel", + side_effect=mock_model, + ): result = await game.WitchResurrectModel(**{"resurrect": True}) - assert result["resurrect"] == True + assert result["resurrect"] is True # ----------------------------- @@ -84,8 +85,9 @@ def test_vote_model_generation() -> None: name=f"Player{i}", sys_prompt=f"Vote system prompt {i}", model=mock_model, - formatter=mock_formatter - ) for i in range(3) + formatter=mock_formatter, + ) + for i in range(3) ] VoteModel = structured_model.get_vote_model(agents) @@ -105,10 +107,10 @@ def test_witch_poison_model_fields() -> None: name="Player1", sys_prompt="Poison system prompt", model=mock_model, - formatter=mock_formatter - ) + formatter=mock_formatter, + ), ] PoisonModel = structured_model.get_poison_model(agents) assert "poison" in PoisonModel.model_fields - assert "name" in PoisonModel.model_fields \ No newline at end of file + assert "name" in PoisonModel.model_fields