diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..05a4559ed --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,63 @@ +# AstrBot Development Instructions + +AstrBot is a multi-platform LLM chatbot and development framework written in Python with a Vue.js dashboard. It supports multiple messaging platforms (QQ, Telegram, Discord, etc.) and various LLM providers (OpenAI, Anthropic, Google Gemini, etc.). + +Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here. + +## Working Effectively + +### Bootstrap and Install Dependencies +- **Python 3.10+ required** - Check `.python-version` file +- Install UV package manager: `pip install uv` +- Install project dependencies: `uv sync` -- takes 6-7 minutes. NEVER CANCEL. Set timeout to 10+ minutes. +- Create required directories: `mkdir -p data/plugins data/config data/temp` + +### Running the Application +- Run main application: `uv run main.py` -- starts in ~3 seconds +- Application creates WebUI on http://localhost:6185 (default credentials: `astrbot`/`astrbot`) +- Application loads plugins automatically from `packages/` and `data/plugins/` directories + +### Dashboard Build (Vue.js/Node.js) +- **Prerequisites**: Node.js 20+ and npm 10+ required +- Navigate to dashboard: `cd dashboard` +- Install dashboard dependencies: `npm install` -- takes 2-3 minutes. NEVER CANCEL. Set timeout to 5+ minutes. +- Build dashboard: `npm run build` -- takes 25-30 seconds. NEVER CANCEL. +- Dashboard creates optimized production build in `dashboard/dist/` + +### Testing +- Do not generate test files for now. + +### Code Quality and Linting +- Install ruff linter: `uv add --dev ruff` +- Check code style: `uv run ruff check .` -- takes <1 second +- Check formatting: `uv run ruff format --check .` -- takes <1 second +- Fix formatting: `uv run ruff format .` +- **ALWAYS** run `uv run ruff check .` and `uv run ruff format .` before committing changes + +### Plugin Development +- Plugins load from `packages/` (built-in) and `data/plugins/` (user-installed) +- Plugin system supports function tools and message handlers +- Key plugins: python_interpreter, web_searcher, astrbot, reminder, session_controller + +### Common Issues and Workarounds +- **Dashboard download fails**: Known issue with "division by zero" error - application still works +- **Import errors in tests**: Ensure `uv run` is used to run tests in proper environment +=- **Build timeouts**: Always set appropriate timeouts (10+ minutes for uv sync, 5+ minutes for npm install) + +## CI/CD Integration +- GitHub Actions workflows in `.github/workflows/` +- Docker builds supported via `Dockerfile` +- Pre-commit hooks enforce ruff formatting and linting + +## Docker Support +- Primary deployment method: `docker run soulter/astrbot:latest` +- Compose file available: `compose.yml` +- Exposes ports: 6185 (WebUI), 6195 (WeChat), 6199 (QQ), etc. +- Volume mount required: `./data:/AstrBot/data` + +## Multi-language Support +- Documentation in Chinese (README.md), English (README_en.md), Japanese (README_ja.md) +- UI supports internationalization +- Default language is Chinese + +Remember: This is a production chatbot framework with real users. Always test thoroughly and ensure changes don't break existing functionality. diff --git a/.github/workflows/auto_release.yml b/.github/workflows/auto_release.yml index 46d914a9a..fca9dc52e 100644 --- a/.github/workflows/auto_release.yml +++ b/.github/workflows/auto_release.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Dashboard Build run: | @@ -70,7 +70,7 @@ jobs: needs: build-and-publish-to-github-release steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8503bb715..9d79d4345 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -56,7 +56,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/coverage_test.yml b/.github/workflows/coverage_test.yml index e9c94d679..06311aa84 100644 --- a/.github/workflows/coverage_test.yml +++ b/.github/workflows/coverage_test.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml index 4c6304e4a..9ea359579 100644 --- a/.github/workflows/dashboard_ci.yml +++ b/.github/workflows/dashboard_ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: npm install, build run: | diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index c0610a3c1..95b8bc9db 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Pull The Codes - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 # Must be 0 so we can fetch tags diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 29261b2b9..001bc94e9 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -558,8 +558,10 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "gpt-4o-mini", + "temperature": 0.4 }, "modalities": ["text", "image"], + "hint": "也兼容所有与OpenAI API兼容的服务。", }, "Azure OpenAI": { "id": "azure", @@ -573,6 +575,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "gpt-4o-mini", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -587,10 +590,12 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "grok-2-latest", + "temperature": 0.4 }, "modalities": ["text", "image"], }, "Anthropic": { + "hint": "注意Claude系列模型的温度调节范围为0到1.0,超出可能导致报错", "id": "claude", "provider": "anthropic", "type": "anthropic_chat_completion", @@ -602,10 +607,12 @@ CONFIG_METADATA_2 = { "model_config": { "model": "claude-3-5-sonnet-latest", "max_tokens": 4096, + "temperature": 0.2 }, "modalities": ["text", "image"], }, "Ollama": { + "hint":"启用前请确保已正确安装并运行 Ollama 服务端,Ollama默认不带鉴权,无需修改key", "id": "ollama_default", "provider": "ollama", "type": "openai_chat_completion", @@ -615,6 +622,7 @@ CONFIG_METADATA_2 = { "api_base": "http://localhost:11434/v1", "model_config": { "model": "llama3.1-8b", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -642,6 +650,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "gemini-1.5-flash", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -656,6 +665,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "gemini-2.0-flash-exp", + "temperature": 0.4 }, "gm_resp_image_modal": False, "gm_native_search": False, @@ -683,6 +693,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "deepseek-chat", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -697,6 +708,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "gpt-4.1-mini", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -711,6 +723,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.siliconflow.cn/v1", "model_config": { "model": "deepseek-ai/DeepSeek-V3", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -725,6 +738,20 @@ CONFIG_METADATA_2 = { "timeout": 120, "model_config": { "model": "deepseek/deepseek-r1", + "temperature": 0.4 + }, + }, + "优云智算": { + "id": "compshare", + "provider": "compshare", + "type": "openai_chat_completion", + "provider_type": "chat_completion", + "enable": True, + "key": [], + "api_base": "https://api.modelverse.cn/v1", + "timeout": 120, + "model_config": { + "model": "moonshotai/Kimi-K2-Instruct", }, "modalities": ["text", "image"], }, @@ -739,6 +766,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.moonshot.cn/v1", "model_config": { "model": "moonshot-v1-8k", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -769,6 +797,7 @@ CONFIG_METADATA_2 = { "dify_query_input_key": "astrbot_text_query", "variables": {}, "timeout": 60, + "hint": "请确保你在 AstrBot 里设置的 APP 类型和 Dify 里面创建的应用的类型一致!" }, "阿里云百炼应用": { "id": "dashscope", @@ -798,6 +827,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api-inference.modelscope.cn/v1", "model_config": { "model": "Qwen/Qwen3-32B", + "temperature": 0.4 }, "modalities": ["text", "image"], }, @@ -822,7 +852,7 @@ CONFIG_METADATA_2 = { "model": "whisper-1", }, "Whisper(本地加载)": { - "whisper_hint": "(不用修改我)", + "hint": "启用前请 pip 安装 openai-whisper 库(N卡用户大约下载 2GB,主要是 torch 和 cuda,CPU 用户大约下载 1 GB),并且安装 ffmpeg。否则将无法正常转文字。", "provider": "openai", "type": "openai_whisper_selfhost", "provider_type": "speech_to_text", @@ -831,7 +861,7 @@ CONFIG_METADATA_2 = { "model": "tiny", }, "SenseVoice(本地加载)": { - "sensevoice_hint": "(不用修改我)", + "hint": "启用前请 pip 安装 funasr、funasr_onnx、torchaudio、torch、modelscope、jieba 库(默认使用CPU,大约下载 1 GB),并且安装 ffmpeg。否则将无法正常转文字。", "type": "sensevoice_stt_selfhost", "provider": "sensevoice", "provider_type": "speech_to_text", @@ -853,7 +883,7 @@ CONFIG_METADATA_2 = { "timeout": "20", }, "Edge TTS": { - "edgetts_hint": "提示:使用这个服务前需要安装有 ffmpeg,并且可以直接在终端调用 ffmpeg 指令。", + "hint": "提示:使用这个服务前需要安装有 ffmpeg,并且可以直接在终端调用 ffmpeg 指令。", "id": "edge_tts", "provider": "microsoft", "type": "edge_tts", diff --git a/astrbot/core/file_token_service.py b/astrbot/core/file_token_service.py index 2ed46d433..ce5e2349a 100644 --- a/astrbot/core/file_token_service.py +++ b/astrbot/core/file_token_service.py @@ -2,6 +2,8 @@ import asyncio import os import uuid import time +from urllib.parse import urlparse, unquote +import platform class FileTokenService: @@ -15,7 +17,9 @@ class FileTokenService: async def _cleanup_expired_tokens(self): """清理过期的令牌""" now = time.time() - expired_tokens = [token for token, (_, expire) in self.staged_files.items() if expire < now] + expired_tokens = [ + token for token, (_, expire) in self.staged_files.items() if expire < now + ] for token in expired_tokens: self.staged_files.pop(token, None) @@ -32,15 +36,35 @@ class FileTokenService: Raises: FileNotFoundError: 当路径不存在时抛出 """ + + # 处理 file:/// + try: + parsed_uri = urlparse(file_path) + if parsed_uri.scheme == "file": + local_path = unquote(parsed_uri.path) + if platform.system() == "Windows" and local_path.startswith("/"): + local_path = local_path[1:] + else: + # 如果没有 file:/// 前缀,则认为是普通路径 + local_path = file_path + except Exception: + # 解析失败时,按原路径处理 + local_path = file_path + async with self.lock: await self._cleanup_expired_tokens() - if not os.path.exists(file_path): - raise FileNotFoundError(f"文件不存在: {file_path}") + if not os.path.exists(local_path): + raise FileNotFoundError( + f"文件不存在: {local_path} (原始输入: {file_path})" + ) file_token = str(uuid.uuid4()) - expire_time = time.time() + (timeout if timeout is not None else self.default_timeout) - self.staged_files[file_token] = (file_path, expire_time) + expire_time = time.time() + ( + timeout if timeout is not None else self.default_timeout + ) + # 存储转换后的真实路径 + self.staged_files[file_token] = (local_path, expire_time) return file_token async def handle_file(self, file_token: str) -> str: diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 56526c121..b04e619a8 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -470,6 +470,10 @@ class ProviderGoogleGenAI(Provider): raise continue + # Accumulate the complete response text for the final response + accumulated_text = "" + final_response = None + async for chunk in result: llm_response = LLMResponse("assistant", is_chunk=True) @@ -481,23 +485,37 @@ class ProviderGoogleGenAI(Provider): chunk, llm_response ) yield llm_response - break + return if chunk.text: + accumulated_text += chunk.text llm_response.result_chain = MessageChain(chain=[Comp.Plain(chunk.text)]) yield llm_response if chunk.candidates[0].finish_reason: - llm_response = LLMResponse("assistant", is_chunk=False) - if not chunk.candidates[0].content.parts: - llm_response.result_chain = MessageChain(chain=[Comp.Plain(" ")]) - else: - llm_response.result_chain = self._process_content_parts( - chunk, llm_response + # Process the final chunk for potential tool calls or other content + if chunk.candidates[0].content.parts: + final_response = LLMResponse("assistant", is_chunk=False) + final_response.result_chain = self._process_content_parts( + chunk, final_response ) - yield llm_response break + # Yield final complete response with accumulated text + if not final_response: + final_response = LLMResponse("assistant", is_chunk=False) + + # Set the complete accumulated text in the final response + if accumulated_text: + final_response.result_chain = MessageChain( + chain=[Comp.Plain(accumulated_text)] + ) + elif not final_response.result_chain: + # If no text was accumulated and no final response was set, provide empty space + final_response.result_chain = MessageChain(chain=[Comp.Plain(" ")]) + + yield final_response + async def text_chat( self, prompt: str, diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 14c2da2de..b286eb701 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -99,6 +99,11 @@ class ProviderOpenAIOfficial(Provider): for key in to_del: del payloads[key] + # 针对 qwen3 模型的特殊处理:非流式调用必须设置 enable_thinking=false + model = payloads.get("model", "") + if "qwen3" in model.lower(): + extra_body["enable_thinking"] = False + completion = await self.client.chat.completions.create( **payloads, stream=False, extra_body=extra_body ) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index b64b4aa85..ab98b254e 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -809,11 +809,11 @@ class PluginManager: if star_metadata.star_cls is None: return - if hasattr(star_metadata.star_cls, "__del__"): + if '__del__' in star_metadata.star_cls_type.__dict__: asyncio.get_event_loop().run_in_executor( None, star_metadata.star_cls.__del__ ) - elif hasattr(star_metadata.star_cls, "terminate"): + elif 'terminate' in star_metadata.star_cls_type.__dict__: await star_metadata.star_cls.terminate() async def turn_on_plugin(self, plugin_name: str): diff --git a/changelogs/v3.5.24.md b/changelogs/v3.5.24.md new file mode 100644 index 000000000..7e0eff884 --- /dev/null +++ b/changelogs/v3.5.24.md @@ -0,0 +1,10 @@ +# What's Changed + +> 新版本预告: v4.0.0 即将发布。 + +1. 新增: 添加对 ModelScope、Compshare(优云智算)的模版支持。 +2. 优化: 增加插件数据缓存,优化插件市场数据获取时的稳定性。 + +其他更新: + +1. 现已支持在 1Panel 平台通过应用商城快捷部署 AstrBot。详见:[在 1Panel 部署 AstrBot](https://docs.astrbot.app/deploy/astrbot/1panel.html) diff --git a/dashboard/src/components/shared/AstrBotConfig.vue b/dashboard/src/components/shared/AstrBotConfig.vue index 57acaa763..e636a61bb 100644 --- a/dashboard/src/components/shared/AstrBotConfig.vue +++ b/dashboard/src/components/shared/AstrBotConfig.vue @@ -1,10 +1,10 @@