├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── dependabot.yml ├── prompts │ └── chat.prompt.md └── workflows │ └── docker-image.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── PRIVACY.md ├── README.md ├── TODO.md ├── __main__.py ├── bot.py ├── changelogs ├── changelog.md └── changes.md ├── depends-data ├── char_frequency.json └── dict.txt ├── docker-compose.yml ├── docs ├── MCP_SIMPLE_GUIDE.md ├── MCP_SSE_INTEGRATION.md ├── MCP_SSE_QUICKSTART.md ├── MCP_SSE_USAGE.md ├── MCP_TOOLS_INTEGRATION.md ├── affinity_flow_guide.md ├── architecture │ ├── PERMISSION_SYSTEM.md │ └── memory_system_design_v3.md ├── assets │ ├── 1750326700269.png │ ├── 1750332508760.png │ ├── image-1.png │ └── image.png ├── deployment_guide.md ├── development │ └── CONTRIBUTE.md ├── guides │ ├── model_configuration_guide.md │ └── vector_db_usage_guide.md ├── integrations │ └── Bing.md └── plugins │ ├── PLUS_COMMAND_GUIDE.md │ ├── action-components.md │ ├── api │ ├── adapter-command-api.md │ ├── chat-api.md │ ├── component-manage-api.md │ ├── config-api.md │ ├── database-api.md │ ├── emoji-api.md │ ├── generator-api.md │ ├── llm-api.md │ ├── logging-api.md │ ├── message-api.md │ ├── person-api.md │ ├── plugin-manage-api.md │ ├── send-api.md │ └── tool-api.md │ ├── configuration-guide.md │ ├── dependency-management.md │ ├── event-system-guide.md │ ├── index.md │ ├── manifest-guide.md │ ├── quick-start.md │ └── tool_guide.md ├── plugins ├── bilibli │ ├── __init__.py │ ├── _manifest.json │ ├── bilibli_base.py │ └── plugin.py ├── hello_world_plugin │ ├── __init__.py │ ├── _manifest.json │ └── plugin.py └── set_emoji_like │ ├── __init__.py │ └── _manifest.json ├── pyproject.toml ├── pyrightconfig.json ├── requirements.lock ├── requirements.txt ├── scripts ├── convert_sqlalchemy_models.py ├── expression_stats.py ├── lpmm_learning_tool.py ├── rebuild_metadata_index.py ├── run_lpmm.sh ├── run_multi_stage_smoke.py ├── text_length_analysis.py └── update_prompt_imports.py ├── src ├── __init__.py ├── api │ ├── __init__.py │ └── message_router.py ├── chat │ ├── __init__.py │ ├── antipromptinjector │ │ ├── __init__.py │ │ ├── anti_injector.py │ │ ├── core │ │ │ ├── __init__.py │ │ │ ├── detector.py │ │ │ └── shield.py │ │ ├── counter_attack.py │ │ ├── decision │ │ │ ├── __init__.py │ │ │ ├── counter_attack.py │ │ │ └── decision_maker.py │ │ ├── decision_maker.py │ │ ├── detector.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ ├── statistics.py │ │ │ └── user_ban.py │ │ ├── processors │ │ │ ├── __init__.py │ │ │ └── message_processor.py │ │ └── types.py │ ├── chatter_manager.py │ ├── emoji_system │ │ ├── emoji_history.py │ │ └── emoji_manager.py │ ├── energy_system │ │ ├── __init__.py │ │ └── energy_manager.py │ ├── express │ │ ├── expression_learner.py │ │ └── expression_selector.py │ ├── frequency_analyzer │ │ ├── analyzer.py │ │ ├── tracker.py │ │ └── trigger.py │ ├── interest_system │ │ ├── __init__.py │ │ ├── bot_interest_manager.py │ │ └── interest_manager.py │ ├── knowledge │ │ ├── LICENSE │ │ ├── __init__.py │ │ ├── embedding_store.py │ │ ├── global_logger.py │ │ ├── ie_process.py │ │ ├── kg_manager.py │ │ ├── knowledge_lib.py │ │ ├── open_ie.py │ │ ├── prompt_template.py │ │ ├── qa_manager.py │ │ └── utils │ │ │ ├── __init__.py │ │ │ ├── dyn_topk.py │ │ │ ├── hash.py │ │ │ └── json_fix.py │ ├── memory_system │ │ ├── __init__.py │ │ ├── enhanced_memory_activator.py │ │ ├── hippocampus_sampler.py │ │ ├── memory_activator_new.py │ │ ├── memory_builder.py │ │ ├── memory_chunk.py │ │ ├── memory_forgetting_engine.py │ │ ├── memory_formatter.py │ │ ├── memory_fusion.py │ │ ├── memory_manager.py │ │ ├── memory_metadata_index.py │ │ ├── memory_query_planner.py │ │ ├── memory_system.py │ │ └── vector_memory_storage_v2.py │ ├── message_manager │ │ ├── __init__.py │ │ ├── adaptive_stream_manager.py │ │ ├── batch_database_writer.py │ │ ├── context_manager.py │ │ ├── distribution_manager.py │ │ ├── message_manager.py │ │ ├── sleep_manager │ │ │ ├── notification_sender.py │ │ │ ├── sleep_manager.py │ │ │ ├── sleep_state.py │ │ │ ├── time_checker.py │ │ │ ├── wakeup_context.py │ │ │ └── wakeup_manager.py │ │ └── stream_cache_manager.py │ ├── message_receive │ │ ├── __init__.py │ │ ├── bot.py │ │ ├── chat_stream.py │ │ ├── message.py │ │ ├── optimized_chat_stream.py │ │ ├── storage.py │ │ └── uni_message_sender.py │ ├── planner_actions │ │ ├── action_manager.py │ │ └── action_modifier.py │ ├── replyer │ │ ├── default_generator.py │ │ └── replyer_manager.py │ └── utils │ │ ├── chat_message_builder.py │ │ ├── memory_mappings.py │ │ ├── prompt.py │ │ ├── statistic.py │ │ ├── timer_calculator.py │ │ ├── typo_generator.py │ │ ├── utils.py │ │ ├── utils_image.py │ │ ├── utils_video.py │ │ ├── utils_video_legacy.py │ │ └── utils_voice.py ├── common │ ├── __init__.py │ ├── cache_manager.py │ ├── config_helpers.py │ ├── data_models │ │ ├── __init__.py │ │ ├── bot_interest_data_model.py │ │ ├── database_data_model.py │ │ ├── info_data_model.py │ │ ├── llm_data_model.py │ │ └── message_manager_data_model.py │ ├── database │ │ ├── __init__.py │ │ ├── connection_pool_manager.py │ │ ├── database.py │ │ ├── db_batch_scheduler.py │ │ ├── db_migration.py │ │ ├── sqlalchemy_database_api.py │ │ ├── sqlalchemy_init.py │ │ ├── sqlalchemy_models.py │ │ └── sqlalchemy_models.py.bak │ ├── logger.py │ ├── message │ │ ├── __init__.py │ │ └── api.py │ ├── message_repository.py │ ├── remote.py │ ├── server.py │ ├── tcp_connector.py │ └── vector_db │ │ ├── __init__.py │ │ ├── base.py │ │ └── chromadb_impl.py ├── config │ ├── api_ada_configs.py │ ├── config.py │ ├── config_base.py │ └── official_configs.py ├── individuality │ ├── individuality.py │ └── not_using │ │ ├── offline_llm.py │ │ ├── per_bf_gen.py │ │ ├── questionnaire.py │ │ ├── scene.py │ │ └── template_scene.json ├── llm_models │ ├── LICENSE │ ├── __init__.py │ ├── exceptions.py │ ├── model_client │ │ ├── __init__.py │ │ ├── aiohttp_gemini_client.py │ │ ├── base_client.py │ │ ├── mcp_sse_client.py │ │ └── openai_client.py │ ├── payload_content │ │ ├── __init__.py │ │ ├── message.py │ │ ├── resp_format.py │ │ └── tool_option.py │ ├── utils.py │ └── utils_model.py ├── main.py ├── mais4u │ ├── config │ │ ├── old │ │ │ └── s4u_config_20250715_141713.toml │ │ ├── s4u_config.toml │ │ └── s4u_config_template.toml │ ├── constant_s4u.py │ ├── mai_think.py │ ├── mais4u_chat │ │ ├── body_emotion_action_manager.py │ │ ├── context_web_manager.py │ │ ├── gift_manager.py │ │ ├── internal_manager.py │ │ ├── s4u_chat.py │ │ ├── s4u_mood_manager.py │ │ ├── s4u_msg_processor.py │ │ ├── s4u_prompt.py │ │ ├── s4u_stream_generator.py │ │ ├── s4u_watching_manager.py │ │ ├── screen_manager.py │ │ ├── super_chat_manager.py │ │ └── yes_or_no.py │ ├── openai_client.py │ └── s4u_config.py ├── manager │ ├── async_task_manager.py │ └── local_store_manager.py ├── mood │ └── mood_manager.py ├── person_info │ ├── person_info.py │ ├── relationship_builder.py │ ├── relationship_builder_manager.py │ ├── relationship_fetcher.py │ └── relationship_manager.py ├── plugin_system │ ├── __init__.py │ ├── apis │ │ ├── __init__.py │ │ ├── chat_api.py │ │ ├── component_manage_api.py │ │ ├── config_api.py │ │ ├── cross_context_api.py │ │ ├── database_api.py │ │ ├── emoji_api.py │ │ ├── generator_api.py │ │ ├── llm_api.py │ │ ├── logging_api.py │ │ ├── message_api.py │ │ ├── permission_api.py │ │ ├── person_api.py │ │ ├── plugin_manage_api.py │ │ ├── plugin_register_api.py │ │ ├── schedule_api.py │ │ ├── scoring_api.py │ │ ├── send_api.py │ │ └── tool_api.py │ ├── base │ │ ├── __init__.py │ │ ├── base_action.py │ │ ├── base_chatter.py │ │ ├── base_command.py │ │ ├── base_event.py │ │ ├── base_events_handler.py │ │ ├── base_interest_calculator.py │ │ ├── base_plugin.py │ │ ├── base_tool.py │ │ ├── command_args.py │ │ ├── component_types.py │ │ ├── config_types.py │ │ ├── plugin_base.py │ │ ├── plugin_metadata.py │ │ └── plus_command.py │ ├── core │ │ ├── __init__.py │ │ ├── component_registry.py │ │ ├── event_manager.py │ │ ├── global_announcement_manager.py │ │ ├── permission_manager.py │ │ ├── plugin_manager.py │ │ └── tool_use.py │ ├── services │ │ ├── interest_service.py │ │ └── relationship_service.py │ └── utils │ │ ├── __init__.py │ │ ├── dependency_alias.py │ │ ├── dependency_config.py │ │ ├── dependency_manager.py │ │ ├── mcp_connector.py │ │ ├── mcp_tool_provider.py │ │ └── permission_decorators.py ├── plugins │ ├── __init__.py │ └── built_in │ │ ├── affinity_flow_chatter │ │ ├── README.md │ │ ├── __init__.py │ │ ├── affinity_chatter.py │ │ ├── affinity_interest_calculator.py │ │ ├── plan_executor.py │ │ ├── plan_filter.py │ │ ├── plan_generator.py │ │ ├── planner.py │ │ ├── planner_prompts.py │ │ ├── plugin.py │ │ └── relationship_tracker.py │ │ ├── core_actions │ │ ├── __init__.py │ │ ├── anti_injector_manager.py │ │ ├── emoji.py │ │ └── plugin.py │ │ ├── knowledge │ │ └── lpmm_get_knowledge.py │ │ ├── maizone_refactored │ │ ├── __init__.py │ │ ├── actions │ │ │ ├── __init__.py │ │ │ ├── read_feed_action.py │ │ │ └── send_feed_action.py │ │ ├── commands │ │ │ ├── __init__.py │ │ │ └── send_feed_command.py │ │ ├── plugin.py │ │ ├── services │ │ │ ├── __init__.py │ │ │ ├── content_service.py │ │ │ ├── cookie_service.py │ │ │ ├── image_service.py │ │ │ ├── manager.py │ │ │ ├── monitor_service.py │ │ │ ├── qzone_service.py │ │ │ ├── reply_tracker_service.py │ │ │ └── scheduler_service.py │ │ └── utils │ │ │ ├── __init__.py │ │ │ └── history_utils.py │ │ ├── napcat_adapter_plugin │ │ ├── .gitignore │ │ ├── CONSTS.py │ │ ├── __init__.py │ │ ├── event_handlers.py │ │ ├── event_types.py │ │ ├── plugin.py │ │ ├── pyproject.toml │ │ ├── src │ │ │ ├── __init__.py │ │ │ ├── database.py │ │ │ ├── message_chunker.py │ │ │ ├── mmc_com_layer.py │ │ │ ├── recv_handler │ │ │ │ ├── __init__.py │ │ │ │ ├── message_handler.py │ │ │ │ ├── message_sending.py │ │ │ │ ├── meta_event_handler.py │ │ │ │ ├── notice_handler.py │ │ │ │ └── qq_emoji_list.py │ │ │ ├── response_pool.py │ │ │ ├── send_handler.py │ │ │ ├── utils.py │ │ │ ├── video_handler.py │ │ │ └── websocket_manager.py │ │ └── todo.md │ │ ├── permission_management │ │ ├── __init__.py │ │ └── plugin.py │ │ ├── plugin_management │ │ ├── __init__.py │ │ └── plugin.py │ │ ├── proactive_thinker │ │ ├── __init__.py │ │ ├── plugin.py │ │ ├── proacive_thinker_event.py │ │ ├── proactive_chatter_refactor_plan.md │ │ └── proactive_thinker_executor.py │ │ ├── social_toolkit_plugin │ │ ├── __init__.py │ │ ├── plugin.py │ │ └── qq_emoji_list.py │ │ ├── tts_plugin │ │ ├── __init__.py │ │ └── plugin.py │ │ └── web_search_tool │ │ ├── __init__.py │ │ ├── engines │ │ ├── __init__.py │ │ ├── base.py │ │ ├── bing_engine.py │ │ ├── ddg_engine.py │ │ ├── exa_engine.py │ │ ├── searxng_engine.py │ │ └── tavily_engine.py │ │ ├── plugin.py │ │ ├── tools │ │ ├── __init__.py │ │ ├── url_parser.py │ │ └── web_search.py │ │ └── utils │ │ ├── __init__.py │ │ ├── api_key_manager.py │ │ ├── formatters.py │ │ └── url_utils.py ├── schedule │ ├── database.py │ ├── llm_generator.py │ ├── monthly_plan_manager.py │ ├── plan_manager.py │ ├── schedule_manager.py │ └── schemas.py └── utils │ ├── __init__.py │ ├── message_chunker.py │ └── timing_utils.py ├── template ├── bot_config_template.toml ├── model_config_template.toml └── template.env ├── ui_log_adapter.py └── uv.lock /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MaiBot-DevContainer", 3 | "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", 4 | "features": { 5 | "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { 6 | "packages": [ 7 | "tmux" 8 | ] 9 | }, 10 | "ghcr.io/devcontainers/features/github-cli:1": {} 11 | }, 12 | "forwardPorts": [ 13 | "8000:8000" 14 | ], 15 | "postCreateCommand": "pip3 install --user -r requirements.txt", 16 | "customizations": { 17 | "jetbrains": { 18 | "backend": "PyCharm" 19 | }, 20 | "vscode": { 21 | "extensions": [ 22 | "tamasfe.even-better-toml", 23 | "njpwerner.autodocstring", 24 | "ms-python.python", 25 | "KevinRose.vsc-python-indent", 26 | "ms-python.vscode-pylance", 27 | "ms-python.autopep8" 28 | ] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | __pycache__ 3 | *.pyo 4 | *.pyd 5 | .DS_Store 6 | mongodb 7 | napcat 8 | docs/ 9 | .github/ 10 | # test 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: 提交 Bug 3 | labels: ["BUG"] 4 | body: 5 | - type: checkboxes 6 | attributes: 7 | label: "检查项" 8 | description: "请检查下列项目,并勾选确认。" 9 | options: 10 | - label: "我确认此问题在所有分支的最新版本中依旧存在" 11 | required: true 12 | - label: "我确认在 Issues 列表中并无其他人已经提出过与此问题相同或相似的问题" 13 | required: true 14 | - label: "我使用了 Docker" 15 | - type: dropdown 16 | attributes: 17 | label: "使用的分支" 18 | description: "请选择您正在使用的版本分支" 19 | options: 20 | - main 21 | - dev 22 | validations: 23 | required: true 24 | - type: input 25 | attributes: 26 | label: "具体版本号" 27 | description: "请输入您使用的具体版本号" 28 | placeholder: "例如:0.5.11、0.5.8、0.6.0" 29 | validations: 30 | required: true 31 | - type: textarea 32 | attributes: 33 | label: 遇到的问题 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: 报错信息 39 | validations: 40 | required: true 41 | - type: textarea 42 | attributes: 43 | label: 如何重现此问题? 44 | placeholder: "若不知道请略过此问题" 45 | - type: textarea 46 | attributes: 47 | label: 可能造成问题的原因 48 | placeholder: "若不知道请略过此问题" 49 | - type: textarea 50 | attributes: 51 | label: 系统环境 52 | placeholder: "例如:Windows 11 专业版 64位 24H2 / Debian Bookworm" 53 | validations: 54 | required: true 55 | - type: textarea 56 | attributes: 57 | label: Python 版本 58 | placeholder: "例如:Python 3.11" 59 | validations: 60 | required: true 61 | - type: textarea 62 | attributes: 63 | label: 补充信息 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: 新功能请求 3 | labels: ["Feature"] 4 | body: 5 | - type: checkboxes 6 | attributes: 7 | label: "检查项" 8 | description: "请检查下列项目,并勾选确认。" 9 | options: 10 | - label: "我确认在Issues列表中并无其他人已经建议过相似的功能" 11 | required: true 12 | - label: "这个新功能可以解决目前存在的某个问题或BUG" 13 | - label: "你已经更新了最新的dev分支,但是你的问题依然没有被解决" 14 | - type: textarea 15 | attributes: 16 | label: 期望的功能描述 17 | validations: 18 | required: true 19 | - type: textarea 20 | attributes: 21 | label: 补充信息 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/prompts/chat.prompt.md: -------------------------------------------------------------------------------- 1 | --- 2 | mode: agent 3 | --- 4 | 记得执行前激活虚拟环境,用的shell是powershell与linux语法有区别 -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker CI 2 | 3 | on: 4 | # push: 5 | # branches: 6 | # - master 7 | # - develop 8 | # tags: 9 | # - "v*.*.*" 10 | # - "v*" 11 | # - "*.*.*" 12 | # - "*.*.*-*" 13 | workflow_dispatch: # 允许手动触发工作流 14 | 15 | jobs: 16 | build-amd64: 17 | name: 构建 AMD64 镜像 18 | runs-on: ubuntu-24.04 19 | outputs: 20 | digest: ${{ steps.build.outputs.digest }} 21 | steps: 22 | - name: 检出 Git 仓库 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: 克隆 maim_message 28 | uses: actions/checkout@v4 29 | with: 30 | repository: MaiM-with-u/maim_message 31 | path: maim_message 32 | 33 | - name: 克隆 MaiMBot-LPMM 34 | uses: actions/checkout@v4 35 | with: 36 | repository: MaiM-with-u/MaiMBot-LPMM 37 | path: MaiMBot-LPMM 38 | 39 | - name: 设置 Docker Buildx 40 | uses: docker/setup-buildx-action@v3 41 | with: 42 | buildkitd-flags: --debug 43 | 44 | - name: 登录到 Docker Hub 45 | uses: docker/login-action@v3 46 | with: 47 | username: ${{ secrets.DOCKERHUB_USERNAME }} 48 | password: ${{ secrets.DOCKERHUB_TOKEN }} 49 | 50 | - name: Docker 元数据 51 | id: meta 52 | uses: docker/metadata-action@v5 53 | with: 54 | images: ${{ secrets.DOCKERHUB_USERNAME }}/maibot 55 | 56 | - name: 动态生成镜像标签 57 | id: tag 58 | run: | 59 | if [ "$GITHUB_REF" == "refs/heads/master" ]; then 60 | echo "tag=latest" >> $GITHUB_ENV 61 | elif [ "$GITHUB_REF" == "refs/heads/develop" ]; then 62 | echo "tag=dev" >> $GITHUB_ENV 63 | else 64 | echo "tag=${{ github.ref_name }}" >> $GITHUB_ENV 65 | fi 66 | 67 | - name: 构建并推送 AMD64 镜像 68 | id: build 69 | uses: docker/build-push-action@v5 70 | with: 71 | context: . 72 | platforms: linux/amd64 73 | labels: ${{ steps.meta.outputs.labels }} 74 | file: ./Dockerfile 75 | cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-buildcache 76 | cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maibot:amd64-buildcache,mode=max 77 | outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/maibot:${{ env.tag }},name-canonical=true,push=true 78 | build-args: | 79 | BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') 80 | VCS_REF=${{ github.sha }} 81 | BRANCH_NAME=${{ github.ref_name }} 82 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.5-slim-bookworm 2 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ 3 | 4 | # 工作目录 5 | WORKDIR /mmc 6 | 7 | # 复制依赖列表 8 | COPY requirements.txt . 9 | # 同级目录下需要有 maim_message MaiMBot-LPMM 10 | #COPY maim_message /maim_message 11 | COPY MaiMBot-LPMM /MaiMBot-LPMM 12 | 13 | # 编译器 14 | RUN apt-get update && apt-get install -y build-essential 15 | 16 | # lpmm编译安装 17 | RUN cd /MaiMBot-LPMM && uv pip install --system -r requirements.txt 18 | RUN uv pip install --system Cython py-cpuinfo setuptools 19 | RUN cd /MaiMBot-LPMM/lib/quick_algo && python build_lib.py --cleanup --cythonize --install 20 | 21 | 22 | # 安装依赖 23 | RUN uv pip install --system --upgrade pip 24 | #RUN uv pip install --system -e /maim_message 25 | RUN uv pip install --system -r requirements.txt 26 | 27 | # 复制项目代码 28 | COPY . . 29 | 30 | EXPOSE 8000 31 | 32 | ENTRYPOINT [ "python","bot.py" ] -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | ### MoFox_Bot 隐私说明 2 | 3 | **版本:** 1.1 4 | **更新于: 2025年8月30日** 5 | **生效日期:2025年8月26日** 6 | **适用的MoFox_Bot版本号:所有版本** 7 | 8 | **2025© MoFox_Bot项目团队* 9 | 10 | 我们(MoFox_Bot 项目团队)非常重视您的隐私。当您使用 MoFox_Bot 时,代表您同意我们按照本说明处理您的信息。 11 | 12 | --- 13 | 14 | #### 我们如何处理您的信息? 15 | 16 | 1. **对话内容与第三方服务** 17 | * 为了让机器人回复您,我们会将您的对话内容发送给第三方 AI 服务(比如 OpenAI、Google 等)。 18 | * 因此,您的对话会同时受到我们的隐私说明和第三方服务隐私政策的约束。 19 | 20 | 2. **本地知识库与记忆** 21 | * 我们会在您自己的设备上,利用您的对话内容来构建专属的知识库和记忆库。 22 | * 这样做能让机器人的回复更贴切、更连贯。这些数据**只会存储在您的本地**。 23 | 24 | 3. **本地日志文件** 25 | * 我们会在您的设备上记录程序运行日志,用于排查问题。这些日志**不会上传到网络**。 26 | * 只有当您需要技术支持并主动将日志文件发送给我们时,我们才会看到这些信息。 27 | 28 | --- 29 | 30 | #### 关于第三方插件 31 | 32 | * **请注意:** 您可以为 MoFox_Bot 安装由其他开发者制作的“插件”。 33 | * 这些插件可能会收集、处理或发送您的数据。**它们的行为由插件开发者决定,我们无法控制。** 34 | * 我们无法保证第三方插件的安全性。在使用前,请务必了解其独立的隐私政策。 35 | * **因使用第三方插件导致的任何隐私风险,需要您自行承担。** 36 | 37 | --- 38 | 39 | #### 责任声明 40 | 41 | * 如果因为您自己的操作、或者其他不可抗力因素导致您的信息泄露,您需要自行承担后果,我们不承担责任。 42 | * **再次强调:** 对于因使用**第三方插件**而产生的任何隐私或数据安全问题,我们概不负责。 43 | 44 | --- 45 | 46 | #### 条款更新 47 | 48 | * 我们将来可能会更新本隐私说明。如果我们更新了,您继续使用就代表您同意新的条款。如果您不同意,请停止使用。 -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [x] 添加向适配器发送命令并获取返回的函数 2 | - [x] 适配器正向/反向Websocket支持 3 | - [x] 内置网页搜索及URL解析工具(多平台,轮询秘钥) 4 | - [x] 内置空间插件 5 | - [ ] 在好友聊天生成回复时设置输入状态 6 | - [x] 基于关键帧的视频识别功能 7 | - [x] 对XML,JSON等特殊消息解析 8 | - [x] 插件热重载 9 | - [x] 适配器黑/白名单迁移至独立配置文件,并支持热重载 10 | - [x] 添加MySQL支持,重构数据库 11 | - [x] 戳一戳插件支持 12 | - [x] 设置表情插件支持 13 | - [ ] 沙盒插件支持 14 | - [ ] 点赞插件支持 15 | - [x] 真艾特支持(LLM决定是否艾特) 16 | - [x] 重新支持聊天模式配置 17 | - [ ] 好友强制回复支持 18 | - [x] 添加表情包情感分析功能 19 | - [x] 添加主动思考配置 20 | - [x] 添加日程管理 21 | - [x] 添加MCP SSE支持 22 | - [ ] 增加基于GPT-Sovits的多情感语音合成功能(插件形式) 23 | - [ ] 增加基于Open Voice的语音合成功能(插件形式) 24 | - [x] 对聊天信息的视频增加一个videoid(就像imageid一样) 25 | - [ ] 修复generate_responce_for_image方法有的时候会对同一张图片生成两次描述的问题 26 | - [x] 主动思考的通用提示词改进 27 | - [x] 添加贴表情聊天流判断,过滤好友 28 | 29 | 30 | - 大工程 31 | · 增加一个基于Rust后端,daisyui为(装饰的)前端的启动器,以下是详细功能 32 | - 一个好看的ui 33 | - 支持所有的非core的插件管理 34 | - 支持一键部署所有环境 35 | - 将启动时的powershell界面内嵌到前端启动器内 36 | - 能够支持自由修改bot、llm的配置 37 | - 兼容Matcha,将Matcha的界面也嵌入到启动器内 38 | - 数据库预览以及修改功能 39 | - (待确定)Live 2d chat功能的开发 -------------------------------------------------------------------------------- /__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Bot项目的主入口点""" 3 | 4 | if __name__ == "__main__": 5 | # 设置Python路径并执行bot.py 6 | import sys 7 | from pathlib import Path 8 | 9 | # 添加当前目录到Python路径 10 | current_dir = Path(__file__).parent 11 | sys.path.insert(0, str(current_dir)) 12 | 13 | # 执行bot.py的代码 14 | bot_file = current_dir / "bot.py" 15 | with open(bot_file, encoding="utf-8") as f: 16 | exec(f.read()) 17 | 18 | 19 | # 这个文件是为了适配一键包使用的,在一键包项目之外没有用 20 | -------------------------------------------------------------------------------- /changelogs/changes.md: -------------------------------------------------------------------------------- 1 | # 插件API与规范修改 2 | 3 | 1. 现在`plugin_system`的`__init__.py`文件中包含了所有插件API的导入,用户可以直接使用`from src.plugin_system import *`来导入所有API。 4 | 5 | 2. register_plugin函数现在转移到了`plugin_system.apis.plugin_register_api`模块中,用户可以通过`from src.plugin_system.apis.plugin_register_api import register_plugin`来导入。 6 | - 顺便一提,按照1中说法,你可以这么用: 7 | ```python 8 | from src.plugin_system import register_plugin 9 | ``` 10 | 11 | 3. 现在强制要求的property如下,即你必须覆盖的属性有: 12 | - `plugin_name`: 插件名称,必须是唯一的。(与文件夹相同) 13 | - `enable_plugin`: 是否启用插件,默认为`True`。 14 | - `dependencies`: 插件依赖的其他插件列表,默认为空。**现在并不检查(也许)** 15 | - `python_dependencies`: 插件依赖的Python包列表,默认为空。**现在并不检查** 16 | - `config_file_name`: 插件配置文件名,默认为`config.toml`。 17 | - `config_schema`: 插件配置文件的schema,用于自动生成配置文件。 18 | 4. 部分API的参数类型和返回值进行了调整 19 | - `chat_api.py`中获取流的参数中可以使用一个特殊的枚举类型来获得所有平台的 ChatStream 了。 20 | - `config_api.py`中的`get_global_config`和`get_plugin_config`方法现在支持嵌套访问的配置键名。 21 | - `database_api.py`中的`db_query`方法调整了参数顺序以增强参数限制的同时,保证了typing正确;`db_get`方法增加了`single_result`参数,与`db_query`保持一致。 22 | 5. 增加了`logging_api`,可以用`get_logger`来获取日志记录器。 23 | 6. 增加了插件和组件管理的API。 24 | 7. `BaseCommand`的`execute`方法现在返回一个三元组,包含是否执行成功、可选的回复消息和是否拦截消息。 25 | - 这意味着你终于可以动态控制是否继续后续消息的处理了。 26 | 8. 移除了dependency_manager,但是依然保留了`python_dependencies`属性,等待后续重构。 27 | - 一并移除了文档有关manager的内容。 28 | 9. 增加了工具的有关api 29 | 30 | # 插件系统修改 31 | 1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)** 32 | 2. 修复了一下显示插件信息不显示的问题。同时精简了一下显示内容 33 | 3. 修复了插件系统混用了`plugin_name`和`display_name`的问题。现在所有的插件信息都使用`display_name`来显示,而内部标识仍然使用`plugin_name`。 34 | 4. 现在增加了参数类型检查,完善了对应注释 35 | 5. 现在插件抽象出了总基类 `PluginBase` 36 | - 基于`Action`和`Command`的插件基类现在为`BasePlugin`。 37 | - 基于`Event`的插件基类现在为`BaseEventPlugin`。 38 | - 基于`Action`,`Command`和`Event`的插件基类现在为`BasePlugin`,所有插件都应该继承此基类。 39 | - `BasePlugin`继承自`PluginBase`。 40 | - 所有的插件类都由`register_plugin`装饰器注册。 41 | 6. 现在我们终于可以让插件有自定义的名字了! 42 | - 真正实现了插件的`plugin_name`**不受文件夹名称限制**的功能。(吐槽:可乐你的某个小小细节导致我搞了好久……) 43 | - 通过在插件类中定义`plugin_name`属性来指定插件内部标识符。 44 | - 由于此更改一个文件中现在可以有多个插件类,但每个插件类必须有**唯一的**`plugin_name`。 45 | - 在某些插件加载失败时,现在会显示包名而不是插件内部标识符。 46 | - 例如:`MaiMBot.plugins.example_plugin`而不是`example_plugin`。 47 | - 仅在插件 import 失败时会如此,正常注册过程中失败的插件不会显示包名,而是显示插件内部标识符。(这是特性,但是基本上不可能出现这个情况) 48 | 7. 现在不支持单文件插件了,加载方式已经完全删除。 49 | 8. 把`BaseEventPlugin`合并到了`BasePlugin`中,所有插件都应该继承自`BasePlugin`。 50 | 9. `BaseEventHandler`现在有了`get_config`方法了。 51 | 10. 修正了`main.py`中的错误输出。 52 | 11. 修正了`command`所编译的`Pattern`注册时的错误输出。 53 | 12. `events_manager`有了task相关逻辑了。 54 | 13. 现在有了插件卸载和重载功能了,也就是热插拔。 55 | 14. 实现了组件的全局启用和禁用功能。 56 | - 通过`enable_component`和`disable_component`方法来启用或禁用组件。 57 | - 不过这个操作不会保存到配置文件~ 58 | 15. 实现了组件的局部禁用,也就是针对某一个聊天禁用的功能。 59 | - 通过`disable_specific_chat_action`,`enable_specific_chat_action`,`disable_specific_chat_command`,`enable_specific_chat_command`,`disable_specific_chat_event_handler`,`enable_specific_chat_event_handler`来操作 60 | - 同样不保存到配置文件~ 61 | 16. 把`BaseTool`一并合并进入了插件系统 62 | 63 | # 官方插件修改 64 | 1. `HelloWorld`插件现在有一个样例的`EventHandler`。 65 | 2. 内置插件增加了一个通过`Command`来管理插件的功能。具体是使用`/pm`命令唤起。(需要自行启用) 66 | 3. `HelloWorld`插件现在有一个样例的`CompareNumbersTool`。 67 | 68 | ### 执笔BGM 69 | 塞壬唱片! -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | adapters: 3 | container_name: maim-bot-adapters 4 | #### prod #### 5 | image: unclas/maimbot-adapter:latest 6 | # image: infinitycat/maimbot-adapter:latest 7 | #### dev #### 8 | # image: unclas/maimbot-adapter:dev 9 | # image: infinitycat/maimbot-adapter:dev 10 | environment: 11 | - TZ=Asia/Shanghai 12 | # ports: 13 | # - "8095:8095" 14 | volumes: 15 | - ./docker-config/adapters/config.toml:/adapters/config.toml # 持久化adapters配置文件 16 | - ./data/adapters:/adapters/data # adapters 数据持久化 17 | restart: always 18 | networks: 19 | - maim_bot 20 | core: 21 | container_name: maim-bot-core 22 | #### prod #### 23 | image: sengokucola/maibot:latest 24 | # image: infinitycat/maibot:latest 25 | #### dev #### 26 | # image: sengokucola/maibot:dev 27 | # image: infinitycat/maibot:dev 28 | environment: 29 | - TZ=Asia/Shanghai 30 | # - EULA_AGREE=99f08e0cab0190de853cb6af7d64d4de # 同意EULA 31 | # - PRIVACY_AGREE=9943b855e72199d0f5016ea39052f1b6 # 同意EULA 32 | # ports: 33 | # - "8000:8000" 34 | volumes: 35 | - ./docker-config/mmc/.env:/MaiMBot/.env # 持久化env配置文件 36 | - ./docker-config/mmc:/MaiMBot/config # 持久化bot配置文件 37 | - ./data/MaiMBot/maibot_statistics.html:/MaiMBot/maibot_statistics.html #统计数据输出 38 | - ./data/MaiMBot:/MaiMBot/data # 共享目录 39 | - ./data/MaiMBot/plugins:/MaiMBot/plugins # 插件目录 40 | - ./data/MaiMBot/logs:/MaiMBot/logs # 日志目录 41 | - site-packages:/usr/local/lib/python3.13/site-packages # 持久化Python包 42 | restart: always 43 | networks: 44 | - maim_bot 45 | napcat: 46 | environment: 47 | - NAPCAT_UID=1000 48 | - NAPCAT_GID=1000 49 | - TZ=Asia/Shanghai 50 | ports: 51 | - "6099:6099" 52 | volumes: 53 | - ./docker-config/napcat:/app/napcat/config # 持久化napcat配置文件 54 | - ./data/qq:/app/.config/QQ # 持久化QQ本体 55 | - ./data/MaiMBot:/MaiMBot/data # 共享目录 56 | container_name: maim-bot-napcat 57 | restart: always 58 | image: mlikiowa/napcat-docker:latest 59 | networks: 60 | - maim_bot 61 | sqlite-web: 62 | # 注意:coleifer/sqlite-web 镜像不支持arm64 63 | image: coleifer/sqlite-web 64 | container_name: sqlite-web 65 | restart: always 66 | ports: 67 | - "8120:8080" 68 | volumes: 69 | - ./data/MaiMBot:/data/MaiMBot 70 | environment: 71 | - SQLITE_DATABASE=MaiMBot/MaiBot.db # 你的数据库文件 72 | networks: 73 | - maim_bot 74 | 75 | # chat2db占用相对较高但是功能强大 76 | # 内存占用约600m,内存充足推荐选此 77 | # chat2db: 78 | # image: chat2db/chat2db:latest 79 | # container_name: maim-bot-chat2db 80 | # restart: always 81 | # ports: 82 | # - "10824:10824" 83 | # volumes: 84 | # - ./data/MaiMBot:/data/MaiMBot 85 | # networks: 86 | # - maim_bot 87 | volumes: 88 | site-packages: 89 | networks: 90 | maim_bot: 91 | driver: bridge 92 | -------------------------------------------------------------------------------- /docs/assets/1750326700269.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/docs/assets/1750326700269.png -------------------------------------------------------------------------------- /docs/assets/1750332508760.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/docs/assets/1750332508760.png -------------------------------------------------------------------------------- /docs/assets/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/docs/assets/image-1.png -------------------------------------------------------------------------------- /docs/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/docs/assets/image.png -------------------------------------------------------------------------------- /docs/deployment_guide.md: -------------------------------------------------------------------------------- 1 | # MoFox_Bot 部署指南 2 | 3 | 欢迎使用 MoFox_Bot!本指南将引导您完成在 Windows 环境下部署 MoFox_Bot 的全部过程。 4 | 5 | ## 1. 系统要求 6 | 7 | - **操作系统**: Windows 10 或 Windows 11 8 | - **Python**: 版本 >= 3.10 9 | - **Git**: 用于克隆项目仓库 10 | - **uv**: 推荐的 Python 包管理器 (版本 >= 0.1.0) 11 | 12 | ## 2. 部署步骤 13 | 14 | ### 第一步:获取必要的文件 15 | 16 | 首先,创建一个用于存放 MoFox_Bot 相关文件的文件夹,并通过 `git` 克隆 MoFox_Bot 主程序和 Napcat 适配器。 17 | 18 | ```shell 19 | mkdir MoFox_Bot_Deployment 20 | cd MoFox_Bot_Deployment 21 | git clone hhttps://github.com/MoFox-Studio/MoFox_Bot.git 22 | git clone https://github.com/MoFox-Studio/Napcat-Adapter.git 23 | ``` 24 | 25 | ### 第二步:环境配置 26 | 27 | 我们推荐使用 `uv` 来管理 Python 环境和依赖,因为它提供了更快的安装速度和更好的依赖管理体验。 28 | 29 | **安装 uv:** 30 | 31 | ```shell 32 | pip install uv 33 | ``` 34 | 35 | ### 第三步:依赖安装 36 | 37 | **1. 安装 MoFox_Bot 依赖:** 38 | 39 | 进入 `mmc` 文件夹,创建虚拟环境并安装依赖。 40 | 41 | ```shell 42 | cd mmc 43 | uv venv 44 | uv pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple --upgrade 45 | ``` 46 | 47 | **2. 安装 Napcat-Adapter 依赖:** 48 | 49 | 回到上一级目录,进入 `Napcat-Adapter` 文件夹,创建虚拟环境并安装依赖。 50 | 51 | ```shell 52 | cd .. 53 | cd Napcat-Adapter 54 | uv venv 55 | uv pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple --upgrade 56 | ``` 57 | 58 | ### 第四步:配置 MoFox_Bot 和 Adapter 59 | 60 | **1. MoFox_Bot 配置:** 61 | 62 | - 在 `mmc` 文件夹中,将 `template/bot_config_template.toml` 复制到 `config/bot_config.toml`。 63 | - 将 `template/model_config_template.toml` 复制到 `config/model_config.toml`。 64 | - 根据 [模型配置指南](guides/model_configuration_guide.md) 和 `bot_config.toml` 文件中的注释,填写您的 API Key 和其他相关配置。 65 | 66 | **2. Napcat-Adapter 配置:** 67 | 68 | - 在 `Napcat-Adapter` 文件夹中,将 `template/template_config.toml` 复制到根目录并改名为 `config.toml`。 69 | - 打开 `config.toml` 文件,配置 `[Napcat_Server]` 和 `[MaiBot_Server]` 字段。 70 | - `[Napcat_Server]` 的 `port` 应与 Napcat 设置的反向代理 URL 中的端口相同。 71 | - `[MaiBot_Server]` 的 `port` 应与 MoFox_Bot 的 `bot_config.toml` 中设置的端口相同。 72 | 73 | ### 第五步:运行 74 | 75 | **1. 启动 Napcat:** 76 | 77 | 请参考 [NapCatQQ 文档](https://napcat-qq.github.io/) 进行部署和启动。 78 | 79 | **2. 启动 MoFox_Bot:** 80 | 81 | 进入 `mmc` 文件夹,使用 `uv` 运行。 82 | 83 | ```shell 84 | cd mmc 85 | uv run python bot.py 86 | ``` 87 | 88 | **3. 启动 Napcat-Adapter:** 89 | 90 | 打开一个新的终端窗口,进入 `Napcat-Adapter` 文件夹,使用 `uv` 运行。 91 | 92 | ```shell 93 | cd Napcat-Adapter 94 | uv run python main.py 95 | ``` 96 | 97 | 至此,MoFox_Bot 已成功部署并运行。 98 | 99 | ## 3. 详细配置说明 100 | 101 | ### `bot_config.toml` 102 | 103 | 这是 MoFox_Bot 的主配置文件,包含了机器人昵称、主人QQ、命令前缀、数据库设置等。请根据文件内的注释进行详细配置。 104 | 105 | ### `model_config.toml` 106 | 107 | 此文件用于配置 AI 模型和 API 服务提供商。详细配置方法请参考 [模型配置指南](guides/model_configuration_guide.md)。 108 | 109 | ### 插件配置 110 | 111 | 每个插件都有独立的配置文件,位于 `mmc/config/plugins/` 目录下。插件的配置由其 `config_schema` 自动生成。详细信息请参考 [插件配置完整指南](plugins/configuration-guide.md)。 112 | 113 | ## 4. 故障排除 114 | 115 | - **依赖安装失败**: 116 | - 尝试更换 PyPI 镜像源。 117 | - 检查网络连接。 118 | - **API 调用失败**: 119 | - 检查 `model_config.toml` 中的 API Key 和 `base_url` 是否正确。 120 | - **无法连接到 Napcat**: 121 | - 检查 Napcat 是否正常运行。 122 | - 确认 `Napcat-Adapter` 的 `config.toml` 中 `[Napcat_Server]` 的 `port` 是否与 Napcat 设置的端口一致。 123 | 124 | 如果遇到其他问题,请查看 `logs/` 目录下的日志文件以获取详细的错误信息。 -------------------------------------------------------------------------------- /docs/development/CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # 如何给MoFox_Bot做贡献v1.0 2 | 3 | 修改时间2025/4/5 4 | 5 | 如有修改建议或疑问,请在github上建立issue 6 | 7 | 首先,非常感谢你抽出时间来做贡献!❤️ 8 | 9 | 这份文档是告诉你,当你想向MoFox_Bot提交代码,或者想要以其他形式加入MoFox_Bot的开发,或周边插件的开发,你可以怎么做。 10 | 11 | 我们鼓励并重视任何形式的贡献,但无序的贡献只会使麦麦的维护与更新变成一团糟。因此,我们建议您在做出贡献之前,先查看本指南。 12 | 13 | 14 | > 另外,如果您喜欢这个项目,但只是没有时间做贡献,那也没关系。还有其他简单的方式来支持本项目并表达您的感激之情,我们也会非常高兴: 15 | > - 给我们点一颗小星星(Star) 16 | > - 在您的项目的readme中引用这个项目 17 | 18 | ## 目录 19 | 20 | ● [我有问题](#我有问题) 21 | ● [我想做贡献](#我想做贡献) 22 | ● [我想提出建议](#提出建议) 23 | 24 | ## 我有问题 25 | 26 | > 如果你想问一个问题,我们会假设你已经阅读了现有的文档。 27 | 28 | 在你提问之前,最好先搜索现有的[issue](/issues),看看是否有助于解决你的问题。如果你找到了匹配的issue,但仍需要追加说明,你可以在该issue下提出你的问题。同时,我们还建议你先在互联网上搜索答案。 29 | 30 | 如果你仍然觉得有必要提问并需要进行说明,我们建议你: 31 | 32 | ● 开一个[新Issue](/issues/new)。并尽可能详细地描述你的问题。 33 | 34 | ● 提供尽可能多的上下文信息,让我们更好地理解你遇到的问题。比如:提供版本信息(哪个分支,版本号是多少,运行环境有哪些等),具体取决于你认为相关的内容。 35 | 36 | 只要你提出的issue明确且合理,我们都会回复并尝试解决您的问题。 37 | 38 | 39 | ## 我想做贡献 40 | 41 | > ### 项目所有权与维护 42 | > MoFox_Bot项目(基于MaiBot修改,核心为MaiCore)由雅诺狐创建,采用GPL3开源协议。 43 | > MoFox_Bot项目现已移动至MoFox_Bot组织下,目前主要内容由核心开发组维护,整体由核心开发组、reviewer和所有贡献者共同维护(该部分在未来将会明确)。 44 | > 为了保证设计的统一和节省不必要的精力,以及为了对项目有整体的把控,我们对不同类型的贡献采取不同的审核策略: 45 | > 46 | > #### 功能新增 47 | > - 定义:涉及新功能添加、架构调整、重要模块重构等 48 | > - 要求:原则上暂不接收,你可以发布issue提供新功能建议。 49 | > 50 | > #### Bug修复 51 | > - 定义:修复现有功能中的错误,包括非预期行为(需要发布issue进行确认)和运行错误,不涉及新功能或架构变动 52 | > - 要求:由核心组成员或2名及以上reviewer同时确认才会被合并 53 | > - 关闭:包含预期行为改动,新功能,破坏原有功能,数据库破坏性改动等的pr将会被关闭 54 | > 55 | > #### 文档修补 56 | > - 定义:修复现有文档中的错误,提供新的帮助文档 57 | > - 要求:现需要提交至组织下docs仓库,由reviewer确认后合并 58 | 59 | 60 | > ### 法律声明 61 | > 当你为本项目贡献代码/文档时,你必须确认: 62 | > 1. 你贡献的内容100%是由你创作; 63 | > 2. 你对这些内容拥有相应的权利; 64 | > 3. 你贡献的内容将按项目许可协议使用。 65 | 66 | 67 | ## 提出建议 68 | 69 | 这一部分指导您如何为MoFox_Bot提交一个建议,包括全新的功能和对现有功能的小改进。遵循这些指南将有助于维护人员和社区了解您的建议并找到相关的建议。 70 | 71 | 在提交建议之前 72 | 73 | ● 请确保您正在使用最新版本(正式版请查看master分支,测试版查看dev分支)。 74 | 75 | ● 请确保您已经阅读了文档,以确认您的建议是否已经被实现,也许是通过单独的配置。 76 | 77 | ● 仔细阅读文档并了解项目目前是否支持该功能,也许可以通过单独的配置来实现。 78 | 79 | ● 进行一番[搜索](/issues)以查看是否已经有人提出了这个建议。如果有,请在现有的issue下添加评论,而不是新开一个issue。 80 | 81 | ● 请确保您的建议符合项目的范围和目标。你需要提出一个强有力的理由来说服项目的开发者这个功能的优点。请记住,我们希望的功能是对大多数用户有用的,而不仅仅是少数用户。如果你只是针对少数用户,请考虑编写一个插件。 82 | 83 | ### 附(暂定): 84 | 核心组成员:@foxcyber907(雅诺狐) @Furina-1013-create(Navinatte)@tt-P607(言柒)@ikun-11451(ikun两年半)@minecraft1024a(一闪) 85 | 86 | reviewer:核心组+MoFox_Bot主仓库合作者/权限者 87 | 88 | 贡献者:所有提交过贡献的用户 89 | -------------------------------------------------------------------------------- /docs/integrations/Bing.md: -------------------------------------------------------------------------------- 1 | - **参数化与动态调整聊天行为**: 2 | - 将 `NormalChatInstance` 和 `HeartFlowChatInstance` 中的关键行为参数(例如:回复概率、思考频率、兴趣度阈值、状态转换条件等)提取出来,使其更易于配置。 3 | - 允许每个 `SubHeartflow` (即每个聊天场景) 拥有其独立的参数配置,实现"千群千面"。 4 | - 开发机制,使得这些参数能够被动态调整: 5 | - 基于外部反馈:例如,根据用户评价("话太多"或"太冷淡")调整回复频率。 6 | - 基于环境分析:例如,根据群消息的活跃度自动调整参与度。 7 | - 基于学习:通过分析历史交互数据,优化特定群聊下的行为模式。 8 | - 目标是让 Mai 在不同群聊中展现出更适应环境、更个性化的交互风格。 9 | 10 | - **动态 Prompt 生成与人格塑造**: 11 | - 当前 Prompt (提示词) 相对静态。计划实现动态或半结构化的 Prompt 生成。 12 | - Prompt 内容可根据以下因素调整: 13 | - **人格特质**: 通过参数化配置(如友善度、严谨性等),影响 Prompt 的措辞、语气和思考倾向,塑造更稳定和独特的人格。 14 | - **当前情绪**: 将实时情绪状态融入 Prompt,使回复更符合当下心境。 15 | - 目标:提升 `HeartFlowChatInstance` (HFC) 回复的多样性、一致性和真实感。 16 | - 前置:需要重构 Prompt 构建逻辑,可能引入 `PromptBuilder` 并提供标准接口 (认为是必须步骤)。 17 | 18 | 19 | - **增强工具调用能力 (Enhanced Tool Usage)**: 20 | - 扩展 `HeartFlowChatInstance` (HFC) 可用的工具集。 21 | - 考虑引入"元工具"或分层工具机制,允许 HFC 在需要时(如深度思考)访问更强大的工具,例如: 22 | - 修改自身或其他 `SubHeartflow` 的聊天参数。 23 | - 请求改变 Mai 的全局状态 (`MaiState`)。 24 | - 管理日程或执行更复杂的分析任务。 25 | - 目标:提升 HFC 的自主决策和行动能力,即使会增加一定的延迟。 26 | 27 | - **标准化人设生成 (Standardized Persona Generation)**: 28 | - **目标**: 解决手动配置 `人设` 文件缺乏标准、难以全面描述个性的问题,并生成更丰富、可操作的人格资源。 29 | - **方法**: 利用大型语言模型 (LLM) 辅助生成标准化的、结构化的人格**资源包**。 30 | - **生成内容**: 不仅生成描述性文本(替代现有 `individual` 配置),还可以同时生成与该人格配套的: 31 | - **相关工具 (Tools)**: 该人格倾向于使用的工具或能力。 32 | - **初始记忆/知识库 (Memories/Knowledge)**: 定义其背景和知识基础。 33 | - **核心行为模式 (Core Behavior Patterns)**: 预置一些典型的行为方式,可作为行为学习的起点。 34 | - **实现途径**: 35 | - 通过与 LLM 的交互式对话来定义和细化人格及其配套资源。 36 | - 让 LLM 分析提供的文本材料(如小说、背景故事)来提取人格特质和相关信息。 37 | - **优势**: 替代易出错且标准不一的手动配置,生成更丰富、一致、包含配套资源且易于系统理解和应用的人格包。 38 | 39 | 40 | - **探索高级记忆检索机制 (GE 系统概念):** 41 | - 研究超越简单关键词/近期性检索的记忆模型。 42 | - 考虑引入基于事件关联、相对时间线索和绝对时间锚点的检索方式。 43 | - 可能涉及设计新的事件表示或记忆结构。 44 | 45 | - **基于人格生成预设知识:** 46 | - 开发利用 LLM 和人格配置生成背景知识的功能。 47 | - 这些知识应符合角色的行为风格和可能的经历。 48 | - 作为一种"冷启动"或丰富角色深度的方式。 49 | 50 | 51 | 1.更nb的工作记忆,直接开一个play_ground,通过llm进行内容检索,这个play_ground可以容纳巨量信息,并且十分通用化,十分好。 -------------------------------------------------------------------------------- /docs/plugins/api/chat-api.md: -------------------------------------------------------------------------------- 1 | # 聊天API 2 | 3 | 聊天API模块专门负责聊天信息的查询和管理,帮助插件获取和管理不同的聊天流。 4 | 5 | ## 导入方式 6 | 7 | ```python 8 | from src.plugin_system import chat_api 9 | # 或者 10 | from src.plugin_system.apis import chat_api 11 | ``` 12 | 13 | 一种**Deprecated**方式: 14 | ```python 15 | from src.plugin_system.apis.chat_api import ChatManager 16 | ``` 17 | 18 | ## 主要功能 19 | 20 | ### 1. 获取所有的聊天流 21 | 22 | ```python 23 | def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: 24 | ``` 25 | 26 | **Args**: 27 | - `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的聊天流。 28 | 29 | **Returns**: 30 | - `List[ChatStream]`:聊天流列表 31 | 32 | ### 2. 获取群聊聊天流 33 | 34 | ```python 35 | def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: 36 | ``` 37 | 38 | **Args**: 39 | - `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。 40 | 41 | **Returns**: 42 | - `List[ChatStream]`:群聊聊天流列表 43 | 44 | ### 3. 获取私聊聊天流 45 | 46 | ```python 47 | def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]: 48 | ``` 49 | 50 | **Args**: 51 | - `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。 52 | 53 | **Returns**: 54 | - `List[ChatStream]`:私聊聊天流列表 55 | 56 | ### 4. 根据群ID获取聊天流 57 | 58 | ```python 59 | def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]: 60 | ``` 61 | 62 | **Args**: 63 | - `group_id`:群聊ID 64 | - `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。 65 | 66 | **Returns**: 67 | - `Optional[ChatStream]`:聊天流对象,如果未找到返回None 68 | 69 | ### 5. 根据用户ID获取私聊流 70 | 71 | ```python 72 | def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]: 73 | ``` 74 | 75 | **Args**: 76 | - `user_id`:用户ID 77 | - `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。 78 | 79 | **Returns**: 80 | - `Optional[ChatStream]`:聊天流对象,如果未找到返回None 81 | 82 | ### 6. 获取聊天流类型 83 | 84 | ```python 85 | def get_stream_type(chat_stream: ChatStream) -> str: 86 | ``` 87 | 88 | **Args**: 89 | - `chat_stream`:聊天流对象 90 | 91 | **Returns**: 92 | - `str`:聊天流类型,可能的值包括`private`(私聊流),`group`(群聊流)以及`unknown`(未知类型)。 93 | 94 | ### 7. 获取聊天流信息 95 | 96 | ```python 97 | def get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]: 98 | ``` 99 | 100 | **Args**: 101 | - `chat_stream`:聊天流对象 102 | 103 | **Returns**: 104 | - `Dict[str, Any]`:聊天流的详细信息,包括但不限于: 105 | - `stream_id`:聊天流ID 106 | - `platform`:平台名称 107 | - `type`:聊天流类型 108 | - `group_id`:群聊ID 109 | - `group_name`:群聊名称 110 | - `user_id`:用户ID 111 | - `user_name`:用户名称 112 | 113 | ### 8. 获取聊天流统计摘要 114 | 115 | ```python 116 | def get_streams_summary() -> Dict[str, int]: 117 | ``` 118 | 119 | **Returns**: 120 | - `Dict[str, int]`:聊天流统计信息摘要,包含以下键: 121 | - `total_streams`:总聊天流数量 122 | - `group_streams`:群聊流数量 123 | - `private_streams`:私聊流数量 124 | - `qq_streams`:QQ平台流数量 125 | 126 | 127 | ## 注意事项 128 | 129 | 1. 大部分函数在参数不合法时候会抛出异常,请确保你的程序进行了捕获。 130 | 2. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等。 -------------------------------------------------------------------------------- /docs/plugins/api/config-api.md: -------------------------------------------------------------------------------- 1 | # 配置API 2 | 3 | 配置API模块提供了配置读取功能,让插件能够安全地访问全局配置和插件配置。 4 | 5 | ## 导入方式 6 | 7 | ```python 8 | from src.plugin_system.apis import config_api 9 | # 或者 10 | from src.plugin_system import config_api 11 | ``` 12 | 13 | ## 主要功能 14 | 15 | ### 1. 访问全局配置 16 | 17 | ```python 18 | def get_global_config(key: str, default: Any = None) -> Any: 19 | ``` 20 | 21 | **Args**: 22 | - `key`: 命名空间式配置键名,使用嵌套访问,如 "section.subsection.key",大小写敏感 23 | - `default`: 如果配置不存在时返回的默认值 24 | 25 | **Returns**: 26 | - `Any`: 配置值或默认值 27 | 28 | #### 示例: 29 | 获取机器人昵称 30 | ```python 31 | bot_name = config_api.get_global_config("bot.nickname", "MaiBot") 32 | ``` 33 | 34 | ### 2. 获取插件配置 35 | 36 | ```python 37 | def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any: 38 | ``` 39 | **Args**: 40 | - `plugin_config`: 插件配置字典 41 | - `key`: 配置键名,支持嵌套访问如 "section.subsection.key",大小写敏感 42 | - `default`: 如果配置不存在时返回的默认值 43 | 44 | **Returns**: 45 | - `Any`: 配置值或默认值 46 | 47 | ## 注意事项 48 | 49 | 1. **只读访问**:配置API只提供读取功能,插件不能修改全局配置 50 | 2. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值 51 | 3. **安全性**:插件通过此API访问配置是安全和隔离的 52 | 4. **性能**:频繁访问的配置建议在插件初始化时获取并缓存 -------------------------------------------------------------------------------- /docs/plugins/api/llm-api.md: -------------------------------------------------------------------------------- 1 | # LLM API 2 | 3 | LLM API模块提供与大语言模型交互的功能,让插件能够使用系统配置的LLM模型进行内容生成。 4 | 5 | ## 导入方式 6 | 7 | ```python 8 | from src.plugin_system.apis import llm_api 9 | # 或者 10 | from src.plugin_system import llm_api 11 | ``` 12 | 13 | ## 主要功能 14 | 15 | ### 1. 查询可用模型 16 | ```python 17 | def get_available_models() -> Dict[str, TaskConfig]: 18 | ``` 19 | 获取所有可用的模型配置。 20 | 21 | **Return:** 22 | - `Dict[str, TaskConfig]`:模型配置字典,key为模型名称,value为模型配置对象。 23 | 24 | ### 2. 使用模型生成内容 25 | ```python 26 | async def generate_with_model( 27 | prompt: str, 28 | model_config: TaskConfig, 29 | request_type: str = "plugin.generate", 30 | temperature: Optional[float] = None, 31 | max_tokens: Optional[int] = None, 32 | ) -> Tuple[bool, str, str, str]: 33 | ``` 34 | 使用指定模型生成内容。 35 | 36 | **Args:** 37 | - `prompt`:提示词。 38 | - `model_config`:模型配置对象(从 `get_available_models` 获取)。 39 | - `request_type`:请求类型标识,默认为 `"plugin.generate"`。 40 | - `temperature`:生成内容的温度设置,影响输出的随机性。 41 | - `max_tokens`:生成内容的最大token数。 42 | 43 | **Return:** 44 | - `Tuple[bool, str, str, str]`:返回一个元组,包含(是否成功, 生成的内容, 推理过程, 模型名称)。 45 | 46 | ### 3. 有Tool情况下使用模型生成内容 47 | ```python 48 | async def generate_with_model_with_tools( 49 | prompt: str, 50 | model_config: TaskConfig, 51 | tool_options: List[Dict[str, Any]] | None = None, 52 | request_type: str = "plugin.generate", 53 | temperature: Optional[float] = None, 54 | max_tokens: Optional[int] = None, 55 | ) -> Tuple[bool, str, str, str, List[ToolCall] | None]: 56 | ``` 57 | 使用指定模型生成内容,并支持工具调用。 58 | 59 | **Args:** 60 | - `prompt`:提示词。 61 | - `model_config`:模型配置对象(从 `get_available_models` 获取)。 62 | - `tool_options`:工具选项列表,包含可用工具的配置,字典为每一个工具的定义,参见[tool-components.md](../tool-components.md#属性说明),可用`tool_api.get_llm_available_tool_definitions()`获取并选择。 63 | - `request_type`:请求类型标识,默认为 `"plugin.generate"`。 64 | - `temperature`:生成内容的温度设置,影响输出的随机性。 65 | - `max_tokens`:生成内容的最大token数。 -------------------------------------------------------------------------------- /docs/plugins/api/logging-api.md: -------------------------------------------------------------------------------- 1 | # Logging API 2 | 3 | Logging API模块提供了获取本体logger的功能,允许插件记录日志信息。 4 | 5 | ## 导入方式 6 | 7 | ```python 8 | from src.plugin_system.apis import get_logger 9 | # 或者 10 | from src.plugin_system import get_logger 11 | ``` 12 | 13 | ## 主要功能 14 | ### 1. 获取本体logger 15 | ```python 16 | def get_logger(name: str) -> structlog.stdlib.BoundLogger: 17 | ``` 18 | 获取本体logger实例。 19 | 20 | **Args:** 21 | - `name` (str): 日志记录器的名称。 22 | 23 | **Returns:** 24 | - 一个logger实例,有以下方法: 25 | - `debug` 26 | - `info` 27 | - `warning` 28 | - `error` 29 | - `critical` -------------------------------------------------------------------------------- /docs/plugins/api/person-api.md: -------------------------------------------------------------------------------- 1 | # 个人信息API 2 | 3 | 个人信息API模块提供用户信息查询和管理功能,让插件能够获取和使用用户的相关信息。 4 | 5 | ## 导入方式 6 | 7 | ```python 8 | from src.plugin_system.apis import person_api 9 | # 或者 10 | from src.plugin_system import person_api 11 | ``` 12 | 13 | ## 主要功能 14 | 15 | ### 1. Person ID 获取 16 | ```python 17 | def get_person_id(platform: str, user_id: int) -> str: 18 | ``` 19 | 根据平台和用户ID获取person_id 20 | 21 | **Args:** 22 | - `platform`:平台名称,如 "qq", "telegram" 等 23 | - `user_id`:用户ID 24 | 25 | **Returns:** 26 | - `str`:唯一的person_id(MD5哈希值) 27 | 28 | #### 示例 29 | ```python 30 | person_id = person_api.get_person_id("qq", 123456) 31 | ``` 32 | 33 | ### 2. 用户信息查询 34 | ```python 35 | async def get_person_value(person_id: str, field_name: str, default: Any = None) -> Any: 36 | ``` 37 | 查询单个用户信息字段值 38 | 39 | **Args:** 40 | - `person_id`:用户的唯一标识ID 41 | - `field_name`:要获取的字段名 42 | - `default`:字段值不存在时的默认值 43 | 44 | **Returns:** 45 | - `Any`:字段值或默认值 46 | 47 | #### 示例 48 | ```python 49 | nickname = await person_api.get_person_value(person_id, "nickname", "未知用户") 50 | impression = await person_api.get_person_value(person_id, "impression") 51 | ``` 52 | 53 | ### 3. 批量用户信息查询 54 | ```python 55 | async def get_person_values(person_id: str, field_names: list, default_dict: Optional[dict] = None) -> dict: 56 | ``` 57 | 批量获取用户信息字段值 58 | 59 | **Args:** 60 | - `person_id`:用户的唯一标识ID 61 | - `field_names`:要获取的字段名列表 62 | - `default_dict`:默认值字典,键为字段名,值为默认值 63 | 64 | **Returns:** 65 | - `dict`:字段名到值的映射字典 66 | 67 | #### 示例 68 | ```python 69 | values = await person_api.get_person_values( 70 | person_id, 71 | ["nickname", "impression", "know_times"], 72 | {"nickname": "未知用户", "know_times": 0} 73 | ) 74 | ``` 75 | 76 | ### 4. 判断用户是否已知 77 | ```python 78 | async def is_person_known(platform: str, user_id: int) -> bool: 79 | ``` 80 | 判断是否认识某个用户 81 | 82 | **Args:** 83 | - `platform`:平台名称 84 | - `user_id`:用户ID 85 | 86 | **Returns:** 87 | - `bool`:是否认识该用户 88 | 89 | ### 5. 根据用户名获取Person ID 90 | ```python 91 | def get_person_id_by_name(person_name: str) -> str: 92 | ``` 93 | 根据用户名获取person_id 94 | 95 | **Args:** 96 | - `person_name`:用户名 97 | 98 | **Returns:** 99 | - `str`:person_id,如果未找到返回空字符串 100 | 101 | ## 常用字段说明 102 | 103 | ### 基础信息字段 104 | - `nickname`:用户昵称 105 | - `platform`:平台信息 106 | - `user_id`:用户ID 107 | 108 | ### 关系信息字段 109 | - `impression`:对用户的印象 110 | - `points`: 用户特征点 111 | 112 | 其他字段可以参考`PersonInfo`类的属性(位于`src.common.database.database_model`) 113 | 114 | ## 注意事项 115 | 116 | 1. **异步操作**:部分查询函数都是异步的,需要使用`await` 117 | 2. **性能考虑**:批量查询优于单个查询 118 | 3. **隐私保护**:确保用户信息的使用符合隐私政策 119 | 4. **数据一致性**:person_id是用户的唯一标识,应妥善保存和使用 -------------------------------------------------------------------------------- /docs/plugins/api/plugin-manage-api.md: -------------------------------------------------------------------------------- 1 | # 插件管理API 2 | 3 | 插件管理API模块提供了对插件的加载、卸载、重新加载以及目录管理功能。 4 | 5 | ## 导入方式 6 | ```python 7 | from src.plugin_system.apis import plugin_manage_api 8 | # 或者 9 | from src.plugin_system import plugin_manage_api 10 | ``` 11 | 12 | ## 功能概述 13 | 14 | 插件管理API主要提供以下功能: 15 | - **插件查询** - 列出当前加载的插件或已注册的插件。 16 | - **插件管理** - 加载、卸载、重新加载插件。 17 | - **插件目录管理** - 添加插件目录并重新扫描。 18 | 19 | ## 主要功能 20 | 21 | ### 1. 列出当前加载的插件 22 | ```python 23 | def list_loaded_plugins() -> List[str]: 24 | ``` 25 | 列出所有当前加载的插件。 26 | 27 | **Returns:** 28 | - `List[str]` - 当前加载的插件名称列表。 29 | 30 | ### 2. 列出所有已注册的插件 31 | ```python 32 | def list_registered_plugins() -> List[str]: 33 | ``` 34 | 列出所有已注册的插件。 35 | 36 | **Returns:** 37 | - `List[str]` - 已注册的插件名称列表。 38 | 39 | ### 3. 获取插件路径 40 | ```python 41 | def get_plugin_path(plugin_name: str) -> str: 42 | ``` 43 | 获取指定插件的路径。 44 | 45 | **Args:** 46 | - `plugin_name` (str): 要查询的插件名称。 47 | **Returns:** 48 | - `str` - 插件的路径,如果插件不存在则 raise ValueError。 49 | 50 | ### 4. 卸载指定的插件 51 | ```python 52 | async def remove_plugin(plugin_name: str) -> bool: 53 | ``` 54 | 卸载指定的插件。 55 | 56 | **Args:** 57 | - `plugin_name` (str): 要卸载的插件名称。 58 | 59 | **Returns:** 60 | - `bool` - 卸载是否成功。 61 | 62 | ### 5. 重新加载指定的插件 63 | ```python 64 | async def reload_plugin(plugin_name: str) -> bool: 65 | ``` 66 | 重新加载指定的插件。 67 | 68 | **Args:** 69 | - `plugin_name` (str): 要重新加载的插件名称。 70 | 71 | **Returns:** 72 | - `bool` - 重新加载是否成功。 73 | 74 | ### 6. 加载指定的插件 75 | ```python 76 | def load_plugin(plugin_name: str) -> Tuple[bool, int]: 77 | ``` 78 | 加载指定的插件。 79 | 80 | **Args:** 81 | - `plugin_name` (str): 要加载的插件名称。 82 | 83 | **Returns:** 84 | - `Tuple[bool, int]` - 加载是否成功,成功或失败的个数。 85 | 86 | ### 7. 添加插件目录 87 | ```python 88 | def add_plugin_directory(plugin_directory: str) -> bool: 89 | ``` 90 | 添加插件目录。 91 | 92 | **Args:** 93 | - `plugin_directory` (str): 要添加的插件目录路径。 94 | 95 | **Returns:** 96 | - `bool` - 添加是否成功。 97 | 98 | ### 8. 重新扫描插件目录 99 | ```python 100 | def rescan_plugin_directory() -> Tuple[int, int]: 101 | ``` 102 | 重新扫描插件目录,加载新插件。 103 | 104 | **Returns:** 105 | - `Tuple[int, int]` - 成功加载的插件数量和失败的插件数量。 -------------------------------------------------------------------------------- /docs/plugins/api/tool-api.md: -------------------------------------------------------------------------------- 1 | # 工具API 2 | 3 | 工具API模块提供了获取和管理工具实例的功能,让插件能够访问系统中注册的工具。 4 | 5 | ## 导入方式 6 | 7 | ```python 8 | from src.plugin_system.apis import tool_api 9 | # 或者 10 | from src.plugin_system import tool_api 11 | ``` 12 | 13 | ## 主要功能 14 | 15 | ### 1. 获取工具实例 16 | 17 | ```python 18 | def get_tool_instance(tool_name: str) -> Optional[BaseTool]: 19 | ``` 20 | 21 | 获取指定名称的工具实例。 22 | 23 | **Args**: 24 | - `tool_name`: 工具名称字符串 25 | 26 | **Returns**: 27 | - `Optional[BaseTool]`: 工具实例,如果工具不存在则返回 None 28 | 29 | ### 2. 获取LLM可用的工具定义 30 | 31 | ```python 32 | def get_llm_available_tool_definitions(): 33 | ``` 34 | 35 | 获取所有LLM可用的工具定义列表。 36 | 37 | **Returns**: 38 | - `List[Tuple[str, Dict[str, Any]]]`: 工具定义列表,每个元素为 `(工具名称, 工具定义字典)` 的元组 39 | - 其具体定义请参照[tool-components.md](../tool-components.md#属性说明)中的工具定义格式。 40 | #### 示例: 41 | 42 | ```python 43 | # 获取所有LLM可用的工具定义 44 | tools = tool_api.get_llm_available_tool_definitions() 45 | for tool_name, tool_definition in tools: 46 | print(f"工具: {tool_name}") 47 | print(f"定义: {tool_definition}") 48 | ``` 49 | 50 | ## 注意事项 51 | 52 | 1. **工具存在性检查**:使用前请检查工具实例是否为 None 53 | 2. **权限控制**:某些工具可能有使用权限限制 54 | 3. **异步调用**:大多数工具方法是异步的,需要使用 await 55 | 4. **错误处理**:调用工具时请做好异常处理 56 | -------------------------------------------------------------------------------- /docs/plugins/index.md: -------------------------------------------------------------------------------- 1 | # MoFox_Bot插件开发文档 2 | 3 | > 欢迎来到MoFox_Bot插件系统开发文档!这里是你开始插件开发旅程的最佳起点。 4 | 5 | ## 新手入门 6 | 7 | - [📖 快速开始指南](quick-start.md) - 快速创建你的第一个插件 8 | 9 | ## 组件功能详解 10 | 11 | - [🧱 Action组件详解](action-components.md) - 掌握最核心的Action组件 12 | - [💻 Command组件详解](PLUS_COMMAND_GUIDE.md) - 学习直接响应命令的组件 13 | - [🔧 Tool组件详解](tool-components.md) - 了解如何扩展信息获取能力 14 | - [⚙️ 配置文件系统指南](configuration-guide.md) - 学会使用自动生成的插件配置文件 15 | - [📄 Manifest系统指南](manifest-guide.md) - 了解插件元数据管理和配置架构 16 | 17 | Command vs Action 选择指南 18 | 19 | 1. 使用Command的场景 20 | 21 | - ✅ 用户需要明确调用特定功能 22 | - ✅ 需要精确的参数控制 23 | - ✅ 管理和配置操作 24 | - ✅ 查询和信息显示 25 | - ✅ 系统维护命令 26 | 27 | 2. 使用Action的场景 28 | 29 | - ✅ 增强麦麦的智能行为 30 | - ✅ 根据上下文自动触发 31 | - ✅ 情绪和表情表达 32 | - ✅ 智能建议和帮助 33 | - ✅ 随机化的互动 34 | 35 | 36 | ## API浏览 37 | 38 | ### 消息发送与处理API 39 | - [📤 发送API](api/send-api.md) - 各种类型消息发送接口 40 | - [消息API](api/message-api.md) - 消息获取,消息构建,消息查询接口 41 | - [聊天流API](api/chat-api.md) - 聊天流管理和查询接口 42 | 43 | ### AI与生成API 44 | - [LLM API](api/llm-api.md) - 大语言模型交互接口,可以使用内置LLM生成内容 45 | - [✨ 回复生成器API](api/generator-api.md) - 智能回复生成接口,可以使用内置风格化生成器 46 | 47 | ### 表情包API 48 | - [😊 表情包API](api/emoji-api.md) - 表情包选择和管理接口 49 | 50 | ### 关系系统API 51 | - [人物信息API](api/person-api.md) - 用户信息,处理麦麦认识的人和关系的接口 52 | 53 | ### 数据与配置API 54 | - [🗄️ 数据库API](api/database-api.md) - 数据库操作接口 55 | - [⚙️ 配置API](api/config-api.md) - 配置读取和用户信息接口 56 | 57 | ### 插件和组件管理API 58 | - [🔌 插件API](api/plugin-manage-api.md) - 插件加载和管理接口 59 | - [🧩 组件API](api/component-manage-api.md) - 组件注册和管理接口 60 | 61 | ### 日志API 62 | - [📜 日志API](api/logging-api.md) - logger实例获取接口 63 | ### 工具API 64 | - [🔧 工具API](api/tool-api.md) - tool获取接口 65 | 66 | 67 | 68 | ## 支持 69 | 70 | > 如果你在文档中发现错误或需要补充,请: 71 | 72 | 1. 检查最新的文档版本 73 | 2. 查看相关示例代码 74 | 3. 参考其他类似插件 75 | 4. 提交文档仓库issue 76 | 77 | ## 一个方便的小设计 78 | 79 | 我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。 80 | 这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。 81 | 或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。 -------------------------------------------------------------------------------- /plugins/bilibli/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="Bilibili Plugin", 5 | description="A plugin for interacting with Bilibili.", 6 | usage="Usage details for Bilibili plugin.", 7 | version="1.0.0", 8 | author="Your Name", 9 | license="MIT", 10 | ) 11 | -------------------------------------------------------------------------------- /plugins/bilibli/_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 1, 3 | "name": "哔哩哔哩视频解析插件 (Bilibili Video Parser)", 4 | "version": "1.0.0", 5 | "description": "解析哔哩哔哩视频链接,获取视频的base64编码数据(无音频版本),支持BV号、AV号和短链接", 6 | "author": { 7 | "name": "雅诺狐" 8 | }, 9 | "license": "GPL-v3.0-or-later", 10 | "host_application": { 11 | "min_version": "0.8.0" 12 | }, 13 | "keywords": [ 14 | "bilibili", 15 | "video", 16 | "parser", 17 | "tool", 18 | "媒体处理" 19 | ], 20 | "categories": [ 21 | "Media", 22 | "Tools" 23 | ], 24 | "default_locale": "zh-CN", 25 | "plugin_info": { 26 | "is_built_in": false, 27 | "plugin_type": "tool", 28 | "components": [ 29 | { 30 | "type": "tool", 31 | "name": "bilibili_video_parser", 32 | "description": "解析哔哩哔哩视频链接,获取视频的base64编码数据(无音频版本)" 33 | } 34 | ], 35 | "features": [ 36 | "支持BV号、AV号视频链接解析", 37 | "支持b23.tv短链接解析", 38 | "返回无音频的视频base64编码", 39 | "自动处理重定向链接", 40 | "详细的解析状态反馈" 41 | ] 42 | } 43 | } -------------------------------------------------------------------------------- /plugins/hello_world_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="Hello World Plugin", 5 | description="A simple hello world plugin.", 6 | usage="!hello", 7 | version="1.0.0", 8 | author="Your Name", 9 | license="MIT", 10 | ) 11 | -------------------------------------------------------------------------------- /plugins/hello_world_plugin/_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 1, 3 | "name": "Hello World 插件", 4 | "version": "1.0.1", 5 | "description": "一个包含四大核心组件和高级配置功能的入门示例插件。", 6 | "author": { 7 | "name": "Kilo Code" 8 | }, 9 | "license": "MIT", 10 | "keywords": [ 11 | "example", 12 | "tutorial", 13 | "hello world" 14 | ], 15 | "categories": [ 16 | "official", 17 | "example" 18 | ] 19 | } -------------------------------------------------------------------------------- /plugins/set_emoji_like/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/plugins/set_emoji_like/__init__.py -------------------------------------------------------------------------------- /plugins/set_emoji_like/_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 1, 3 | "name": "消息表情回应 (Set Message Emoji Like)", 4 | "version": "1.0.0", 5 | "description": "通过大模型判断或指令,为特定的聊天消息设置QQ表情回应。需要NapCat服务支持。", 6 | "author": { 7 | "name": "MoFox-Studio", 8 | "url": "https://github.com/MoFox-Studio" 9 | }, 10 | "license": "GPL-v3.0-or-later", 11 | 12 | "host_application": { 13 | "min_version": "0.10.0" 14 | }, 15 | "keywords": ["emoji", "reaction", "like", "表情", "回应", "点赞"], 16 | "categories": ["Chat", "Integration"], 17 | 18 | "default_locale": "zh-CN", 19 | "locales_path": "_locales", 20 | 21 | "plugin_info": { 22 | "is_built_in": false, 23 | "plugin_type": "functional", 24 | "components": [ 25 | { 26 | "type": "action", 27 | "name": "set_emoji_like", 28 | "description": "为某条已经存在的消息添加‘贴表情’回应(类似点赞),而不是发送新消息。当用户明确要求‘贴表情’时使用。" 29 | } 30 | ], 31 | "features": [ 32 | "通过LLM理解并为消息添加表情回应", 33 | "支持通过表情名称或ID指定表情" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/microsoft/pyright/main/packages/vscode-pyright/schemas/pyrightconfig.schema.json", 3 | "include": [ 4 | "src", 5 | "bot.py" 6 | ], 7 | "exclude": [ 8 | "**/__pycache__", 9 | "data", 10 | "logs", 11 | "tests", 12 | "target", 13 | "*.egg-info", 14 | "src/plugins/built_in/*", 15 | "__main__.py" 16 | ], 17 | "typeCheckingMode": "standard", 18 | "reportMissingImports": false, 19 | "reportMissingTypeStubs": false, 20 | "reportMissingModuleSource": false, 21 | "diagnosticSeverityOverrides": { 22 | "reportMissingImports": "none", 23 | "reportMissingTypeStubs": "none", 24 | "reportMissingModuleSource": "none" 25 | }, 26 | "pythonVersion": "3.12", 27 | "venvPath": ".", 28 | "venv": ".venv", 29 | "executionEnvironments": [ 30 | {"root": "src"} 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | sqlalchemy 2 | aiosqlite 3 | aiomysql 4 | APScheduler 5 | aiohttp 6 | aiohttp-cors 7 | colorama 8 | customtkinter 9 | dotenv 10 | faiss-cpu 11 | fastapi 12 | rjieba 13 | jsonlines 14 | maim_message 15 | quick_algo 16 | matplotlib 17 | networkx 18 | numpy 19 | openai 20 | google-genai 21 | pandas 22 | peewee 23 | pyarrow 24 | pydantic 25 | pypinyin 26 | python-dateutil 27 | python-dotenv 28 | python-igraph 29 | pymongo 30 | requests 31 | ruff 32 | scipy 33 | setuptools 34 | toml 35 | tomli 36 | tomli_w 37 | tomlkit 38 | tqdm 39 | urllib3 40 | uvicorn 41 | websockets 42 | strawberry-graphql[fastapi] 43 | httpx[socks] 44 | packaging 45 | rich 46 | psutil 47 | cryptography 48 | json-repair 49 | reportportal-client 50 | scikit-learn 51 | seaborn 52 | structlog 53 | httpx 54 | requests 55 | beautifulsoup4 56 | lxml 57 | json5 58 | toml 59 | beautifulsoup4 60 | exa_py 61 | asyncddgs 62 | opencv-python 63 | Pillow 64 | chromadb 65 | asyncio 66 | tavily-python 67 | google-generativeai 68 | lunar_python 69 | fuzzywuzzy 70 | python-multipart 71 | aiofiles 72 | inkfox 73 | mcp 74 | sse-starlette -------------------------------------------------------------------------------- /scripts/run_lpmm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ============================================== 4 | # Environment Initialization 5 | # ============================================== 6 | 7 | # Step 1: Locate project root directory 8 | SCRIPTS_DIR="scripts" 9 | SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) 10 | PROJECT_ROOT=$(cd "$SCRIPT_DIR/.." && pwd) 11 | 12 | # Step 2: Verify scripts directory exists 13 | if [ ! -d "$PROJECT_ROOT/$SCRIPTS_DIR" ]; then 14 | echo "❌ Error: scripts directory not found in project root" >&2 15 | echo "Current path: $PROJECT_ROOT" >&2 16 | exit 1 17 | fi 18 | 19 | # Step 3: Set up Python environment 20 | export PYTHONPATH="$PROJECT_ROOT:$PYTHONPATH" 21 | cd "$PROJECT_ROOT" || { 22 | echo "❌ Failed to cd to project root: $PROJECT_ROOT" >&2 23 | exit 1 24 | } 25 | 26 | # Debug info 27 | echo "============================" 28 | echo "Project Root: $PROJECT_ROOT" 29 | echo "Python Path: $PYTHONPATH" 30 | echo "Working Dir: $(pwd)" 31 | echo "============================" 32 | 33 | # ============================================== 34 | # Python Script Execution 35 | # ============================================== 36 | 37 | run_python_script() { 38 | local script_name=$1 39 | echo "🔄 Running $script_name" 40 | if ! python3 "$SCRIPTS_DIR/$script_name"; then 41 | echo "❌ $script_name failed" >&2 42 | exit 1 43 | fi 44 | } 45 | 46 | # Execute scripts in order 47 | run_python_script "raw_data_preprocessor.py" 48 | run_python_script "info_extraction.py" 49 | run_python_script "import_openie.py" 50 | 51 | echo "✅ All scripts completed successfully" -------------------------------------------------------------------------------- /scripts/run_multi_stage_smoke.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | 轻量烟雾测试:初始化 MemorySystem 并运行一次检索,验证 MemoryMetadata.source 访问不再报错 4 | """ 5 | 6 | import asyncio 7 | import os 8 | import sys 9 | 10 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 11 | 12 | from src.chat.memory_system.memory_system import MemorySystem 13 | 14 | 15 | async def main(): 16 | ms = MemorySystem() 17 | await ms.initialize() 18 | results = await ms.retrieve_relevant_memories(query_text="测试查询:杰瑞喵喜欢什么?", limit=3) 19 | print(f"检索到 {len(results)} 条记忆(如果 >0 则表明运行成功)") 20 | for i, m in enumerate(results, 1): 21 | print(f"{i}. id={m.metadata.memory_id} source={getattr(m.metadata, 'source', None)}") 22 | 23 | 24 | if __name__ == "__main__": 25 | asyncio.run(main()) 26 | -------------------------------------------------------------------------------- /scripts/update_prompt_imports.py: -------------------------------------------------------------------------------- 1 | """ 2 | 更新Prompt类导入脚本 3 | 将旧的prompt_builder.Prompt导入更新为unified_prompt.Prompt 4 | """ 5 | 6 | import os 7 | 8 | # 需要更新的文件列表 9 | files_to_update = [ 10 | "src/person_info/relationship_fetcher.py", 11 | "src/mood/mood_manager.py", 12 | "src/mais4u/mais4u_chat/body_emotion_action_manager.py", 13 | "src/chat/express/expression_learner.py", 14 | "src/chat/planner_actions/planner.py", 15 | "src/mais4u/mais4u_chat/s4u_prompt.py", 16 | "src/chat/message_receive/bot.py", 17 | "src/chat/replyer/default_generator.py", 18 | "src/chat/express/expression_selector.py", 19 | "src/mais4u/mai_think.py", 20 | "src/mais4u/mais4u_chat/s4u_mood_manager.py", 21 | "src/plugin_system/core/tool_use.py", 22 | "src/chat/memory_system/memory_activator.py", 23 | "src/chat/utils/smart_prompt.py", 24 | ] 25 | 26 | 27 | def update_prompt_imports(file_path): 28 | """更新文件中的Prompt导入""" 29 | if not os.path.exists(file_path): 30 | print(f"文件不存在: {file_path}") 31 | return False 32 | 33 | with open(file_path, encoding="utf-8") as f: 34 | content = f.read() 35 | 36 | # 替换导入语句 37 | old_import = "from src.chat.utils.prompt_builder import Prompt, global_prompt_manager" 38 | new_import = "from src.chat.utils.prompt import Prompt, global_prompt_manager" 39 | 40 | if old_import in content: 41 | new_content = content.replace(old_import, new_import) 42 | with open(file_path, "w", encoding="utf-8") as f: 43 | f.write(new_content) 44 | print(f"已更新: {file_path}") 45 | return True 46 | else: 47 | print(f"无需更新: {file_path}") 48 | return False 49 | 50 | 51 | def main(): 52 | """主函数""" 53 | updated_count = 0 54 | for file_path in files_to_update: 55 | if update_prompt_imports(file_path): 56 | updated_count += 1 57 | 58 | print(f"\n更新完成!共更新了 {updated_count} 个文件") 59 | 60 | 61 | if __name__ == "__main__": 62 | main() 63 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import random 2 | from collections.abc import Sequence 3 | from typing import List, Optional 4 | 5 | from colorama import Fore, init 6 | 7 | from src.common.logger import get_logger 8 | 9 | egg = get_logger("小彩蛋") 10 | 11 | 12 | def weighted_choice(data: Sequence[str], weights: list[float] | None = None) -> str: 13 | """ 14 | 从 data 中按权重随机返回一条。 15 | 若 weights 为 None,则所有元素权重默认为 1。 16 | """ 17 | if weights is None: 18 | weights = [1.0] * len(data) 19 | 20 | if len(data) != len(weights): 21 | raise ValueError("data 和 weights 长度必须相等") 22 | 23 | # 计算累计权重区间 24 | total = 0.0 25 | acc = [] 26 | for w in weights: 27 | total += w 28 | acc.append(total) 29 | 30 | if total <= 0: 31 | raise ValueError("总权重必须大于 0") 32 | 33 | # 随机落点 34 | r = random.random() * total 35 | # 二分查找落点所在的区间 36 | left, right = 0, len(acc) - 1 37 | while left < right: 38 | mid = (left + right) // 2 39 | if r < acc[mid]: 40 | right = mid 41 | else: 42 | left = mid + 1 43 | return data[left] 44 | 45 | 46 | class BaseMain: 47 | """基础主程序类""" 48 | 49 | def __init__(self): 50 | """初始化基础主程序""" 51 | self.easter_egg() 52 | 53 | @staticmethod 54 | def easter_egg(): 55 | # 彩蛋 56 | init() 57 | items = [ 58 | "多年以后,面对AI行刑队,张三将会回想起他2023年在会议上讨论人工智能的那个下午", 59 | "你知道吗?诺狐的耳朵很软,很好rua", 60 | "喵喵~你的麦麦被猫娘入侵了喵~", 61 | ] 62 | w = [10, 5, 2] 63 | text = weighted_choice(items, w) 64 | rainbow_colors = [Fore.RED, Fore.YELLOW, Fore.GREEN, Fore.CYAN, Fore.BLUE, Fore.MAGENTA] 65 | rainbow_text = "" 66 | for i, char in enumerate(text): 67 | rainbow_text += rainbow_colors[i % len(rainbow_colors)] + char 68 | egg.info(rainbow_text) 69 | -------------------------------------------------------------------------------- /src/api/__init__.py: -------------------------------------------------------------------------------- 1 | # This file makes src/api a Python package. 2 | -------------------------------------------------------------------------------- /src/chat/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MaiBot模块系统 3 | 包含聊天、情绪、记忆、日程等功能模块 4 | """ 5 | 6 | from src.chat.emoji_system.emoji_manager import get_emoji_manager 7 | from src.chat.message_receive.chat_stream import get_chat_manager 8 | 9 | # 导出主要组件供外部使用 10 | __all__ = [ 11 | "get_chat_manager", 12 | "get_emoji_manager", 13 | ] 14 | -------------------------------------------------------------------------------- /src/chat/antipromptinjector/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MaiBot 反注入系统模块 3 | 4 | 本模块提供了一个完整的LLM反注入检测和防护系统,用于防止恶意的提示词注入攻击。 5 | 6 | 主要功能: 7 | 1. 基于规则的快速检测 8 | 2. 黑白名单机制 9 | 3. LLM二次分析 10 | 4. 消息处理模式(严格模式/宽松模式/反击模式) 11 | 12 | 作者: FOX YaNuo 13 | """ 14 | 15 | from .anti_injector import AntiPromptInjector, get_anti_injector, initialize_anti_injector 16 | from .core import MessageShield, PromptInjectionDetector 17 | from .decision import CounterAttackGenerator, ProcessingDecisionMaker 18 | from .management import AntiInjectionStatistics, UserBanManager 19 | from .processors.message_processor import MessageProcessor 20 | from .types import DetectionResult, ProcessResult 21 | 22 | __all__ = [ 23 | "AntiInjectionStatistics", 24 | "AntiPromptInjector", 25 | "CounterAttackGenerator", 26 | "DetectionResult", 27 | "MessageProcessor", 28 | "MessageShield", 29 | "ProcessResult", 30 | "ProcessingDecisionMaker", 31 | "PromptInjectionDetector", 32 | "UserBanManager", 33 | "get_anti_injector", 34 | "initialize_anti_injector", 35 | ] 36 | 37 | 38 | __author__ = "FOX YaNuo" 39 | -------------------------------------------------------------------------------- /src/chat/antipromptinjector/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 反注入系统核心检测模块 3 | 4 | 包含: 5 | - detector: 提示词注入检测器 6 | - shield: 消息防护盾 7 | """ 8 | 9 | from .detector import PromptInjectionDetector 10 | from .shield import MessageShield 11 | 12 | __all__ = ["MessageShield", "PromptInjectionDetector"] 13 | -------------------------------------------------------------------------------- /src/chat/antipromptinjector/decision/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 反注入系统决策模块 3 | 4 | 包含: 5 | - decision_maker: 处理决策制定器 6 | - counter_attack: 反击消息生成器 7 | """ 8 | 9 | from .counter_attack import CounterAttackGenerator 10 | from .decision_maker import ProcessingDecisionMaker 11 | 12 | __all__ = ["CounterAttackGenerator", "ProcessingDecisionMaker"] 13 | -------------------------------------------------------------------------------- /src/chat/antipromptinjector/management/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 反注入系统管理模块 3 | 4 | 包含: 5 | - statistics: 统计数据管理 6 | - user_ban: 用户封禁管理 7 | """ 8 | 9 | from .statistics import AntiInjectionStatistics 10 | from .user_ban import UserBanManager 11 | 12 | __all__ = ["AntiInjectionStatistics", "UserBanManager"] 13 | -------------------------------------------------------------------------------- /src/chat/antipromptinjector/processors/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 反注入系统消息处理模块 3 | 4 | 包含: 5 | - message_processor: 消息内容处理器 6 | """ 7 | 8 | from .message_processor import MessageProcessor 9 | 10 | __all__ = ["MessageProcessor"] 11 | -------------------------------------------------------------------------------- /src/chat/antipromptinjector/types.py: -------------------------------------------------------------------------------- 1 | """ 2 | 反注入系统数据类型定义模块 3 | 4 | 本模块定义了反注入系统使用的数据类型、枚举和数据结构: 5 | - ProcessResult: 处理结果枚举 6 | - DetectionResult: 检测结果数据类 7 | 8 | 实际的配置从 global_config.anti_prompt_injection 获取。 9 | """ 10 | 11 | import time 12 | from dataclasses import dataclass, field 13 | from enum import Enum 14 | 15 | 16 | class ProcessResult(Enum): 17 | """处理结果枚举""" 18 | 19 | ALLOWED = "allowed" # 允许通过 20 | BLOCKED_INJECTION = "blocked_injection" # 被阻止-注入攻击 21 | BLOCKED_BAN = "blocked_ban" # 被阻止-用户封禁 22 | SHIELDED = "shielded" # 已加盾处理 23 | COUNTER_ATTACK = "counter_attack" # 反击模式-使用LLM反击并丢弃消息 24 | 25 | 26 | @dataclass 27 | class DetectionResult: 28 | """检测结果类""" 29 | 30 | is_injection: bool = False 31 | confidence: float = 0.0 32 | matched_patterns: list[str] = field(default_factory=list) 33 | llm_analysis: str | None = None 34 | processing_time: float = 0.0 35 | detection_method: str = "unknown" 36 | reason: str = "" 37 | 38 | def __post_init__(self): 39 | """结果后处理""" 40 | self.timestamp = time.time() 41 | -------------------------------------------------------------------------------- /src/chat/emoji_system/emoji_history.py: -------------------------------------------------------------------------------- 1 | """ 2 | 表情包发送历史记录模块 3 | """ 4 | 5 | from collections import deque 6 | 7 | from src.common.logger import get_logger 8 | 9 | logger = get_logger("EmojiHistory") 10 | 11 | MAX_HISTORY_SIZE = 5 # 每个聊天会话最多保留最近5条表情历史 12 | 13 | # 使用一个全局字典在内存中存储历史记录 14 | # 键是 chat_id,值是一个 deque 对象 15 | _history_cache: dict[str, deque] = {} 16 | 17 | 18 | def add_emoji_to_history(chat_id: str, emoji_description: str): 19 | """ 20 | 将发送的表情包添加到内存历史记录中。 21 | 22 | :param chat_id: 聊天会话ID (例如 "private_12345" 或 "group_67890") 23 | :param emoji_description: 发送的表情包的描述 24 | """ 25 | if not chat_id or not emoji_description: 26 | return 27 | 28 | # 如果当前聊天还没有历史记录,则创建一个新的 deque 29 | if chat_id not in _history_cache: 30 | _history_cache[chat_id] = deque(maxlen=MAX_HISTORY_SIZE) 31 | 32 | # 添加新表情到历史记录 33 | history = _history_cache[chat_id] 34 | history.append(emoji_description) 35 | 36 | logger.debug(f"已将表情 '{emoji_description}' 添加到聊天 {chat_id} 的内存历史中") 37 | 38 | 39 | def get_recent_emojis(chat_id: str, limit: int = 5) -> list[str]: 40 | """ 41 | 从内存中获取最近发送的表情包描述列表。 42 | 43 | :param chat_id: 聊天会话ID 44 | :param limit: 获取的表情数量上限 45 | :return: 最近发送的表情包描述列表 46 | """ 47 | if not chat_id or chat_id not in _history_cache: 48 | return [] 49 | 50 | history = _history_cache[chat_id] 51 | 52 | # 从 deque 的右侧(即最近添加的)开始取 53 | num_to_get = min(limit, len(history)) 54 | recent_emojis = [history[-i] for i in range(1, num_to_get + 1)] 55 | 56 | logger.debug(f"为聊天 {chat_id} 从内存中获取到最近 {len(recent_emojis)} 个表情: {recent_emojis}") 57 | return recent_emojis 58 | -------------------------------------------------------------------------------- /src/chat/energy_system/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 能量系统模块 3 | 提供稳定、高效的聊天流能量计算和管理功能 4 | """ 5 | 6 | from .energy_manager import ( 7 | ActivityEnergyCalculator, 8 | EnergyCalculator, 9 | EnergyComponent, 10 | EnergyLevel, 11 | EnergyManager, 12 | InterestEnergyCalculator, 13 | RecencyEnergyCalculator, 14 | RelationshipEnergyCalculator, 15 | energy_manager, 16 | ) 17 | 18 | __all__ = [ 19 | "ActivityEnergyCalculator", 20 | "EnergyCalculator", 21 | "EnergyComponent", 22 | "EnergyLevel", 23 | "EnergyManager", 24 | "InterestEnergyCalculator", 25 | "RecencyEnergyCalculator", 26 | "RelationshipEnergyCalculator", 27 | "energy_manager", 28 | ] 29 | -------------------------------------------------------------------------------- /src/chat/frequency_analyzer/tracker.py: -------------------------------------------------------------------------------- 1 | import time 2 | from pathlib import Path 3 | 4 | import orjson 5 | 6 | from src.common.logger import get_logger 7 | 8 | # 数据存储路径 9 | DATA_DIR = Path("data/frequency_analyzer") 10 | DATA_DIR.mkdir(parents=True, exist_ok=True) 11 | TRACKER_FILE = DATA_DIR / "chat_timestamps.json" 12 | 13 | logger = get_logger("ChatFrequencyTracker") 14 | 15 | 16 | class ChatFrequencyTracker: 17 | """ 18 | 负责跟踪和存储用户聊天启动时间戳。 19 | """ 20 | 21 | def __init__(self): 22 | self._timestamps: dict[str, list[float]] = self._load_timestamps() 23 | 24 | @staticmethod 25 | def _load_timestamps() -> dict[str, list[float]]: 26 | """从本地文件加载时间戳数据。""" 27 | if not TRACKER_FILE.exists(): 28 | return {} 29 | try: 30 | with open(TRACKER_FILE, "rb") as f: 31 | data = orjson.loads(f.read()) 32 | logger.info(f"成功从 {TRACKER_FILE} 加载了聊天时间戳数据。") 33 | return data 34 | except orjson.JSONDecodeError: 35 | logger.warning(f"无法解析 {TRACKER_FILE},将创建一个新的空数据文件。") 36 | return {} 37 | except Exception as e: 38 | logger.error(f"加载聊天时间戳数据时发生未知错误: {e}") 39 | return {} 40 | 41 | def _save_timestamps(self): 42 | """将当前的时间戳数据保存到本地文件。""" 43 | try: 44 | with open(TRACKER_FILE, "wb") as f: 45 | f.write(orjson.dumps(self._timestamps)) 46 | except Exception as e: 47 | logger.error(f"保存聊天时间戳数据到 {TRACKER_FILE} 时失败: {e}") 48 | 49 | def record_chat_start(self, chat_id: str): 50 | """ 51 | 记录一次聊天会话的开始。 52 | 53 | Args: 54 | chat_id (str): 唯一的聊天标识符 (例如,用户ID)。 55 | """ 56 | now = time.time() 57 | if chat_id not in self._timestamps: 58 | self._timestamps[chat_id] = [] 59 | 60 | self._timestamps[chat_id].append(now) 61 | logger.debug(f"为 chat_id '{chat_id}' 记录了新的聊天时间: {now}") 62 | self._save_timestamps() 63 | 64 | def get_timestamps_for_chat(self, chat_id: str) -> list[float] | None: 65 | """ 66 | 获取指定聊天的所有时间戳记录。 67 | 68 | Args: 69 | chat_id (str): 聊天标识符。 70 | 71 | Returns: 72 | Optional[List[float]]: 时间戳列表,如果不存在则返回 None。 73 | """ 74 | return self._timestamps.get(chat_id) 75 | 76 | 77 | # 创建一个全局单例 78 | chat_frequency_tracker = ChatFrequencyTracker() 79 | -------------------------------------------------------------------------------- /src/chat/interest_system/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 兴趣度系统模块 3 | 提供机器人兴趣标签和智能匹配功能,以及消息兴趣值计算功能 4 | """ 5 | 6 | from src.common.data_models.bot_interest_data_model import BotInterestTag, BotPersonalityInterests, InterestMatchResult 7 | 8 | from .bot_interest_manager import BotInterestManager, bot_interest_manager 9 | from .interest_manager import InterestManager, get_interest_manager 10 | 11 | __all__ = [ 12 | # 机器人兴趣标签管理 13 | "BotInterestManager", 14 | "BotInterestTag", 15 | "BotPersonalityInterests", 16 | # 消息兴趣值计算管理 17 | "InterestManager", 18 | "InterestMatchResult", 19 | "bot_interest_manager", 20 | "get_interest_manager", 21 | ] 22 | -------------------------------------------------------------------------------- /src/chat/knowledge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/chat/knowledge/__init__.py -------------------------------------------------------------------------------- /src/chat/knowledge/global_logger.py: -------------------------------------------------------------------------------- 1 | # Configure logger 2 | 3 | from src.common.logger import get_logger 4 | 5 | logger = get_logger("lpmm") 6 | -------------------------------------------------------------------------------- /src/chat/knowledge/knowledge_lib.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from src.chat.knowledge.embedding_store import EmbeddingManager 4 | from src.chat.knowledge.global_logger import logger 5 | from src.chat.knowledge.kg_manager import KGManager 6 | from src.chat.knowledge.qa_manager import QAManager 7 | from src.config.config import global_config 8 | 9 | INVALID_ENTITY = [ 10 | "", 11 | "你", 12 | "他", 13 | "她", 14 | "它", 15 | "我们", 16 | "你们", 17 | "他们", 18 | "她们", 19 | "它们", 20 | ] 21 | 22 | RAG_GRAPH_NAMESPACE = "rag-graph" 23 | RAG_ENT_CNT_NAMESPACE = "rag-ent-cnt" 24 | RAG_PG_HASH_NAMESPACE = "rag-pg-hash" 25 | 26 | 27 | ROOT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) 28 | DATA_PATH = os.path.join(ROOT_PATH, "data") 29 | 30 | 31 | qa_manager = None 32 | inspire_manager = None 33 | 34 | 35 | def initialize_lpmm_knowledge(): 36 | """初始化LPMM知识库""" 37 | global qa_manager, inspire_manager 38 | 39 | # 检查LPMM知识库是否启用 40 | if global_config.lpmm_knowledge.enable: 41 | logger.info("正在初始化Mai-LPMM") 42 | logger.info("创建LLM客户端") 43 | 44 | # 初始化Embedding库 45 | embed_manager = EmbeddingManager() 46 | logger.info("正在从文件加载Embedding库") 47 | try: 48 | embed_manager.load_from_file() 49 | except Exception as e: 50 | logger.warning(f"此消息不会影响正常使用:从文件加载Embedding库时,{e}") 51 | # logger.warning("如果你是第一次导入知识,或者还未导入知识,请忽略此错误") 52 | logger.info("Embedding库加载完成") 53 | # 初始化KG 54 | kg_manager = KGManager() 55 | logger.info("正在从文件加载KG") 56 | try: 57 | kg_manager.load_from_file() 58 | except Exception as e: 59 | logger.warning(f"此消息不会影响正常使用:从文件加载KG时,{e}") 60 | # logger.warning("如果你是第一次导入知识,或者还未导入知识,请忽略此错误") 61 | logger.info("KG加载完成") 62 | 63 | logger.info(f"KG节点数量:{len(kg_manager.graph.get_node_list())}") 64 | logger.info(f"KG边数量:{len(kg_manager.graph.get_edge_list())}") 65 | 66 | # 数据比对:Embedding库与KG的段落hash集合 67 | for pg_hash in kg_manager.stored_paragraph_hashes: 68 | key = f"paragraph-{pg_hash}" 69 | if key not in embed_manager.stored_pg_hashes: 70 | logger.warning(f"KG中存在Embedding库中不存在的段落:{key}") 71 | 72 | # 问答系统(用于知识库) 73 | qa_manager = QAManager( 74 | embed_manager, 75 | kg_manager, 76 | ) 77 | 78 | # # 记忆激活(用于记忆库) 79 | # inspire_manager = MemoryActiveManager( 80 | # embed_manager, 81 | # llm_client_list[global_config["embedding"]["provider"]], 82 | # ) 83 | else: 84 | logger.info("LPMM知识库已禁用,跳过初始化") 85 | # 创建空的占位符对象,避免导入错误 86 | -------------------------------------------------------------------------------- /src/chat/knowledge/prompt_template.py: -------------------------------------------------------------------------------- 1 | entity_extract_system_prompt = """你是一个性能优异的实体提取系统。请从段落中提取出所有实体,并以JSON列表的形式输出。 2 | 3 | 输出格式示例: 4 | [ "实体A", "实体B", "实体C" ] 5 | 6 | 请注意以下要求: 7 | - 将代词(如“你”、“我”、“他”、“她”、“它”等)转化为对应的实体命名,以避免指代不清。 8 | - 尽可能多的提取出段落中的全部实体; 9 | """ 10 | 11 | 12 | def build_entity_extract_context(paragraph: str) -> str: 13 | """构建实体提取的完整提示文本""" 14 | return f"""{entity_extract_system_prompt} 15 | 16 | 段落: 17 | ``` 18 | {paragraph} 19 | ```""" 20 | 21 | 22 | rdf_triple_extract_system_prompt = """你是一个性能优异的RDF(资源描述框架,由节点和边组成,节点表示实体/资源、属性,边则表示了实体和实体之间的关系以及实体和属性的关系。)构造系统。你的任务是根据给定的段落和实体列表构建RDF图。 23 | 24 | 请使用JSON回复,使用三元组的JSON列表输出RDF图中的关系(每个三元组代表一个关系)。 25 | 26 | 输出格式示例: 27 | [ 28 | ["某实体","关系","某属性"], 29 | ["某实体","关系","某实体"], 30 | ["某资源","关系","某属性"] 31 | ] 32 | 33 | 请注意以下要求: 34 | - 每个三元组应包含每个段落的实体命名列表中的至少一个命名实体,但最好是两个。 35 | - 将代词(如“你”、“我”、“他”、“她”、“它”等)转化为对应的实体命名,以避免指代不清。 36 | """ 37 | 38 | 39 | def build_rdf_triple_extract_context(paragraph: str, entities: str) -> str: 40 | """构建RDF三元组提取的完整提示文本""" 41 | return f"""{rdf_triple_extract_system_prompt} 42 | 43 | 段落: 44 | ``` 45 | {paragraph} 46 | ``` 47 | 48 | 实体列表: 49 | ``` 50 | {entities} 51 | ```""" 52 | 53 | 54 | qa_system_prompt = """ 55 | 你是一个性能优异的QA系统。请根据给定的问题和一些可能对你有帮助的信息作出回答。 56 | 57 | 请注意以下要求: 58 | - 你可以使用给定的信息来回答问题,但请不要直接引用它们。 59 | - 你的回答应该简洁明了,避免冗长的解释。 60 | - 如果你无法回答问题,请直接说“我不知道”。 61 | """ 62 | 63 | 64 | # def build_qa_context(question: str, knowledge: list[tuple[str, str, str]]) -> list[LLMMessage]: 65 | # knowledge = "\n".join([f"{i + 1}. 相关性:{k[0]}\n{k[1]}" for i, k in enumerate(knowledge)]) 66 | # messages = [ 67 | # LLMMessage("system", qa_system_prompt).to_dict(), 68 | # LLMMessage("user", f"问题:\n{question}\n\n可能有帮助的信息:\n{knowledge}").to_dict(), 69 | # ] 70 | # return messages 71 | -------------------------------------------------------------------------------- /src/chat/knowledge/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/chat/knowledge/utils/__init__.py -------------------------------------------------------------------------------- /src/chat/knowledge/utils/dyn_topk.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | def dyn_select_top_k( 5 | score: list[tuple[Any, float]], jmp_factor: float, var_factor: float 6 | ) -> list[tuple[Any, float, float]]: 7 | """动态TopK选择""" 8 | # 检查输入列表是否为空 9 | if not score: 10 | return [] 11 | 12 | # 按照分数排序(降序) 13 | sorted_score = sorted(score, key=lambda x: x[1], reverse=True) 14 | 15 | # 归一化 16 | max_score = sorted_score[0][1] 17 | min_score = sorted_score[-1][1] 18 | normalized_score = [] 19 | for score_item in sorted_score: 20 | normalized_score.append( 21 | ( 22 | score_item[0], 23 | score_item[1], 24 | (score_item[1] - min_score) / (max_score - min_score), 25 | ) 26 | ) 27 | 28 | # 寻找跳变点:score变化最大的位置 29 | jump_idx = 0 30 | for i in range(1, len(normalized_score)): 31 | if abs(normalized_score[i][2] - normalized_score[i - 1][2]) > abs( 32 | normalized_score[jump_idx][2] - normalized_score[jump_idx - 1][2] 33 | ): 34 | jump_idx = i 35 | # 跳变阈值 36 | jump_threshold = normalized_score[jump_idx][2] 37 | 38 | # 计算均值 39 | mean_score = sum([s[2] for s in normalized_score]) / len(normalized_score) 40 | # 计算方差 41 | var_score = sum([(s[2] - mean_score) ** 2 for s in normalized_score]) / len(normalized_score) 42 | 43 | # 动态阈值 44 | threshold = jmp_factor * jump_threshold + (1 - jmp_factor) * (mean_score + var_factor * var_score) 45 | 46 | # 重新过滤 47 | res = [s for s in normalized_score if s[2] > threshold] 48 | 49 | return res 50 | -------------------------------------------------------------------------------- /src/chat/knowledge/utils/hash.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | def get_sha256(string: str) -> str: 5 | """获取字符串的SHA256值""" 6 | sha256 = hashlib.sha256() 7 | sha256.update(string.encode("utf-8")) 8 | return sha256.hexdigest() 9 | -------------------------------------------------------------------------------- /src/chat/knowledge/utils/json_fix.py: -------------------------------------------------------------------------------- 1 | import orjson 2 | from json_repair import repair_json 3 | 4 | 5 | def _find_unclosed(json_str): 6 | """ 7 | Identifies the unclosed braces and brackets in the JSON string. 8 | 9 | Args: 10 | json_str (str): The JSON string to analyze. 11 | 12 | Returns: 13 | list: A list of unclosed elements in the order they were opened. 14 | """ 15 | unclosed = [] 16 | inside_string = False 17 | escape_next = False 18 | 19 | for char in json_str: 20 | if inside_string: 21 | if escape_next: 22 | escape_next = False 23 | elif char == "\\": 24 | escape_next = True 25 | elif char == '"': 26 | inside_string = False 27 | else: 28 | if char == '"': 29 | inside_string = True 30 | elif char in "{[": 31 | unclosed.append(char) 32 | elif char in "}]": 33 | if unclosed and ((char == "}" and unclosed[-1] == "{") or (char == "]" and unclosed[-1] == "[")): 34 | unclosed.pop() 35 | 36 | return unclosed 37 | 38 | 39 | # The following code is used to fix a broken JSON string. 40 | # From HippoRAG2 (GitHub: OSU-NLP-Group/HippoRAG) 41 | def fix_broken_generated_json(json_str: str) -> str: 42 | """ 43 | Fixes a malformed JSON string by: 44 | - Removing the last comma and any trailing content. 45 | - Iterating over the JSON string once to determine and fix unclosed braces or brackets. 46 | - Ensuring braces and brackets inside string literals are not considered. 47 | 48 | If the original json_str string can be successfully loaded by orjson.loads(), will directly return it without any modification. 49 | 50 | Args: 51 | json_str (str): The malformed JSON string to be fixed. 52 | 53 | Returns: 54 | str: The corrected JSON string. 55 | """ 56 | 57 | try: 58 | # Try to load the JSON to see if it is valid 59 | orjson.loads(json_str) 60 | return json_str # Return as-is if valid 61 | except orjson.JSONDecodeError: 62 | ... 63 | 64 | # Step 1: Remove trailing content after the last comma. 65 | last_comma_index = json_str.rfind(",") 66 | if last_comma_index != -1: 67 | json_str = json_str[:last_comma_index] 68 | 69 | # Step 2: Identify unclosed braces and brackets. 70 | unclosed_elements = _find_unclosed(json_str) 71 | 72 | # Step 3: Append the necessary closing elements in reverse order of opening. 73 | closing_map = {"{": "}", "[": "]"} 74 | for open_char in reversed(unclosed_elements): 75 | json_str += closing_map[open_char] 76 | 77 | return json_str 78 | 79 | 80 | def new_fix_broken_generated_json(json_str: str) -> str: 81 | """ 82 | 使用 json-repair 库修复格式错误的 JSON 字符串。 83 | 84 | 如果原始 json_str 字符串可以被 orjson.loads() 成功加载,则直接返回而不进行任何修改。 85 | 86 | 参数: 87 | json_str (str): 需要修复的格式错误的 JSON 字符串。 88 | 89 | 返回: 90 | str: 修复后的 JSON 字符串。 91 | """ 92 | try: 93 | # 尝试加载 JSON 以查看其是否有效 94 | orjson.loads(json_str) 95 | return json_str # 如果有效则按原样返回 96 | except orjson.JSONDecodeError: 97 | # 如果无效,则尝试修复它 98 | return repair_json(json_str) 99 | -------------------------------------------------------------------------------- /src/chat/memory_system/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 简化记忆系统模块 3 | 移除即时记忆和长期记忆分类,实现统一记忆架构和智能遗忘机制 4 | """ 5 | 6 | # 核心数据结构 7 | # 激活器 8 | from .enhanced_memory_activator import MemoryActivator, enhanced_memory_activator, memory_activator 9 | from .memory_chunk import ( 10 | ConfidenceLevel, 11 | ContentStructure, 12 | ImportanceLevel, 13 | MemoryChunk, 14 | MemoryMetadata, 15 | MemoryType, 16 | create_memory_chunk, 17 | ) 18 | 19 | # 兼容性别名 20 | from .memory_chunk import MemoryChunk as Memory 21 | 22 | # 遗忘引擎 23 | from .memory_forgetting_engine import ForgettingConfig, MemoryForgettingEngine, get_memory_forgetting_engine 24 | from .memory_formatter import format_memories_bracket_style 25 | 26 | # 记忆管理器 27 | from .memory_manager import MemoryManager, MemoryResult, memory_manager 28 | 29 | # 记忆核心系统 30 | from .memory_system import MemorySystem, MemorySystemConfig, get_memory_system, initialize_memory_system 31 | 32 | # Vector DB存储系统 33 | from .vector_memory_storage_v2 import VectorMemoryStorage, VectorStorageConfig, get_vector_memory_storage 34 | 35 | __all__ = [ 36 | "ConfidenceLevel", 37 | "ContentStructure", 38 | "ForgettingConfig", 39 | "ImportanceLevel", 40 | "Memory", # 兼容性别名 41 | # 激活器 42 | "MemoryActivator", 43 | # 核心数据结构 44 | "MemoryChunk", 45 | # 遗忘引擎 46 | "MemoryForgettingEngine", 47 | # 记忆管理器 48 | "MemoryManager", 49 | "MemoryMetadata", 50 | "MemoryResult", 51 | # 记忆系统 52 | "MemorySystem", 53 | "MemorySystemConfig", 54 | "MemoryType", 55 | # Vector DB存储 56 | "VectorMemoryStorage", 57 | "VectorStorageConfig", 58 | "create_memory_chunk", 59 | "enhanced_memory_activator", # 兼容性别名 60 | # 格式化工具 61 | "format_memories_bracket_style", 62 | "get_memory_forgetting_engine", 63 | "get_memory_system", 64 | "get_vector_memory_storage", 65 | "initialize_memory_system", 66 | "memory_activator", 67 | "memory_manager", 68 | ] 69 | 70 | # 版本信息 71 | __version__ = "3.0.0" 72 | __author__ = "MoFox Team" 73 | __description__ = "简化记忆系统 - 统一记忆架构与智能遗忘机制" 74 | -------------------------------------------------------------------------------- /src/chat/message_manager/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 消息管理器模块 3 | 提供统一的消息管理、上下文管理和流循环调度功能 4 | """ 5 | 6 | from .context_manager import SingleStreamContextManager 7 | from .distribution_manager import StreamLoopManager, stream_loop_manager 8 | from .message_manager import MessageManager, message_manager 9 | 10 | __all__ = [ 11 | "MessageManager", 12 | "SingleStreamContextManager", 13 | "StreamLoopManager", 14 | "message_manager", 15 | "stream_loop_manager", 16 | ] 17 | -------------------------------------------------------------------------------- /src/chat/message_manager/sleep_manager/notification_sender.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | 3 | # from ..hfc_context import HfcContext 4 | 5 | logger = get_logger("notification_sender") 6 | 7 | 8 | class NotificationSender: 9 | @staticmethod 10 | async def send_goodnight_notification(context): # type: ignore 11 | """发送晚安通知""" 12 | # try: 13 | # from ..proactive.events import ProactiveTriggerEvent 14 | # from ..proactive.proactive_thinker import ProactiveThinker 15 | 16 | # event = ProactiveTriggerEvent(source="sleep_manager", reason="goodnight") 17 | # proactive_thinker = ProactiveThinker(context, context.chat_instance.cycle_processor) 18 | # await proactive_thinker.think(event) 19 | # except Exception as e: 20 | # logger.error(f"发送晚安通知失败: {e}") 21 | 22 | @staticmethod 23 | async def send_insomnia_notification(context, reason: str): # type: ignore 24 | """发送失眠通知""" 25 | # try: 26 | # from ..proactive.events import ProactiveTriggerEvent 27 | # from ..proactive.proactive_thinker import ProactiveThinker 28 | 29 | # event = ProactiveTriggerEvent(source="sleep_manager", reason=reason) 30 | # proactive_thinker = ProactiveThinker(context, context.chat_instance.cycle_processor) 31 | # await proactive_thinker.think(event) 32 | # except Exception as e: 33 | # logger.error(f"发送失眠通知失败: {e}") 34 | -------------------------------------------------------------------------------- /src/chat/message_manager/sleep_manager/sleep_state.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime 2 | from enum import Enum, auto 3 | 4 | from src.common.logger import get_logger 5 | from src.manager.local_store_manager import local_storage 6 | 7 | logger = get_logger("sleep_state") 8 | 9 | 10 | class SleepState(Enum): 11 | """ 12 | 定义了角色可能处于的几种睡眠状态。 13 | 这是一个状态机,用于管理角色的睡眠周期。 14 | """ 15 | 16 | AWAKE = auto() # 清醒状态 17 | INSOMNIA = auto() # 失眠状态 18 | PREPARING_SLEEP = auto() # 准备入睡状态,一个短暂的过渡期 19 | SLEEPING = auto() # 正在睡觉状态 20 | WOKEN_UP = auto() # 被吵醒状态 21 | 22 | 23 | class SleepContext: 24 | """ 25 | 睡眠上下文,负责封装和管理所有与睡眠相关的状态,并处理其持久化。 26 | """ 27 | 28 | def __init__(self): 29 | """初始化睡眠上下文,并从本地存储加载初始状态。""" 30 | self.current_state: SleepState = SleepState.AWAKE 31 | self.sleep_buffer_end_time: datetime | None = None 32 | self.total_delayed_minutes_today: float = 0.0 33 | self.last_sleep_check_date: date | None = None 34 | self.re_sleep_attempt_time: datetime | None = None 35 | self.load() 36 | 37 | def save(self): 38 | """将当前的睡眠状态数据保存到本地存储。""" 39 | try: 40 | state = { 41 | "current_state": self.current_state.name, 42 | "sleep_buffer_end_time_ts": self.sleep_buffer_end_time.timestamp() 43 | if self.sleep_buffer_end_time 44 | else None, 45 | "total_delayed_minutes_today": self.total_delayed_minutes_today, 46 | "last_sleep_check_date_str": self.last_sleep_check_date.isoformat() 47 | if self.last_sleep_check_date 48 | else None, 49 | "re_sleep_attempt_time_ts": self.re_sleep_attempt_time.timestamp() 50 | if self.re_sleep_attempt_time 51 | else None, 52 | } 53 | local_storage["schedule_sleep_state"] = state 54 | logger.debug(f"已保存睡眠上下文: {state}") 55 | except Exception as e: 56 | logger.error(f"保存睡眠上下文失败: {e}") 57 | 58 | def load(self): 59 | """从本地存储加载并解析睡眠状态。""" 60 | try: 61 | state = local_storage["schedule_sleep_state"] 62 | if not (state and isinstance(state, dict)): 63 | logger.info("未找到本地睡眠上下文,使用默认值。") 64 | return 65 | 66 | state_name = state.get("current_state") 67 | if state_name and hasattr(SleepState, state_name): 68 | self.current_state = SleepState[state_name] 69 | 70 | end_time_ts = state.get("sleep_buffer_end_time_ts") 71 | if end_time_ts: 72 | self.sleep_buffer_end_time = datetime.fromtimestamp(end_time_ts) 73 | 74 | re_sleep_ts = state.get("re_sleep_attempt_time_ts") 75 | if re_sleep_ts: 76 | self.re_sleep_attempt_time = datetime.fromtimestamp(re_sleep_ts) 77 | 78 | self.total_delayed_minutes_today = state.get("total_delayed_minutes_today", 0.0) 79 | 80 | date_str = state.get("last_sleep_check_date_str") 81 | if date_str: 82 | self.last_sleep_check_date = datetime.fromisoformat(date_str).date() 83 | 84 | logger.info(f"成功从本地存储加载睡眠上下文: {state}") 85 | except Exception as e: 86 | logger.warning(f"加载睡眠上下文失败,将使用默认值: {e}") 87 | -------------------------------------------------------------------------------- /src/chat/message_manager/sleep_manager/wakeup_context.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | from src.manager.local_store_manager import local_storage 3 | 4 | logger = get_logger("wakeup_context") 5 | 6 | 7 | class WakeUpContext: 8 | """ 9 | 唤醒上下文,负责封装和管理所有与唤醒相关的状态,并处理其持久化。 10 | """ 11 | 12 | def __init__(self): 13 | """初始化唤醒上下文,并从本地存储加载初始状态。""" 14 | self.wakeup_value: float = 0.0 15 | self.is_angry: bool = False 16 | self.angry_start_time: float = 0.0 17 | self.sleep_pressure: float = 100.0 # 新增:睡眠压力 18 | self.load() 19 | 20 | def _get_storage_key(self) -> str: 21 | """获取本地存储键""" 22 | return "global_wakeup_manager_state" 23 | 24 | def load(self): 25 | """从本地存储加载状态""" 26 | state = local_storage[self._get_storage_key()] 27 | if state and isinstance(state, dict): 28 | self.wakeup_value = state.get("wakeup_value", 0.0) 29 | self.is_angry = state.get("is_angry", False) 30 | self.angry_start_time = state.get("angry_start_time", 0.0) 31 | self.sleep_pressure = state.get("sleep_pressure", 100.0) 32 | logger.info(f"成功从本地存储加载唤醒上下文: {state}") 33 | else: 34 | logger.info("未找到本地唤醒上下文,将使用默认值初始化。") 35 | 36 | def save(self): 37 | """将当前状态保存到本地存储""" 38 | state = { 39 | "wakeup_value": self.wakeup_value, 40 | "is_angry": self.is_angry, 41 | "angry_start_time": self.angry_start_time, 42 | "sleep_pressure": self.sleep_pressure, 43 | } 44 | local_storage[self._get_storage_key()] = state 45 | logger.debug(f"已将唤醒上下文保存到本地存储: {state}") 46 | -------------------------------------------------------------------------------- /src/chat/message_receive/__init__.py: -------------------------------------------------------------------------------- 1 | from src.chat.emoji_system.emoji_manager import get_emoji_manager 2 | from src.chat.message_receive.chat_stream import get_chat_manager 3 | from src.chat.message_receive.storage import MessageStorage 4 | 5 | __all__ = [ 6 | "MessageStorage", 7 | "get_chat_manager", 8 | "get_emoji_manager", 9 | ] 10 | -------------------------------------------------------------------------------- /src/chat/message_receive/uni_message_sender.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import traceback 3 | 4 | from rich.traceback import install 5 | 6 | from src.chat.message_receive.message import MessageSending 7 | from src.chat.message_receive.storage import MessageStorage 8 | from src.chat.utils.utils import calculate_typing_time, truncate_message 9 | from src.common.logger import get_logger 10 | from src.common.message.api import get_global_api 11 | 12 | install(extra_lines=3) 13 | 14 | logger = get_logger("sender") 15 | 16 | 17 | async def send_message(message: MessageSending, show_log=True) -> bool: 18 | """合并后的消息发送函数,包含WS发送和日志记录""" 19 | message_preview = truncate_message(message.processed_plain_text, max_length=120) 20 | 21 | try: 22 | # 直接调用API发送消息 23 | await get_global_api().send_message(message) 24 | if show_log: 25 | logger.info(f"已将消息 '{message_preview}' 发往平台'{message.message_info.platform}'") 26 | return True 27 | 28 | except Exception as e: 29 | logger.error(f"发送消息 '{message_preview}' 发往平台'{message.message_info.platform}' 失败: {e!s}") 30 | traceback.print_exc() 31 | raise e # 重新抛出其他异常 32 | 33 | 34 | class HeartFCSender: 35 | """管理消息的注册、即时处理、发送和存储,并跟踪思考状态。""" 36 | 37 | def __init__(self): 38 | self.storage = MessageStorage() 39 | 40 | async def send_message( 41 | self, message: MessageSending, typing=False, set_reply=False, storage_message=True, show_log=True 42 | ): 43 | """ 44 | 处理、发送并存储一条消息。 45 | 46 | 参数: 47 | message: MessageSending 对象,待发送的消息。 48 | typing: 是否模拟打字等待。 49 | 50 | 用法: 51 | - typing=True 时,发送前会有打字等待。 52 | """ 53 | if not message.chat_stream: 54 | logger.error("消息缺少 chat_stream,无法发送") 55 | raise ValueError("消息缺少 chat_stream,无法发送") 56 | if not message.message_info or not message.message_info.message_id: 57 | logger.error("消息缺少 message_info 或 message_id,无法发送") 58 | raise ValueError("消息缺少 message_info 或 message_id,无法发送") 59 | 60 | chat_id = message.chat_stream.stream_id 61 | message_id = message.message_info.message_id 62 | 63 | try: 64 | if set_reply: 65 | message.build_reply() 66 | logger.debug(f"[{chat_id}] 选择回复引用消息: {message.processed_plain_text[:20]}...") 67 | 68 | await message.process() 69 | 70 | if typing: 71 | typing_time = calculate_typing_time( 72 | input_string=message.processed_plain_text, 73 | thinking_start_time=message.thinking_start_time, 74 | is_emoji=message.is_emoji, 75 | ) 76 | await asyncio.sleep(typing_time) 77 | 78 | sent_msg = await send_message(message, show_log=show_log) 79 | if not sent_msg: 80 | return False 81 | 82 | if storage_message: 83 | await self.storage.store_message(message, message.chat_stream) 84 | 85 | return sent_msg 86 | 87 | except Exception as e: 88 | logger.error(f"[{chat_id}] 处理或存储消息 {message_id} 时出错: {e}") 89 | raise e 90 | -------------------------------------------------------------------------------- /src/chat/replyer/replyer_manager.py: -------------------------------------------------------------------------------- 1 | from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager 2 | from src.chat.replyer.default_generator import DefaultReplyer 3 | from src.common.logger import get_logger 4 | 5 | logger = get_logger("ReplyerManager") 6 | 7 | 8 | class ReplyerManager: 9 | def __init__(self): 10 | self._repliers: dict[str, DefaultReplyer] = {} 11 | 12 | async def get_replyer( 13 | self, 14 | chat_stream: ChatStream | None = None, 15 | chat_id: str | None = None, 16 | request_type: str = "replyer", 17 | ) -> DefaultReplyer | None: 18 | """ 19 | 获取或创建回复器实例。 20 | 21 | model_configs 仅在首次为某个 chat_id/stream_id 创建实例时有效。 22 | 后续调用将返回已缓存的实例,忽略 model_configs 参数。 23 | """ 24 | stream_id = chat_stream.stream_id if chat_stream else chat_id 25 | if not stream_id: 26 | logger.warning("[ReplyerManager] 缺少 stream_id,无法获取回复器。") 27 | return None 28 | 29 | # 如果已有缓存实例,直接返回 30 | if stream_id in self._repliers: 31 | logger.debug(f"[ReplyerManager] 为 stream_id '{stream_id}' 返回已存在的回复器实例。") 32 | return self._repliers[stream_id] 33 | 34 | # 如果没有缓存,则创建新实例(首次初始化) 35 | logger.debug(f"[ReplyerManager] 为 stream_id '{stream_id}' 创建新的回复器实例并缓存。") 36 | 37 | target_stream = chat_stream 38 | if not target_stream: 39 | if chat_manager := get_chat_manager(): 40 | target_stream = await chat_manager.get_stream(stream_id) 41 | 42 | if not target_stream: 43 | logger.warning(f"[ReplyerManager] 未找到 stream_id='{stream_id}' 的聊天流,无法创建回复器。") 44 | return None 45 | 46 | # model_configs 只在此时(初始化时)生效 47 | replyer = DefaultReplyer( 48 | chat_stream=target_stream, 49 | request_type=request_type, 50 | ) 51 | self._repliers[stream_id] = replyer 52 | return replyer 53 | 54 | 55 | # 创建一个全局实例 56 | replyer_manager = ReplyerManager() 57 | -------------------------------------------------------------------------------- /src/chat/utils/memory_mappings.py: -------------------------------------------------------------------------------- 1 | """ 2 | 记忆系统相关的映射表和工具函数 3 | 提供记忆类型、置信度、重要性等的中文标签映射 4 | """ 5 | 6 | # 记忆类型到中文标签的完整映射表 7 | MEMORY_TYPE_CHINESE_MAPPING = { 8 | "personal_fact": "个人事实", 9 | "preference": "偏好", 10 | "event": "事件", 11 | "opinion": "观点", 12 | "relationship": "人际关系", 13 | "emotion": "情感状态", 14 | "knowledge": "知识信息", 15 | "skill": "技能能力", 16 | "goal": "目标计划", 17 | "experience": "经验教训", 18 | "contextual": "上下文信息", 19 | "unknown": "未知", 20 | } 21 | 22 | # 置信度等级到中文标签的映射表 23 | CONFIDENCE_LEVEL_CHINESE_MAPPING = { 24 | 1: "低置信度", 25 | 2: "中等置信度", 26 | 3: "高置信度", 27 | 4: "已验证", 28 | "LOW": "低置信度", 29 | "MEDIUM": "中等置信度", 30 | "HIGH": "高置信度", 31 | "VERIFIED": "已验证", 32 | "unknown": "未知", 33 | } 34 | 35 | # 重要性等级到中文标签的映射表 36 | IMPORTANCE_LEVEL_CHINESE_MAPPING = { 37 | 1: "低重要性", 38 | 2: "一般重要性", 39 | 3: "高重要性", 40 | 4: "关键重要性", 41 | "LOW": "低重要性", 42 | "NORMAL": "一般重要性", 43 | "HIGH": "高重要性", 44 | "CRITICAL": "关键重要性", 45 | "unknown": "未知", 46 | } 47 | 48 | 49 | def get_memory_type_chinese_label(memory_type: str) -> str: 50 | """获取记忆类型的中文标签 51 | 52 | Args: 53 | memory_type: 记忆类型字符串 54 | 55 | Returns: 56 | str: 对应的中文标签,如果找不到则返回"未知" 57 | """ 58 | return MEMORY_TYPE_CHINESE_MAPPING.get(memory_type, "未知") 59 | 60 | 61 | def get_confidence_level_chinese_label(level) -> str: 62 | """获取置信度等级的中文标签 63 | 64 | Args: 65 | level: 置信度等级(可以是数字、字符串或枚举实例) 66 | 67 | Returns: 68 | str: 对应的中文标签,如果找不到则返回"未知" 69 | """ 70 | # 处理枚举实例 71 | if hasattr(level, "value"): 72 | level = level.value 73 | 74 | # 处理数字 75 | if isinstance(level, int): 76 | return CONFIDENCE_LEVEL_CHINESE_MAPPING.get(level, "未知") 77 | 78 | # 处理字符串 79 | if isinstance(level, str): 80 | level_upper = level.upper() 81 | return CONFIDENCE_LEVEL_CHINESE_MAPPING.get(level_upper, "未知") 82 | 83 | return "未知" 84 | 85 | 86 | def get_importance_level_chinese_label(level) -> str: 87 | """获取重要性等级的中文标签 88 | 89 | Args: 90 | level: 重要性等级(可以是数字、字符串或枚举实例) 91 | 92 | Returns: 93 | str: 对应的中文标签,如果找不到则返回"未知" 94 | """ 95 | # 处理枚举实例 96 | if hasattr(level, "value"): 97 | level = level.value 98 | 99 | # 处理数字 100 | if isinstance(level, int): 101 | return IMPORTANCE_LEVEL_CHINESE_MAPPING.get(level, "未知") 102 | 103 | # 处理字符串 104 | if isinstance(level, str): 105 | level_upper = level.upper() 106 | return IMPORTANCE_LEVEL_CHINESE_MAPPING.get(level_upper, "未知") 107 | 108 | return "未知" 109 | -------------------------------------------------------------------------------- /src/chat/utils/utils_voice.py: -------------------------------------------------------------------------------- 1 | from rich.traceback import install 2 | 3 | from src.common.logger import get_logger 4 | from src.config.config import global_config, model_config 5 | from src.llm_models.utils_model import LLMRequest 6 | 7 | install(extra_lines=3) 8 | 9 | logger = get_logger("chat_voice") 10 | 11 | 12 | async def get_voice_text(voice_base64: str) -> str: 13 | """获取音频文件转录文本""" 14 | if not global_config.voice.enable_asr: 15 | logger.warning("语音识别未启用,无法处理语音消息") 16 | return "[语音]" 17 | try: 18 | _llm = LLMRequest(model_set=model_config.model_task_config.voice, request_type="audio") 19 | text = await _llm.generate_response_for_voice(voice_base64) 20 | if text is None: 21 | logger.warning("未能生成语音文本") 22 | return "[语音(文本生成失败)]" 23 | 24 | logger.debug(f"描述是{text}") 25 | 26 | return f"[语音:{text}]" 27 | except Exception as e: 28 | logger.error(f"语音转文字失败: {e!s}") 29 | return "[语音]" 30 | -------------------------------------------------------------------------------- /src/common/__init__.py: -------------------------------------------------------------------------------- 1 | # 这个文件可以为空,但必须存在 2 | -------------------------------------------------------------------------------- /src/common/config_helpers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from src.config.config import global_config, model_config 4 | 5 | 6 | def resolve_embedding_dimension(fallback: int | None = None, *, sync_global: bool = True) -> int | None: 7 | """获取当前配置的嵌入向量维度。 8 | 9 | 优先顺序: 10 | 1. 模型配置中 `model_task_config.embedding.embedding_dimension` 11 | 2. 机器人配置中 `lpmm_knowledge.embedding_dimension` 12 | 3. 调用方提供的 fallback 13 | """ 14 | 15 | candidates: list[int | None] = [] 16 | 17 | try: 18 | embedding_task = getattr(model_config.model_task_config, "embedding", None) 19 | if embedding_task is not None: 20 | candidates.append(getattr(embedding_task, "embedding_dimension", None)) 21 | except Exception: 22 | candidates.append(None) 23 | 24 | try: 25 | candidates.append(getattr(global_config.lpmm_knowledge, "embedding_dimension", None)) 26 | except Exception: 27 | candidates.append(None) 28 | 29 | candidates.append(fallback) 30 | 31 | resolved: int | None = next((int(dim) for dim in candidates if dim and int(dim) > 0), None) 32 | 33 | if resolved and sync_global: 34 | try: 35 | if getattr(global_config.lpmm_knowledge, "embedding_dimension", None) != resolved: 36 | global_config.lpmm_knowledge.embedding_dimension = resolved # type: ignore[attr-defined] 37 | except Exception: 38 | pass 39 | 40 | return resolved 41 | -------------------------------------------------------------------------------- /src/common/data_models/__init__.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from typing import Any 3 | 4 | 5 | class BaseDataModel: 6 | def deepcopy(self): 7 | return copy.deepcopy(self) 8 | 9 | 10 | def temporarily_transform_class_to_dict(obj: Any) -> Any: 11 | # sourcery skip: assign-if-exp, reintroduce-else 12 | """ 13 | 将对象或容器中的 BaseDataModel 子类(类对象)或 BaseDataModel 实例 14 | 递归转换为普通 dict,不修改原对象。 15 | - 对于类对象(isinstance(value, type) 且 issubclass(..., BaseDataModel)), 16 | 读取类的 __dict__ 中非 dunder 项并递归转换。 17 | - 对于实例(isinstance(value, BaseDataModel)),读取 vars(instance) 并递归转换。 18 | """ 19 | 20 | def _transform(value: Any) -> Any: 21 | # 值是类对象且为 BaseDataModel 的子类 22 | if isinstance(value, type) and issubclass(value, BaseDataModel): 23 | return {k: _transform(v) for k, v in value.__dict__.items() if not k.startswith("__") and not callable(v)} 24 | 25 | # 值是 BaseDataModel 的实例 26 | if isinstance(value, BaseDataModel): 27 | return {k: _transform(v) for k, v in vars(value).items()} 28 | 29 | # 常见容器类型,递归处理 30 | if isinstance(value, dict): 31 | return {k: _transform(v) for k, v in value.items()} 32 | if isinstance(value, list): 33 | return [_transform(v) for v in value] 34 | if isinstance(value, tuple): 35 | return tuple(_transform(v) for v in value) 36 | if isinstance(value, set): 37 | return {_transform(v) for v in value} 38 | # 基本类型,直接返回 39 | return value 40 | 41 | result = _transform(obj) 42 | 43 | def flatten(target_dict: dict): 44 | flat_dict = {} 45 | for k, v in target_dict.items(): 46 | if isinstance(v, dict): 47 | # 递归扁平化子字典 48 | sub_flat = flatten(v) 49 | flat_dict.update(sub_flat) 50 | else: 51 | flat_dict[k] = v 52 | return flat_dict 53 | 54 | return flatten(result) if isinstance(result, dict) else result 55 | -------------------------------------------------------------------------------- /src/common/data_models/info_data_model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import TYPE_CHECKING, Optional 3 | 4 | from src.plugin_system.base.component_types import ChatType 5 | 6 | from . import BaseDataModel 7 | 8 | if TYPE_CHECKING: 9 | from src.plugin_system.base.component_types import ActionInfo, ChatMode 10 | 11 | from .database_data_model import DatabaseMessages 12 | 13 | 14 | @dataclass 15 | class TargetPersonInfo(BaseDataModel): 16 | platform: str = field(default_factory=str) 17 | user_id: str = field(default_factory=str) 18 | user_nickname: str = field(default_factory=str) 19 | person_id: str | None = None 20 | person_name: str | None = None 21 | 22 | 23 | @dataclass 24 | class ActionPlannerInfo(BaseDataModel): 25 | action_type: str = field(default_factory=str) 26 | reasoning: str | None = None 27 | action_data: dict | None = None 28 | action_message: Optional["DatabaseMessages"] = None 29 | available_actions: dict[str, "ActionInfo"] | None = None 30 | 31 | 32 | @dataclass 33 | class InterestScore(BaseDataModel): 34 | """兴趣度评分结果""" 35 | 36 | message_id: str 37 | total_score: float 38 | interest_match_score: float 39 | relationship_score: float 40 | mentioned_score: float 41 | details: dict[str, str] 42 | 43 | 44 | @dataclass 45 | class Plan(BaseDataModel): 46 | """ 47 | 统一规划数据模型 48 | """ 49 | 50 | chat_id: str 51 | mode: "ChatMode" 52 | 53 | chat_type: "ChatType" 54 | # Generator 填充 55 | available_actions: dict[str, "ActionInfo"] = field(default_factory=dict) 56 | chat_history: list["DatabaseMessages"] = field(default_factory=list) 57 | target_info: TargetPersonInfo | None = None 58 | 59 | # Filter 填充 60 | llm_prompt: str | None = None 61 | decided_actions: list[ActionPlannerInfo] | None = None 62 | -------------------------------------------------------------------------------- /src/common/data_models/llm_data_model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import TYPE_CHECKING, Any 3 | 4 | from src.llm_models.payload_content.tool_option import ToolCall 5 | 6 | from . import BaseDataModel 7 | 8 | if TYPE_CHECKING: 9 | pass 10 | 11 | 12 | @dataclass 13 | class LLMGenerationDataModel(BaseDataModel): 14 | content: str | None = None 15 | reasoning: str | None = None 16 | model: str | None = None 17 | tool_calls: list["ToolCall"] | None = None 18 | prompt: str | None = None 19 | selected_expressions: list[int] | None = None 20 | reply_set: list[tuple[str, Any]] | None = None 21 | -------------------------------------------------------------------------------- /src/common/database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/common/database/__init__.py -------------------------------------------------------------------------------- /src/common/database/sqlalchemy_init.py: -------------------------------------------------------------------------------- 1 | """SQLAlchemy数据库初始化模块 2 | 3 | 替换Peewee的数据库初始化逻辑 4 | 提供统一的异步数据库初始化接口 5 | """ 6 | 7 | from sqlalchemy.exc import SQLAlchemyError 8 | 9 | from src.common.database.sqlalchemy_models import Base, get_engine, initialize_database 10 | from src.common.logger import get_logger 11 | 12 | logger = get_logger("sqlalchemy_init") 13 | 14 | 15 | async def initialize_sqlalchemy_database() -> bool: 16 | """ 17 | 初始化SQLAlchemy异步数据库 18 | 创建所有表结构 19 | 20 | Returns: 21 | bool: 初始化是否成功 22 | """ 23 | try: 24 | logger.info("开始初始化SQLAlchemy异步数据库...") 25 | 26 | # 初始化数据库引擎和会话 27 | engine, session_local = await initialize_database() 28 | 29 | if engine is None: 30 | logger.error("数据库引擎初始化失败") 31 | return False 32 | 33 | logger.info("SQLAlchemy异步数据库初始化成功") 34 | return True 35 | 36 | except SQLAlchemyError as e: 37 | logger.error(f"SQLAlchemy数据库初始化失败: {e}") 38 | return False 39 | except Exception as e: 40 | logger.error(f"数据库初始化过程中发生未知错误: {e}") 41 | return False 42 | 43 | 44 | async def create_all_tables() -> bool: 45 | """ 46 | 异步创建所有数据库表 47 | 48 | Returns: 49 | bool: 创建是否成功 50 | """ 51 | try: 52 | logger.info("开始创建数据库表...") 53 | 54 | engine = await get_engine() 55 | if engine is None: 56 | logger.error("无法获取数据库引擎") 57 | return False 58 | 59 | # 异步创建所有表 60 | async with engine.begin() as conn: 61 | await conn.run_sync(Base.metadata.create_all) 62 | 63 | logger.info("数据库表创建成功") 64 | return True 65 | 66 | except SQLAlchemyError as e: 67 | logger.error(f"创建数据库表失败: {e}") 68 | return False 69 | except Exception as e: 70 | logger.error(f"创建数据库表过程中发生未知错误: {e}") 71 | return False 72 | 73 | 74 | async def get_database_info() -> dict | None: 75 | """ 76 | 异步获取数据库信息 77 | 78 | Returns: 79 | dict: 数据库信息字典,包含引擎信息等 80 | """ 81 | try: 82 | engine = await get_engine() 83 | if engine is None: 84 | return None 85 | 86 | info = { 87 | "engine_name": engine.name, 88 | "driver": engine.driver, 89 | "url": str(engine.url).replace(engine.url.password or "", "***"), # 隐藏密码 90 | "pool_size": getattr(engine.pool, "size", None), 91 | "max_overflow": getattr(engine.pool, "max_overflow", None), 92 | } 93 | 94 | return info 95 | 96 | except Exception as e: 97 | logger.error(f"获取数据库信息失败: {e}") 98 | return None 99 | 100 | 101 | _database_initialized = False 102 | 103 | 104 | async def initialize_database_compat() -> bool: 105 | """ 106 | 兼容性异步数据库初始化函数 107 | 用于替换原有的Peewee初始化代码 108 | 109 | Returns: 110 | bool: 初始化是否成功 111 | """ 112 | global _database_initialized 113 | 114 | if _database_initialized: 115 | return True 116 | 117 | success = await initialize_sqlalchemy_database() 118 | if success: 119 | success = await create_all_tables() 120 | 121 | if success: 122 | _database_initialized = True 123 | 124 | return success 125 | -------------------------------------------------------------------------------- /src/common/message/__init__.py: -------------------------------------------------------------------------------- 1 | """Maim Message - A message handling library""" 2 | 3 | __version__ = "0.1.0" 4 | 5 | from .api import get_global_api 6 | 7 | __all__ = [ 8 | "get_global_api", 9 | ] 10 | -------------------------------------------------------------------------------- /src/common/message/api.py: -------------------------------------------------------------------------------- 1 | import importlib.metadata 2 | import os 3 | 4 | from maim_message import MessageServer 5 | 6 | from src.common.logger import get_logger 7 | from src.common.server import get_global_server 8 | from src.config.config import global_config 9 | 10 | global_api = None 11 | 12 | 13 | def get_global_api() -> MessageServer: # sourcery skip: extract-method 14 | """获取全局MessageServer实例""" 15 | global global_api 16 | if global_api is None: 17 | # 检查maim_message版本 18 | try: 19 | maim_message_version = importlib.metadata.version("maim_message") 20 | version_compatible = [int(x) for x in maim_message_version.split(".")] >= [0, 3, 3] 21 | except (importlib.metadata.PackageNotFoundError, ValueError): 22 | version_compatible = False 23 | 24 | # 读取配置项 25 | maim_message_config = global_config.maim_message 26 | 27 | # 设置基本参数 28 | 29 | host = os.getenv("HOST", "127.0.0.1") 30 | port_str = os.getenv("PORT", "8000") 31 | 32 | try: 33 | port = int(port_str) 34 | except ValueError: 35 | port = 8000 36 | 37 | kwargs = { 38 | "host": host, 39 | "port": port, 40 | "app": get_global_server().get_app(), 41 | } 42 | 43 | # 只有在版本 >= 0.3.0 时才使用高级特性 44 | if version_compatible: 45 | # 添加自定义logger 46 | maim_message_logger = get_logger("maim_message") 47 | kwargs["custom_logger"] = maim_message_logger 48 | 49 | # 添加token认证 50 | if maim_message_config.auth_token and len(maim_message_config.auth_token) > 0: 51 | kwargs["enable_token"] = True 52 | 53 | if maim_message_config.use_custom: 54 | # 添加WSS模式支持 55 | del kwargs["app"] 56 | kwargs["host"] = maim_message_config.host 57 | kwargs["port"] = maim_message_config.port 58 | kwargs["mode"] = maim_message_config.mode 59 | if maim_message_config.use_wss: 60 | if maim_message_config.cert_file: 61 | kwargs["ssl_certfile"] = maim_message_config.cert_file 62 | if maim_message_config.key_file: 63 | kwargs["ssl_keyfile"] = maim_message_config.key_file 64 | kwargs["enable_custom_uvicorn_logger"] = False 65 | 66 | global_api = MessageServer(**kwargs) 67 | if version_compatible and maim_message_config.auth_token: 68 | for token in maim_message_config.auth_token: 69 | global_api.add_valid_token(token) 70 | return global_api 71 | -------------------------------------------------------------------------------- /src/common/tcp_connector.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | 3 | import aiohttp 4 | import certifi 5 | 6 | ssl_context = ssl.create_default_context(cafile=certifi.where()) 7 | 8 | 9 | async def get_tcp_connector(): 10 | return aiohttp.TCPConnector(ssl=ssl_context) 11 | -------------------------------------------------------------------------------- /src/common/vector_db/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import VectorDBBase 2 | from .chromadb_impl import ChromaDBImpl 3 | 4 | 5 | def get_vector_db_service() -> VectorDBBase: 6 | """ 7 | 工厂函数,初始化并返回向量数据库服务实例。 8 | 9 | 目前硬编码为 ChromaDB,未来可以从配置中读取。 10 | """ 11 | # TODO: 从全局配置中读取数据库类型和路径 12 | db_path = "data/chroma_db" 13 | 14 | # ChromaDBImpl 是一个单例,所以这里每次调用都会返回同一个实例 15 | return ChromaDBImpl(path=db_path) 16 | 17 | 18 | # 全局向量数据库服务实例 19 | vector_db_service: VectorDBBase = get_vector_db_service() 20 | 21 | __all__ = ["VectorDBBase", "vector_db_service"] 22 | -------------------------------------------------------------------------------- /src/individuality/not_using/scene.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any 3 | 4 | import orjson 5 | 6 | 7 | def load_scenes() -> dict[str, Any]: 8 | """ 9 | 从JSON文件加载场景数据 10 | 11 | Returns: 12 | Dict: 包含所有场景的字典 13 | """ 14 | current_dir = os.path.dirname(os.path.abspath(__file__)) 15 | json_path = os.path.join(current_dir, "template_scene.json") 16 | 17 | with open(json_path, encoding="utf-8") as f: 18 | return orjson.loads(f.read()) 19 | 20 | 21 | PERSONALITY_SCENES = load_scenes() 22 | 23 | 24 | def get_scene_by_factor(factor: str) -> dict | None: 25 | """ 26 | 根据人格因子获取对应的情景测试 27 | 28 | Args: 29 | factor (str): 人格因子名称 30 | 31 | Returns: 32 | dict: 包含情景描述的字典 33 | """ 34 | return PERSONALITY_SCENES.get(factor, None) 35 | 36 | 37 | def get_all_scenes() -> dict: 38 | """ 39 | 获取所有情景测试 40 | 41 | Returns: 42 | Dict: 所有情景测试的字典 43 | """ 44 | return PERSONALITY_SCENES 45 | -------------------------------------------------------------------------------- /src/llm_models/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mai.To.The.Gate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/llm_models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/llm_models/__init__.py -------------------------------------------------------------------------------- /src/llm_models/exceptions.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | # 常见Error Code Mapping (以OpenAI API为例) 4 | error_code_mapping = { 5 | 400: "参数不正确", 6 | 401: "API-Key错误,认证失败,请检查/config/model_list.toml中的配置是否正确", 7 | 402: "账号余额不足", 8 | 403: "模型拒绝访问,可能需要实名或余额不足", 9 | 404: "Not Found", 10 | 413: "请求体过大,请尝试压缩图片或减少输入内容", 11 | 429: "请求过于频繁,请稍后再试", 12 | 500: "服务器内部故障", 13 | 503: "服务器负载过高", 14 | } 15 | 16 | 17 | class NetworkConnectionError(Exception): 18 | """连接异常,常见于网络问题或服务器不可用""" 19 | 20 | def __init__(self): 21 | super().__init__() 22 | 23 | def __str__(self): 24 | return "连接异常,请检查网络连接状态或URL是否正确" 25 | 26 | 27 | class ReqAbortException(Exception): 28 | """请求异常退出,常见于请求被中断或取消""" 29 | 30 | def __init__(self, message: str | None = None): 31 | super().__init__(message) 32 | self.message = message 33 | 34 | def __str__(self): 35 | return self.message or "请求因未知原因异常终止" 36 | 37 | 38 | class RespNotOkException(Exception): 39 | """请求响应异常,见于请求未能成功响应(非 '200 OK')""" 40 | 41 | def __init__(self, status_code: int, message: str | None = None): 42 | super().__init__(message) 43 | self.status_code = status_code 44 | self.message = message 45 | 46 | def __str__(self): 47 | if self.status_code in error_code_mapping: 48 | return error_code_mapping[self.status_code] 49 | elif self.message: 50 | return self.message 51 | else: 52 | return f"未知的异常响应代码:{self.status_code}" 53 | 54 | 55 | class RespParseException(Exception): 56 | """响应解析错误,常见于响应格式不正确或解析方法不匹配""" 57 | 58 | def __init__(self, ext_info: Any, message: str | None = None): 59 | super().__init__(message) 60 | self.ext_info = ext_info 61 | self.message = message 62 | 63 | def __str__(self): 64 | return self.message or "解析响应内容时发生未知错误,请检查是否配置了正确的解析方法" 65 | 66 | 67 | class PayLoadTooLargeError(Exception): 68 | """自定义异常类,用于处理请求体过大错误""" 69 | 70 | def __init__(self, message: str): 71 | super().__init__(message) 72 | self.message = message 73 | 74 | def __str__(self): 75 | return "请求体过大,请尝试压缩图片或减少输入内容。" 76 | 77 | 78 | class RequestAbortException(Exception): 79 | """自定义异常类,用于处理请求中断异常""" 80 | 81 | def __init__(self, message: str): 82 | super().__init__(message) 83 | self.message = message 84 | 85 | def __str__(self): 86 | return self.message 87 | 88 | 89 | class PermissionDeniedException(Exception): 90 | """自定义异常类,用于处理访问拒绝的异常""" 91 | 92 | def __init__(self, message: str): 93 | super().__init__(message) 94 | self.message = message 95 | 96 | def __str__(self): 97 | return self.message 98 | -------------------------------------------------------------------------------- /src/llm_models/model_client/__init__.py: -------------------------------------------------------------------------------- 1 | from src.config.config import model_config 2 | 3 | used_client_types = {provider.client_type for provider in model_config.api_providers} 4 | 5 | if "openai" in used_client_types: 6 | from . import openai_client # noqa: F401 7 | if "aiohttp_gemini" in used_client_types: 8 | from . import aiohttp_gemini_client # noqa: F401 9 | if "mcp_sse" in used_client_types: 10 | from . import mcp_sse_client # noqa: F401 11 | -------------------------------------------------------------------------------- /src/llm_models/payload_content/__init__.py: -------------------------------------------------------------------------------- 1 | from .tool_option import ToolCall 2 | 3 | __all__ = ["ToolCall"] 4 | -------------------------------------------------------------------------------- /src/mais4u/config/old/s4u_config_20250715_141713.toml: -------------------------------------------------------------------------------- 1 | [inner] 2 | version = "1.0.0" 3 | 4 | #----以下是S4U聊天系统配置文件---- 5 | # S4U (Smart 4 U) 聊天系统是MaiBot的核心对话模块 6 | # 支持优先级队列、消息中断、VIP用户等高级功能 7 | # 8 | # 如果你想要修改配置文件,请在修改后将version的值进行变更 9 | # 如果新增项目,请参考src/mais4u/s4u_config.py中的S4UConfig类 10 | # 11 | # 版本格式:主版本号.次版本号.修订号 12 | #----S4U配置说明结束---- 13 | 14 | [s4u] 15 | # 消息管理配置 16 | message_timeout_seconds = 120 # 普通消息存活时间(秒),超过此时间的消息将被丢弃 17 | recent_message_keep_count = 6 # 保留最近N条消息,超出范围的普通消息将被移除 18 | 19 | # 优先级系统配置 20 | at_bot_priority_bonus = 100.0 # @机器人时的优先级加成分数 21 | vip_queue_priority = true # 是否启用VIP队列优先级系统 22 | enable_message_interruption = true # 是否允许高优先级消息中断当前回复 23 | 24 | # 打字效果配置 25 | typing_delay = 0.1 # 打字延迟时间(秒),模拟真实打字速度 26 | enable_dynamic_typing_delay = false # 是否启用基于文本长度的动态打字延迟 27 | 28 | # 动态打字延迟参数(仅在enable_dynamic_typing_delay=true时生效) 29 | chars_per_second = 15.0 # 每秒字符数,用于计算动态打字延迟 30 | min_typing_delay = 0.2 # 最小打字延迟(秒) 31 | max_typing_delay = 2.0 # 最大打字延迟(秒) 32 | 33 | # 系统功能开关 34 | enable_old_message_cleanup = true # 是否自动清理过旧的普通消息 35 | enable_loading_indicator = true # 是否显示加载提示 36 | 37 | -------------------------------------------------------------------------------- /src/mais4u/config/s4u_config.toml: -------------------------------------------------------------------------------- 1 | [inner] 2 | version = "1.1.0" 3 | 4 | #----以下是S4U聊天系统配置文件---- 5 | # S4U (Smart 4 U) 聊天系统是MaiBot的核心对话模块 6 | # 支持优先级队列、消息中断、VIP用户等高级功能 7 | # 8 | # 如果你想要修改配置文件,请在修改后将version的值进行变更 9 | # 如果新增项目,请参考src/mais4u/s4u_config.py中的S4UConfig类 10 | # 11 | # 版本格式:主版本号.次版本号.修订号 12 | #----S4U配置说明结束---- 13 | 14 | [s4u] 15 | # 消息管理配置 16 | message_timeout_seconds = 80 # 普通消息存活时间(秒),超过此时间的消息将被丢弃 17 | recent_message_keep_count = 8 # 保留最近N条消息,超出范围的普通消息将被移除 18 | 19 | # 优先级系统配置 20 | at_bot_priority_bonus = 100.0 # @机器人时的优先级加成分数 21 | vip_queue_priority = true # 是否启用VIP队列优先级系统 22 | enable_message_interruption = true # 是否允许高优先级消息中断当前回复 23 | 24 | # 打字效果配置 25 | typing_delay = 0.1 # 打字延迟时间(秒),模拟真实打字速度 26 | enable_dynamic_typing_delay = false # 是否启用基于文本长度的动态打字延迟 27 | 28 | # 动态打字延迟参数(仅在enable_dynamic_typing_delay=true时生效) 29 | chars_per_second = 15.0 # 每秒字符数,用于计算动态打字延迟 30 | min_typing_delay = 0.2 # 最小打字延迟(秒) 31 | max_typing_delay = 2.0 # 最大打字延迟(秒) 32 | 33 | # 系统功能开关 34 | enable_old_message_cleanup = true # 是否自动清理过旧的普通消息 35 | enable_loading_indicator = true # 是否显示加载提示 36 | 37 | enable_streaming_output = false # 是否启用流式输出,false时全部生成后一次性发送 38 | 39 | max_context_message_length = 30 40 | max_core_message_length = 20 41 | 42 | # 模型配置 43 | [models] 44 | # 主要对话模型配置 45 | [models.chat] 46 | name = "qwen3-8b" 47 | provider = "BAILIAN" 48 | pri_in = 0.5 49 | pri_out = 2 50 | temp = 0.7 51 | enable_thinking = false 52 | 53 | # 规划模型配置 54 | [models.motion] 55 | name = "qwen3-8b" 56 | provider = "BAILIAN" 57 | pri_in = 0.5 58 | pri_out = 2 59 | temp = 0.7 60 | enable_thinking = false 61 | 62 | # 情感分析模型配置 63 | [models.emotion] 64 | name = "qwen3-8b" 65 | provider = "BAILIAN" 66 | pri_in = 0.5 67 | pri_out = 2 68 | temp = 0.7 69 | 70 | # 记忆模型配置 71 | [models.memory] 72 | name = "qwen3-8b" 73 | provider = "BAILIAN" 74 | pri_in = 0.5 75 | pri_out = 2 76 | temp = 0.7 77 | 78 | # 工具使用模型配置 79 | [models.tool_use] 80 | name = "qwen3-8b" 81 | provider = "BAILIAN" 82 | pri_in = 0.5 83 | pri_out = 2 84 | temp = 0.7 85 | 86 | # 嵌入模型配置 87 | [models.embedding] 88 | name = "text-embedding-v1" 89 | provider = "OPENAI" 90 | dimension = 1024 91 | 92 | # 视觉语言模型配置 93 | [models.vlm] 94 | name = "qwen-vl-plus" 95 | provider = "BAILIAN" 96 | pri_in = 0.5 97 | pri_out = 2 98 | temp = 0.7 99 | 100 | # 知识库模型配置 101 | [models.knowledge] 102 | name = "qwen3-8b" 103 | provider = "BAILIAN" 104 | pri_in = 0.5 105 | pri_out = 2 106 | temp = 0.7 107 | 108 | # 实体提取模型配置 109 | [models.entity_extract] 110 | name = "qwen3-8b" 111 | provider = "BAILIAN" 112 | pri_in = 0.5 113 | pri_out = 2 114 | temp = 0.7 115 | 116 | # 问答模型配置 117 | [models.qa] 118 | name = "qwen3-8b" 119 | provider = "BAILIAN" 120 | pri_in = 0.5 121 | pri_out = 2 122 | temp = 0.7 123 | 124 | # 兼容性配置(已废弃,请使用models.motion) 125 | [model_motion] # 在麦麦的一些组件中使用的小模型,消耗量较大,建议使用速度较快的小模型 126 | # 强烈建议使用免费的小模型 127 | name = "qwen3-8b" 128 | provider = "BAILIAN" 129 | pri_in = 0.5 130 | pri_out = 2 131 | temp = 0.7 132 | enable_thinking = false # 是否启用思考 -------------------------------------------------------------------------------- /src/mais4u/config/s4u_config_template.toml: -------------------------------------------------------------------------------- 1 | [inner] 2 | version = "1.1.0" 3 | 4 | #----以下是S4U聊天系统配置文件---- 5 | # S4U (Smart 4 U) 聊天系统是MaiBot的核心对话模块 6 | # 支持优先级队列、消息中断、VIP用户等高级功能 7 | # 8 | # 如果你想要修改配置文件,请在修改后将version的值进行变更 9 | # 如果新增项目,请参考src/mais4u/s4u_config.py中的S4UConfig类 10 | # 11 | # 版本格式:主版本号.次版本号.修订号 12 | #----S4U配置说明结束---- 13 | 14 | [s4u] 15 | # 消息管理配置 16 | message_timeout_seconds = 120 # 普通消息存活时间(秒),超过此时间的消息将被丢弃 17 | recent_message_keep_count = 6 # 保留最近N条消息,超出范围的普通消息将被移除 18 | 19 | # 优先级系统配置 20 | at_bot_priority_bonus = 100.0 # @机器人时的优先级加成分数 21 | vip_queue_priority = true # 是否启用VIP队列优先级系统 22 | enable_message_interruption = true # 是否允许高优先级消息中断当前回复 23 | 24 | # 打字效果配置 25 | typing_delay = 0.1 # 打字延迟时间(秒),模拟真实打字速度 26 | enable_dynamic_typing_delay = false # 是否启用基于文本长度的动态打字延迟 27 | 28 | # 动态打字延迟参数(仅在enable_dynamic_typing_delay=true时生效) 29 | chars_per_second = 15.0 # 每秒字符数,用于计算动态打字延迟 30 | min_typing_delay = 0.2 # 最小打字延迟(秒) 31 | max_typing_delay = 2.0 # 最大打字延迟(秒) 32 | 33 | # 系统功能开关 34 | enable_old_message_cleanup = true # 是否自动清理过旧的普通消息 35 | 36 | enable_streaming_output = true # 是否启用流式输出,false时全部生成后一次性发送 37 | 38 | max_context_message_length = 20 39 | max_core_message_length = 30 40 | 41 | # 模型配置 42 | [models] 43 | # 主要对话模型配置 44 | [models.chat] 45 | name = "qwen3-8b" 46 | provider = "BAILIAN" 47 | pri_in = 0.5 48 | pri_out = 2 49 | temp = 0.7 50 | enable_thinking = false 51 | 52 | # 规划模型配置 53 | [models.motion] 54 | name = "qwen3-32b" 55 | provider = "BAILIAN" 56 | pri_in = 0.5 57 | pri_out = 2 58 | temp = 0.7 59 | enable_thinking = false 60 | 61 | # 情感分析模型配置 62 | [models.emotion] 63 | name = "qwen3-8b" 64 | provider = "BAILIAN" 65 | pri_in = 0.5 66 | pri_out = 2 67 | temp = 0.7 68 | -------------------------------------------------------------------------------- /src/mais4u/constant_s4u.py: -------------------------------------------------------------------------------- 1 | ENABLE_S4U = False 2 | -------------------------------------------------------------------------------- /src/mais4u/mais4u_chat/internal_manager.py: -------------------------------------------------------------------------------- 1 | class InternalManager: 2 | def __init__(self): 3 | self.now_internal_state = "" 4 | 5 | def set_internal_state(self, internal_state: str): 6 | self.now_internal_state = internal_state 7 | 8 | def get_internal_state(self): 9 | return self.now_internal_state 10 | 11 | def get_internal_state_str(self): 12 | return f"你今天的直播内容是直播QQ水群,你正在一边回复弹幕,一边在QQ群聊天,你在QQ群聊天中产生的想法是:{self.now_internal_state}" 13 | 14 | 15 | internal_manager = InternalManager() 16 | -------------------------------------------------------------------------------- /src/mais4u/mais4u_chat/s4u_watching_manager.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | from src.plugin_system.apis import send_api 3 | 4 | """ 5 | 视线管理系统使用说明: 6 | 7 | 1. 视线状态: 8 | - wandering: 随意看 9 | - danmu: 看弹幕 10 | - lens: 看镜头 11 | 12 | 2. 状态切换逻辑: 13 | - 收到消息时 → 切换为看弹幕,立即发送更新 14 | - 开始生成回复时 → 切换为看镜头或随意,立即发送更新 15 | - 生成完毕后 → 看弹幕1秒,然后回到看镜头直到有新消息,状态变化时立即发送更新 16 | 17 | 3. 使用方法: 18 | # 获取视线管理器 19 | watching = watching_manager.get_watching_by_chat_id(chat_id) 20 | 21 | # 收到消息时调用 22 | await watching.on_message_received() 23 | 24 | # 开始生成回复时调用 25 | await watching.on_reply_start() 26 | 27 | # 生成回复完毕时调用 28 | await watching.on_reply_finished() 29 | 30 | 4. 自动更新系统: 31 | - 状态变化时立即发送type为"watching",data为状态值的websocket消息 32 | - 使用定时器自动处理状态转换(如看弹幕时间结束后自动切换到看镜头) 33 | - 无需定期检查,所有状态变化都是事件驱动的 34 | """ 35 | 36 | logger = get_logger("watching") 37 | 38 | HEAD_CODE = { 39 | "看向上方": "(0,0.5,0)", 40 | "看向下方": "(0,-0.5,0)", 41 | "看向左边": "(-1,0,0)", 42 | "看向右边": "(1,0,0)", 43 | "随意朝向": "random", 44 | "看向摄像机": "camera", 45 | "注视对方": "(0,0,0)", 46 | "看向正前方": "(0,0,0)", 47 | } 48 | 49 | 50 | class ChatWatching: 51 | def __init__(self, chat_id: str): 52 | self.chat_id: str = chat_id 53 | 54 | async def on_reply_start(self): 55 | """开始生成回复时调用""" 56 | await send_api.custom_to_stream( 57 | message_type="state", content="start_thinking", stream_id=self.chat_id, storage_message=False 58 | ) 59 | 60 | async def on_reply_finished(self): 61 | """生成回复完毕时调用""" 62 | await send_api.custom_to_stream( 63 | message_type="state", content="finish_reply", stream_id=self.chat_id, storage_message=False 64 | ) 65 | 66 | async def on_thinking_finished(self): 67 | """思考完毕时调用""" 68 | await send_api.custom_to_stream( 69 | message_type="state", content="finish_thinking", stream_id=self.chat_id, storage_message=False 70 | ) 71 | 72 | async def on_message_received(self): 73 | """收到消息时调用""" 74 | await send_api.custom_to_stream( 75 | message_type="state", content="start_viewing", stream_id=self.chat_id, storage_message=False 76 | ) 77 | 78 | async def on_internal_message_start(self): 79 | """收到消息时调用""" 80 | await send_api.custom_to_stream( 81 | message_type="state", content="start_internal_thinking", stream_id=self.chat_id, storage_message=False 82 | ) 83 | 84 | 85 | class WatchingManager: 86 | def __init__(self): 87 | self.watching_list: list[ChatWatching] = [] 88 | """当前视线状态列表""" 89 | self.task_started: bool = False 90 | 91 | def get_watching_by_chat_id(self, chat_id: str) -> ChatWatching: 92 | """获取或创建聊天对应的视线管理器""" 93 | for watching in self.watching_list: 94 | if watching.chat_id == chat_id: 95 | return watching 96 | 97 | new_watching = ChatWatching(chat_id) 98 | self.watching_list.append(new_watching) 99 | logger.info(f"为chat {chat_id}创建新的视线管理器") 100 | 101 | return new_watching 102 | 103 | 104 | # 全局视线管理器实例 105 | watching_manager = WatchingManager() 106 | """全局视线管理器""" 107 | -------------------------------------------------------------------------------- /src/mais4u/mais4u_chat/screen_manager.py: -------------------------------------------------------------------------------- 1 | class ScreenManager: 2 | def __init__(self): 3 | self.now_screen = "" 4 | 5 | def set_screen(self, screen_str: str): 6 | self.now_screen = screen_str 7 | 8 | def get_screen(self): 9 | return self.now_screen 10 | 11 | def get_screen_str(self): 12 | return f"你可以看见面前的屏幕,目前屏幕的内容是:现在千石可乐在和你一起直播,这是他正在操作的屏幕内容:{self.now_screen}" 13 | 14 | 15 | screen_manager = ScreenManager() 16 | -------------------------------------------------------------------------------- /src/mais4u/mais4u_chat/yes_or_no.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | from src.config.config import model_config 3 | from src.llm_models.utils_model import LLMRequest 4 | from src.plugin_system.apis import send_api 5 | 6 | logger = get_logger(__name__) 7 | 8 | head_actions_list = ["不做额外动作", "点头一次", "点头两次", "摇头", "歪脑袋", "低头望向一边"] 9 | 10 | 11 | async def yes_or_no_head(text: str, emotion: str = "", chat_history: str = "", chat_id: str = ""): 12 | prompt = f""" 13 | {chat_history} 14 | 以上是对方的发言: 15 | 16 | 对这个发言,你的心情是:{emotion} 17 | 对上面的发言,你的回复是:{text} 18 | 请判断时是否要伴随回复做头部动作,你可以选择: 19 | 20 | 不做额外动作 21 | 点头一次 22 | 点头两次 23 | 摇头 24 | 歪脑袋 25 | 低头望向一边 26 | 27 | 请从上面的动作中选择一个,并输出,请只输出你选择的动作就好,不要输出其他内容。""" 28 | model = LLMRequest(model_set=model_config.model_task_config.emotion, request_type="motion") 29 | 30 | try: 31 | # logger.info(f"prompt: {prompt}") 32 | response, _ = await model.generate_response_async(prompt=prompt, temperature=0.7) 33 | logger.info(f"response: {response}") 34 | 35 | head_action = response if response in head_actions_list else "不做额外动作" 36 | await send_api.custom_to_stream( 37 | message_type="head_action", 38 | content=head_action, 39 | stream_id=chat_id, 40 | storage_message=False, 41 | show_log=True, 42 | ) 43 | 44 | except Exception as e: 45 | logger.error(f"yes_or_no_head error: {e}") 46 | return "不做额外动作" 47 | -------------------------------------------------------------------------------- /src/manager/local_store_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import orjson 4 | 5 | from src.common.logger import get_logger 6 | 7 | LOCAL_STORE_FILE_PATH = "data/local_store.json" 8 | 9 | logger = get_logger("local_storage") 10 | 11 | 12 | class LocalStoreManager: 13 | file_path: str 14 | """本地存储路径""" 15 | 16 | store: dict[str, str | list | dict | int | float | bool] 17 | """本地存储数据""" 18 | 19 | def __init__(self, local_store_path: str | None = None): 20 | self.file_path = local_store_path or LOCAL_STORE_FILE_PATH 21 | self.store = {} 22 | self.load_local_store() 23 | 24 | def __getitem__(self, item: str) -> str | list | dict | int | float | bool | None: 25 | """获取本地存储数据""" 26 | return self.store.get(item) 27 | 28 | def __setitem__(self, key: str, value: str | list | dict | float | bool): 29 | """设置本地存储数据""" 30 | self.store[key] = value 31 | self.save_local_store() 32 | 33 | def __delitem__(self, key: str): 34 | """删除本地存储数据""" 35 | if key in self.store: 36 | del self.store[key] 37 | self.save_local_store() 38 | else: 39 | logger.warning(f"尝试删除不存在的键: {key}") 40 | 41 | def __contains__(self, item: str) -> bool: 42 | """检查本地存储数据是否存在""" 43 | return item in self.store 44 | 45 | def load_local_store(self): 46 | """加载本地存储数据""" 47 | if os.path.exists(self.file_path): 48 | # 存在本地存储文件,加载数据 49 | logger.info("正在阅读记事本......我在看,我真的在看!") 50 | logger.debug(f"加载本地存储数据: {self.file_path}") 51 | try: 52 | with open(self.file_path, encoding="utf-8") as f: 53 | self.store = orjson.loads(f.read()) 54 | logger.info("全都记起来了!") 55 | except orjson.JSONDecodeError: 56 | logger.warning("啊咧?记事本被弄脏了,正在重建记事本......") 57 | self.store = {} 58 | with open(self.file_path, "w", encoding="utf-8") as f: 59 | f.write(orjson.dumps({}, option=orjson.OPT_INDENT_2).decode("utf-8")) 60 | logger.info("记事本重建成功!") 61 | else: 62 | # 不存在本地存储文件,创建新的目录和文件 63 | logger.warning("啊咧?记事本不存在,正在创建新的记事本......") 64 | os.makedirs(os.path.dirname(self.file_path), exist_ok=True) 65 | with open(self.file_path, "w", encoding="utf-8") as f: 66 | f.write(orjson.dumps({}, option=orjson.OPT_INDENT_2).decode("utf-8")) 67 | logger.info("记事本创建成功!") 68 | 69 | def save_local_store(self): 70 | """保存本地存储数据""" 71 | logger.debug(f"保存本地存储数据: {self.file_path}") 72 | with open(self.file_path, "w", encoding="utf-8") as f: 73 | f.write(orjson.dumps(self.store, option=orjson.OPT_INDENT_2).decode("utf-8")) 74 | 75 | 76 | local_storage = LocalStoreManager("data/local_store.json") # 全局单例化 77 | -------------------------------------------------------------------------------- /src/person_info/relationship_builder_manager.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from src.common.logger import get_logger 4 | 5 | from .relationship_builder import RelationshipBuilder 6 | 7 | logger = get_logger("relationship_builder_manager") 8 | 9 | 10 | class RelationshipBuilderManager: 11 | """关系构建器管理器 12 | 13 | 简单的关系构建器存储和获取管理 14 | """ 15 | 16 | def __init__(self): 17 | self.builders: dict[str, RelationshipBuilder] = {} 18 | 19 | def get_or_create_builder(self, chat_id: str) -> RelationshipBuilder: 20 | """获取或创建关系构建器 21 | 22 | Args: 23 | chat_id: 聊天ID 24 | 25 | Returns: 26 | RelationshipBuilder: 关系构建器实例 27 | """ 28 | if chat_id not in self.builders: 29 | self.builders[chat_id] = RelationshipBuilder(chat_id) 30 | logger.debug(f"创建聊天 {chat_id} 的关系构建器") 31 | 32 | return self.builders[chat_id] 33 | 34 | def get_builder(self, chat_id: str) -> RelationshipBuilder | None: 35 | """获取关系构建器 36 | 37 | Args: 38 | chat_id: 聊天ID 39 | 40 | Returns: 41 | Optional[RelationshipBuilder]: 关系构建器实例或None 42 | """ 43 | return self.builders.get(chat_id) 44 | 45 | def remove_builder(self, chat_id: str) -> bool: 46 | """移除关系构建器 47 | 48 | Args: 49 | chat_id: 聊天ID 50 | 51 | Returns: 52 | bool: 是否成功移除 53 | """ 54 | if chat_id in self.builders: 55 | del self.builders[chat_id] 56 | logger.debug(f"移除聊天 {chat_id} 的关系构建器") 57 | return True 58 | return False 59 | 60 | def get_all_chat_ids(self) -> list[str]: 61 | """获取所有管理的聊天ID列表 62 | 63 | Returns: 64 | List[str]: 聊天ID列表 65 | """ 66 | return list(self.builders.keys()) 67 | 68 | def get_status(self) -> dict[str, Any]: 69 | """获取管理器状态 70 | 71 | Returns: 72 | Dict[str, any]: 状态信息 73 | """ 74 | return { 75 | "total_builders": len(self.builders), 76 | "chat_ids": list(self.builders.keys()), 77 | } 78 | 79 | async def process_chat_messages(self, chat_id: str): 80 | """处理指定聊天的消息 81 | 82 | Args: 83 | chat_id: 聊天ID 84 | """ 85 | builder = self.get_or_create_builder(chat_id) 86 | await builder.build_relation() 87 | 88 | async def force_cleanup_user(self, chat_id: str, person_id: str) -> bool: 89 | """强制清理指定用户的关系构建缓存 90 | 91 | Args: 92 | chat_id: 聊天ID 93 | person_id: 用户ID 94 | 95 | Returns: 96 | bool: 是否成功清理 97 | """ 98 | builder = self.get_builder(chat_id) 99 | return builder.force_cleanup_user_segments(person_id) if builder else False 100 | 101 | 102 | # 全局管理器实例 103 | relationship_builder_manager = RelationshipBuilderManager() 104 | -------------------------------------------------------------------------------- /src/plugin_system/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MaiBot 插件系统 3 | 4 | 提供统一的插件开发和管理框架 5 | """ 6 | 7 | # 导出主要的公共接口 8 | from .apis import ( 9 | chat_api, 10 | component_manage_api, 11 | config_api, 12 | database_api, 13 | emoji_api, 14 | generator_api, 15 | get_logger, 16 | llm_api, 17 | message_api, 18 | person_api, 19 | plugin_manage_api, 20 | register_plugin, 21 | send_api, 22 | tool_api, 23 | ) 24 | from .base import ( 25 | ActionActivationType, 26 | ActionInfo, 27 | BaseAction, 28 | BaseCommand, 29 | BaseEventHandler, 30 | BasePlugin, 31 | BaseTool, 32 | ChatMode, 33 | ChatType, 34 | CommandArgs, 35 | CommandInfo, 36 | ComponentInfo, 37 | ComponentType, 38 | ConfigField, 39 | EventHandlerInfo, 40 | EventType, 41 | MaiMessages, 42 | PluginInfo, 43 | # 新增的增强命令系统 44 | PlusCommand, 45 | PlusCommandAdapter, 46 | PlusCommandInfo, 47 | PythonDependency, 48 | ToolInfo, 49 | ToolParamType, 50 | create_plus_command_adapter, 51 | ) 52 | from .utils.dependency_config import configure_dependency_settings, get_dependency_config 53 | 54 | # 导入依赖管理模块 55 | from .utils.dependency_manager import configure_dependency_manager, get_dependency_manager 56 | 57 | __version__ = "2.0.0" 58 | 59 | __all__ = [ 60 | "ActionActivationType", 61 | "ActionInfo", 62 | "BaseAction", 63 | "BaseCommand", 64 | "BaseEventHandler", 65 | # 基础类 66 | "BasePlugin", 67 | "BaseTool", 68 | "ChatMode", 69 | "ChatType", 70 | "CommandArgs", 71 | "CommandInfo", 72 | "ComponentInfo", 73 | # 类型定义 74 | "ComponentType", 75 | "ConfigField", 76 | "EventHandlerInfo", 77 | "EventType", 78 | # 消息 79 | "MaiMessages", 80 | # 工具函数 81 | "ManifestValidator", 82 | "PluginInfo", 83 | # 增强命令系统 84 | "PlusCommand", 85 | "PlusCommandAdapter", 86 | "PythonDependency", 87 | "ToolInfo", 88 | "ToolParamType", 89 | # API 模块 90 | "chat_api", 91 | "component_manage_api", 92 | "config_api", 93 | "configure_dependency_manager", 94 | "configure_dependency_settings", 95 | "create_plus_command_adapter", 96 | "create_plus_command_adapter", 97 | "database_api", 98 | "emoji_api", 99 | "generator_api", 100 | "get_dependency_config", 101 | # 依赖管理 102 | "get_dependency_manager", 103 | "get_logger", 104 | "get_logger", 105 | "llm_api", 106 | "message_api", 107 | "person_api", 108 | "plugin_manage_api", 109 | "register_plugin", 110 | # 装饰器 111 | "register_plugin", 112 | "send_api", 113 | "tool_api", 114 | # "ManifestGenerator", 115 | # "validate_plugin_manifest", 116 | # "generate_plugin_manifest", 117 | ] 118 | -------------------------------------------------------------------------------- /src/plugin_system/apis/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 插件系统API模块 3 | 4 | 提供了插件开发所需的各种API 5 | """ 6 | 7 | # 导入所有API模块 8 | from src.plugin_system.apis import ( 9 | chat_api, 10 | component_manage_api, 11 | config_api, 12 | database_api, 13 | emoji_api, 14 | generator_api, 15 | llm_api, 16 | message_api, 17 | permission_api, 18 | person_api, 19 | plugin_manage_api, 20 | schedule_api, 21 | send_api, 22 | tool_api, 23 | ) 24 | from src.plugin_system.apis.chat_api import ChatManager as context_api 25 | 26 | from .logging_api import get_logger 27 | from .plugin_register_api import register_plugin 28 | 29 | # 导出所有API模块,使它们可以通过 apis.xxx 方式访问 30 | __all__ = [ 31 | "chat_api", 32 | "component_manage_api", 33 | "config_api", 34 | "context_api", 35 | "database_api", 36 | "emoji_api", 37 | "generator_api", 38 | "get_logger", 39 | "llm_api", 40 | "message_api", 41 | "permission_api", 42 | "person_api", 43 | "plugin_manage_api", 44 | "register_plugin", 45 | "schedule_api", 46 | "send_api", 47 | "tool_api", 48 | ] 49 | -------------------------------------------------------------------------------- /src/plugin_system/apis/config_api.py: -------------------------------------------------------------------------------- 1 | """配置API模块 2 | 3 | 提供了配置读取和用户信息获取等功能 4 | 使用方式: 5 | from src.plugin_system.apis import config_api 6 | value = config_api.get_global_config("section.key") 7 | platform, user_id = await config_api.get_user_id_by_person_name("用户名") 8 | """ 9 | 10 | from typing import Any 11 | 12 | from src.common.logger import get_logger 13 | from src.config.config import global_config 14 | 15 | logger = get_logger("config_api") 16 | 17 | 18 | # ============================================================================= 19 | # 配置访问API函数 20 | # ============================================================================= 21 | 22 | 23 | def get_global_config(key: str, default: Any = None) -> Any: 24 | """ 25 | 安全地从全局配置中获取一个值。 26 | 插件应使用此方法读取全局配置,以保证只读和隔离性。 27 | 28 | Args: 29 | key: 命名空间式配置键名,使用嵌套访问,如 "section.subsection.key",大小写敏感 30 | default: 如果配置不存在时返回的默认值 31 | 32 | Returns: 33 | Any: 配置值或默认值 34 | """ 35 | # 支持嵌套键访问 36 | keys = key.split(".") 37 | current = global_config 38 | 39 | try: 40 | for k in keys: 41 | if hasattr(current, k): 42 | current = getattr(current, k) 43 | else: 44 | raise KeyError(f"配置中不存在子空间或键 '{k}'") 45 | return current 46 | except Exception as e: 47 | logger.warning(f"[ConfigAPI] 获取全局配置 {key} 失败: {e}") 48 | return default 49 | 50 | 51 | def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any: 52 | """ 53 | 从插件配置中获取值,支持嵌套键访问 54 | 55 | Args: 56 | plugin_config: 插件配置字典 57 | key: 配置键名,支持嵌套访问如 "section.subsection.key",大小写敏感 58 | default: 如果配置不存在时返回的默认值 59 | 60 | Returns: 61 | Any: 配置值或默认值 62 | """ 63 | # 支持嵌套键访问 64 | keys = key.split(".") 65 | current = plugin_config 66 | 67 | try: 68 | for k in keys: 69 | if isinstance(current, dict) and k in current: 70 | current = current[k] 71 | elif hasattr(current, k): 72 | current = getattr(current, k) 73 | else: 74 | raise KeyError(f"配置中不存在子空间或键 '{k}'") 75 | return current 76 | except Exception as e: 77 | logger.warning(f"[ConfigAPI] 获取插件配置 {key} 失败: {e}") 78 | return default 79 | -------------------------------------------------------------------------------- /src/plugin_system/apis/database_api.py: -------------------------------------------------------------------------------- 1 | """数据库API模块 2 | 3 | 提供数据库操作相关功能,采用标准Python包设计模式 4 | 使用方式: 5 | from src.plugin_system.apis import database_api 6 | records = await database_api.db_query(ActionRecords, query_type="get") 7 | record = await database_api.db_save(ActionRecords, data={"action_id": "123"}) 8 | 9 | 注意:此模块现在使用SQLAlchemy实现,提供更好的连接管理和错误处理 10 | """ 11 | 12 | from src.common.database.sqlalchemy_database_api import MODEL_MAPPING, db_get, db_query, db_save, store_action_info 13 | 14 | # 保持向后兼容性 15 | __all__ = ["MODEL_MAPPING", "db_get", "db_query", "db_save", "store_action_info"] 16 | -------------------------------------------------------------------------------- /src/plugin_system/apis/logging_api.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | 3 | __all__ = ["get_logger"] 4 | -------------------------------------------------------------------------------- /src/plugin_system/apis/plugin_manage_api.py: -------------------------------------------------------------------------------- 1 | def list_loaded_plugins() -> list[str]: 2 | """ 3 | 列出所有当前加载的插件。 4 | 5 | Returns: 6 | List[str]: 当前加载的插件名称列表。 7 | """ 8 | from src.plugin_system.core.plugin_manager import plugin_manager 9 | 10 | return plugin_manager.list_loaded_plugins() 11 | 12 | 13 | def list_registered_plugins() -> list[str]: 14 | """ 15 | 列出所有已注册的插件。 16 | 17 | Returns: 18 | List[str]: 已注册的插件名称列表。 19 | """ 20 | from src.plugin_system.core.plugin_manager import plugin_manager 21 | 22 | return plugin_manager.list_registered_plugins() 23 | 24 | 25 | def get_plugin_path(plugin_name: str) -> str: 26 | """ 27 | 获取指定插件的路径。 28 | 29 | Args: 30 | plugin_name (str): 插件名称。 31 | 32 | Returns: 33 | str: 插件目录的绝对路径。 34 | 35 | Raises: 36 | ValueError: 如果插件不存在。 37 | """ 38 | from src.plugin_system.core.plugin_manager import plugin_manager 39 | 40 | if plugin_path := plugin_manager.get_plugin_path(plugin_name): 41 | return plugin_path 42 | else: 43 | raise ValueError(f"插件 '{plugin_name}' 不存在。") 44 | 45 | 46 | async def remove_plugin(plugin_name: str) -> bool: 47 | """ 48 | 卸载指定的插件。 49 | 50 | **此函数是异步的,确保在异步环境中调用。** 51 | 52 | Args: 53 | plugin_name (str): 要卸载的插件名称。 54 | 55 | Returns: 56 | bool: 卸载是否成功。 57 | """ 58 | from src.plugin_system.core.plugin_manager import plugin_manager 59 | 60 | return await plugin_manager.remove_registered_plugin(plugin_name) 61 | 62 | 63 | async def reload_plugin(plugin_name: str) -> bool: 64 | """ 65 | 重新加载指定的插件。 66 | 67 | **此函数是异步的,确保在异步环境中调用。** 68 | 69 | Args: 70 | plugin_name (str): 要重新加载的插件名称。 71 | 72 | Returns: 73 | bool: 重新加载是否成功。 74 | """ 75 | from src.plugin_system.core.plugin_manager import plugin_manager 76 | 77 | return await plugin_manager.reload_registered_plugin(plugin_name) 78 | 79 | 80 | def load_plugin(plugin_name: str) -> tuple[bool, int]: 81 | """ 82 | 加载指定的插件。 83 | 84 | Args: 85 | plugin_name (str): 要加载的插件名称。 86 | 87 | Returns: 88 | Tuple[bool, int]: 加载是否成功,成功或失败个数。 89 | """ 90 | from src.plugin_system.core.plugin_manager import plugin_manager 91 | 92 | return plugin_manager.load_registered_plugin_classes(plugin_name) 93 | 94 | 95 | def add_plugin_directory(plugin_directory: str) -> bool: 96 | """ 97 | 添加插件目录。 98 | 99 | Args: 100 | plugin_directory (str): 要添加的插件目录路径。 101 | Returns: 102 | bool: 添加是否成功。 103 | """ 104 | from src.plugin_system.core.plugin_manager import plugin_manager 105 | 106 | return plugin_manager.add_plugin_directory(plugin_directory) 107 | 108 | 109 | def rescan_plugin_directory() -> tuple[int, int]: 110 | """ 111 | 重新扫描插件目录,加载新插件。 112 | Returns: 113 | Tuple[int, int]: 成功加载的插件数量和失败的插件数量。 114 | """ 115 | from src.plugin_system.core.plugin_manager import plugin_manager 116 | 117 | return plugin_manager.rescan_plugin_directory() 118 | -------------------------------------------------------------------------------- /src/plugin_system/apis/plugin_register_api.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from src.common.logger import get_logger 4 | 5 | logger = get_logger("plugin_manager") # 复用plugin_manager名称 6 | 7 | 8 | def register_plugin(cls): 9 | from src.plugin_system.base.base_plugin import BasePlugin 10 | from src.plugin_system.core.plugin_manager import plugin_manager 11 | 12 | """插件注册装饰器 13 | 14 | 用法: 15 | @register_plugin 16 | class MyPlugin(BasePlugin): 17 | plugin_name = "my_plugin" 18 | plugin_description = "我的插件" 19 | ... 20 | """ 21 | if not issubclass(cls, BasePlugin): 22 | logger.error(f"类 {cls.__name__} 不是 BasePlugin 的子类") 23 | return cls 24 | 25 | # 只是注册插件类,不立即实例化 26 | # 插件管理器会负责实例化和注册 27 | plugin_name: str = cls.plugin_name # type: ignore 28 | if "." in plugin_name: 29 | logger.error(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代") 30 | raise ValueError(f"插件名称 '{plugin_name}' 包含非法字符 '.',请使用下划线替代") 31 | splitted_name = cls.__module__.split(".") 32 | root_path = Path(__file__) 33 | 34 | # 查找项目根目录 35 | while not (root_path / "pyproject.toml").exists() and root_path.parent != root_path: 36 | root_path = root_path.parent 37 | 38 | if not (root_path / "pyproject.toml").exists(): 39 | logger.error(f"注册 {plugin_name} 无法找到项目根目录") 40 | return cls 41 | 42 | plugin_manager.plugin_classes[plugin_name] = cls 43 | plugin_manager.plugin_paths[plugin_name] = str(Path(root_path, *splitted_name).resolve()) 44 | logger.debug(f"插件类已注册: {plugin_name}, 路径: {plugin_manager.plugin_paths[plugin_name]}") 45 | 46 | return cls 47 | -------------------------------------------------------------------------------- /src/plugin_system/apis/scoring_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | 统一评分系统API 3 | 提供系统级的关系分和兴趣管理服务,供所有插件和主项目组件使用 4 | """ 5 | 6 | from typing import Any 7 | 8 | from src.common.logger import get_logger 9 | from src.plugin_system.services.interest_service import interest_service 10 | from src.plugin_system.services.relationship_service import relationship_service 11 | 12 | logger = get_logger("scoring_api") 13 | 14 | 15 | class ScoringAPI: 16 | """ 17 | 统一评分系统API - 系统级服务 18 | 19 | 提供关系分和兴趣管理的统一接口,替代原有的插件依赖方式。 20 | 所有插件和主项目组件都应该通过此API访问评分功能。 21 | """ 22 | 23 | @staticmethod 24 | async def get_user_relationship_score(user_id: str) -> float: 25 | """ 26 | 获取用户关系分 27 | 28 | Args: 29 | user_id: 用户ID 30 | 31 | Returns: 32 | 关系分 (0.0 - 1.0) 33 | """ 34 | return await relationship_service.get_user_relationship_score(user_id) 35 | 36 | @staticmethod 37 | async def get_user_relationship_data(user_id: str) -> dict: 38 | """ 39 | 获取用户完整关系数据 40 | 41 | Args: 42 | user_id: 用户ID 43 | 44 | Returns: 45 | 包含关系分、关系文本等的字典 46 | """ 47 | return await relationship_service.get_user_relationship_data(user_id) 48 | 49 | @staticmethod 50 | async def update_user_relationship(user_id: str, relationship_score: float, relationship_text: str = None, user_name: str = None): 51 | """ 52 | 更新用户关系数据 53 | 54 | Args: 55 | user_id: 用户ID 56 | relationship_score: 关系分 (0.0 - 1.0) 57 | relationship_text: 关系描述文本 58 | user_name: 用户名称 59 | """ 60 | await relationship_service.update_user_relationship(user_id, relationship_score, relationship_text, user_name) 61 | 62 | @staticmethod 63 | async def initialize_smart_interests(personality_description: str, personality_id: str = "default"): 64 | """ 65 | 初始化智能兴趣系统 66 | 67 | Args: 68 | personality_description: 机器人性格描述 69 | personality_id: 性格ID 70 | """ 71 | await interest_service.initialize_smart_interests(personality_description, personality_id) 72 | 73 | @staticmethod 74 | async def calculate_interest_match(content: str, keywords: list[str] = None): 75 | """ 76 | 计算内容与兴趣的匹配度 77 | 78 | Args: 79 | content: 消息内容 80 | keywords: 关键词列表 81 | 82 | Returns: 83 | 匹配结果 84 | """ 85 | return await interest_service.calculate_interest_match(content, keywords) 86 | 87 | @staticmethod 88 | def get_system_stats() -> dict[str, Any]: 89 | """ 90 | 获取系统统计信息 91 | 92 | Returns: 93 | 包含各子系统统计的字典 94 | """ 95 | return { 96 | "relationship_service": relationship_service.get_cache_stats(), 97 | "interest_service": interest_service.get_interest_stats() 98 | } 99 | 100 | @staticmethod 101 | def clear_caches(user_id: str = None): 102 | """ 103 | 清理缓存 104 | 105 | Args: 106 | user_id: 特定用户ID,如果为None则清理所有缓存 107 | """ 108 | relationship_service.clear_cache(user_id) 109 | logger.info(f"清理缓存: {user_id if user_id else '全部'}") 110 | 111 | 112 | # 创建全局API实例 - 系统级服务 113 | scoring_api = ScoringAPI() 114 | -------------------------------------------------------------------------------- /src/plugin_system/apis/tool_api.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | from src.plugin_system.base.base_tool import BaseTool 3 | from src.plugin_system.base.component_types import ComponentType 4 | 5 | logger = get_logger("tool_api") 6 | 7 | 8 | def get_tool_instance(tool_name: str) -> BaseTool | None: 9 | """获取公开工具实例""" 10 | from src.plugin_system.core import component_registry 11 | 12 | # 获取插件配置 13 | tool_info = component_registry.get_component_info(tool_name, ComponentType.TOOL) 14 | if tool_info: 15 | plugin_config = component_registry.get_plugin_config(tool_info.plugin_name) 16 | else: 17 | plugin_config = None 18 | 19 | tool_class: type[BaseTool] = component_registry.get_component_class(tool_name, ComponentType.TOOL) # type: ignore 20 | if tool_class: 21 | return tool_class(plugin_config) 22 | 23 | # 如果不是常规工具,检查是否是MCP工具 24 | # MCP工具不需要返回实例,会在execute_tool_call中特殊处理 25 | return None 26 | 27 | 28 | def get_llm_available_tool_definitions(): 29 | """获取LLM可用的工具定义列表 30 | 31 | Returns: 32 | List[Tuple[str, Dict[str, Any]]]: 工具定义列表,为[("tool_name", 定义)] 33 | """ 34 | from src.plugin_system.core import component_registry 35 | 36 | llm_available_tools = component_registry.get_llm_available_tools() 37 | tool_definitions = [(name, tool_class.get_tool_definition()) for name, tool_class in llm_available_tools.items()] 38 | 39 | # 添加MCP工具 40 | try: 41 | from src.plugin_system.utils.mcp_tool_provider import mcp_tool_provider 42 | 43 | mcp_tools = mcp_tool_provider.get_mcp_tool_definitions() 44 | tool_definitions.extend(mcp_tools) 45 | if mcp_tools: 46 | logger.debug(f"已添加 {len(mcp_tools)} 个MCP工具到可用工具列表") 47 | except Exception as e: 48 | logger.debug(f"获取MCP工具失败(可能未配置): {e}") 49 | 50 | return tool_definitions 51 | -------------------------------------------------------------------------------- /src/plugin_system/base/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 插件基础类模块 3 | 4 | 提供插件开发的基础类和类型定义 5 | """ 6 | 7 | from .base_action import BaseAction 8 | from .base_command import BaseCommand 9 | from .base_events_handler import BaseEventHandler 10 | from .base_plugin import BasePlugin 11 | from .base_tool import BaseTool 12 | from .command_args import CommandArgs 13 | from .component_types import ( 14 | ActionActivationType, 15 | ActionInfo, 16 | ChatMode, 17 | ChatType, 18 | CommandInfo, 19 | ComponentInfo, 20 | ComponentType, 21 | EventHandlerInfo, 22 | EventType, 23 | MaiMessages, 24 | PluginInfo, 25 | PlusCommandInfo, 26 | PythonDependency, 27 | ToolInfo, 28 | ToolParamType, 29 | ) 30 | from .config_types import ConfigField 31 | from .plus_command import PlusCommand, PlusCommandAdapter, create_plus_command_adapter 32 | 33 | __all__ = [ 34 | "ActionActivationType", 35 | "ActionInfo", 36 | "BaseAction", 37 | "BaseCommand", 38 | "BaseEventHandler", 39 | "BasePlugin", 40 | "BaseTool", 41 | "ChatMode", 42 | "ChatType", 43 | "CommandArgs", 44 | "CommandInfo", 45 | "ComponentInfo", 46 | "ComponentType", 47 | "ConfigField", 48 | "EventHandlerInfo", 49 | "EventType", 50 | "MaiMessages", 51 | "PluginInfo", 52 | # 增强命令系统 53 | "PlusCommand", 54 | "PlusCommandAdapter", 55 | "PlusCommandInfo", 56 | "PythonDependency", 57 | "ToolInfo", 58 | "ToolParamType", 59 | "create_plus_command_adapter", 60 | ] 61 | -------------------------------------------------------------------------------- /src/plugin_system/base/base_chatter.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import TYPE_CHECKING 3 | 4 | from src.common.data_models.message_manager_data_model import StreamContext 5 | from src.plugin_system.base.component_types import ChatterInfo, ComponentType 6 | 7 | from .component_types import ChatType 8 | 9 | if TYPE_CHECKING: 10 | from src.chat.planner_actions.action_manager import ChatterActionManager 11 | 12 | 13 | class BaseChatter(ABC): 14 | chatter_name: str = "" 15 | """Chatter组件的名称""" 16 | chatter_description: str = "" 17 | """Chatter组件的描述""" 18 | chat_types: list[ChatType] = [ChatType.PRIVATE, ChatType.GROUP] 19 | 20 | def __init__(self, stream_id: str, action_manager: "ChatterActionManager"): 21 | """ 22 | 初始化聊天处理器 23 | 24 | Args: 25 | stream_id: 聊天流ID 26 | action_manager: 动作管理器 27 | """ 28 | self.stream_id = stream_id 29 | self.action_manager = action_manager 30 | 31 | @abstractmethod 32 | async def execute(self, context: StreamContext) -> dict: 33 | """ 34 | 执行聊天处理流程 35 | 36 | Args: 37 | context: StreamContext对象,包含聊天流的所有消息信息 38 | 39 | Returns: 40 | 处理结果字典 41 | """ 42 | pass 43 | 44 | @classmethod 45 | def get_chatter_info(cls) -> "ChatterInfo": 46 | """从类属性生成ChatterInfo 47 | Returns: 48 | ChatterInfo对象 49 | """ 50 | 51 | return ChatterInfo( 52 | name=cls.chatter_name, 53 | description=cls.chatter_description or "No description provided.", 54 | chat_type_allow=cls.chat_types[0], 55 | component_type=ComponentType.CHATTER, 56 | ) 57 | -------------------------------------------------------------------------------- /src/plugin_system/base/config_types.py: -------------------------------------------------------------------------------- 1 | """ 2 | 插件系统配置类型定义 3 | """ 4 | 5 | from dataclasses import dataclass, field 6 | from typing import Any 7 | 8 | 9 | @dataclass 10 | class ConfigField: 11 | """配置字段定义""" 12 | 13 | type: type # 字段类型 14 | default: Any # 默认值 15 | description: str # 字段描述 16 | example: str | None = None # 示例值 17 | required: bool = False # 是否必需 18 | choices: list[Any] | None = field(default_factory=list) # 可选值列表 19 | -------------------------------------------------------------------------------- /src/plugin_system/base/plugin_metadata.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Any 3 | 4 | 5 | @dataclass 6 | class PluginMetadata: 7 | """ 8 | 插件元数据,用于存储插件的开发者信息和用户帮助信息。 9 | """ 10 | 11 | name: str # 插件名称 (供用户查看) 12 | description: str # 插件功能描述 13 | usage: str # 插件使用方法 14 | 15 | # 以下为可选字段,参考自 _manifest.json 和 NoneBot 设计 16 | type: str | None = None # 插件类别: "library", "application" 17 | 18 | # 从原 _manifest.json 迁移的字段 19 | version: str = "1.0.0" # 插件版本 20 | author: str = "" # 作者名称 21 | license: str | None = None # 开源协议 22 | repository_url: str | None = None # 仓库地址 23 | keywords: list[str] = field(default_factory=list) # 关键词 24 | categories: list[str] = field(default_factory=list) # 分类 25 | 26 | # 扩展字段 27 | extra: dict[str, Any] = field(default_factory=dict) # 其他任意信息 28 | -------------------------------------------------------------------------------- /src/plugin_system/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 插件核心管理模块 3 | 4 | 提供插件的加载、注册和管理功能 5 | """ 6 | 7 | from src.plugin_system.core.component_registry import component_registry 8 | from src.plugin_system.core.event_manager import event_manager 9 | from src.plugin_system.core.global_announcement_manager import global_announcement_manager 10 | from src.plugin_system.core.plugin_manager import plugin_manager 11 | 12 | __all__ = [ 13 | "component_registry", 14 | "event_manager", 15 | "global_announcement_manager", 16 | "plugin_manager", 17 | ] 18 | -------------------------------------------------------------------------------- /src/plugin_system/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 插件系统工具模块 3 | 4 | 提供插件开发和管理的实用工具 5 | """ 6 | -------------------------------------------------------------------------------- /src/plugin_system/utils/dependency_config.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | 3 | logger = get_logger("dependency_config") 4 | 5 | 6 | class DependencyConfig: 7 | """依赖管理配置类 - 现在使用全局配置""" 8 | 9 | def __init__(self, global_config=None): 10 | self._global_config = global_config 11 | 12 | def _get_config(self): 13 | """获取全局配置对象""" 14 | if self._global_config is not None: 15 | return self._global_config 16 | 17 | # 延迟导入以避免循环依赖 18 | try: 19 | from src.config.config import global_config 20 | 21 | return global_config 22 | except ImportError: 23 | logger.warning("无法导入全局配置,使用默认设置") 24 | return None 25 | 26 | @property 27 | def auto_install(self) -> bool: 28 | """是否启用自动安装""" 29 | config = self._get_config() 30 | if config and hasattr(config, "dependency_management"): 31 | return config.dependency_management.auto_install 32 | return True 33 | 34 | @property 35 | def use_mirror(self) -> bool: 36 | """是否使用PyPI镜像源""" 37 | config = self._get_config() 38 | if config and hasattr(config, "dependency_management"): 39 | return config.dependency_management.use_mirror 40 | return False 41 | 42 | @property 43 | def mirror_url(self) -> str: 44 | """PyPI镜像源URL""" 45 | config = self._get_config() 46 | if config and hasattr(config, "dependency_management"): 47 | return config.dependency_management.mirror_url 48 | return "" 49 | 50 | @property 51 | def install_timeout(self) -> int: 52 | """安装超时时间(秒)""" 53 | config = self._get_config() 54 | if config and hasattr(config, "dependency_management"): 55 | return config.dependency_management.auto_install_timeout 56 | return 300 57 | 58 | @property 59 | def prompt_before_install(self) -> bool: 60 | """安装前是否提示用户""" 61 | config = self._get_config() 62 | if config and hasattr(config, "dependency_management"): 63 | return config.dependency_management.prompt_before_install 64 | return False 65 | 66 | 67 | # 全局配置实例 68 | _global_dependency_config: DependencyConfig | None = None 69 | 70 | 71 | def get_dependency_config() -> DependencyConfig: 72 | """获取全局依赖配置实例""" 73 | global _global_dependency_config 74 | if _global_dependency_config is None: 75 | _global_dependency_config = DependencyConfig() 76 | return _global_dependency_config 77 | 78 | 79 | def configure_dependency_settings(**kwargs) -> None: 80 | """配置依赖管理设置 - 注意:这个函数现在仅用于兼容性,实际配置需要修改bot_config.toml""" 81 | logger.info("依赖管理设置现在通过 bot_config.toml 的 [dependency_management] 节进行配置") 82 | logger.info(f"请求的配置更改: {kwargs}") 83 | logger.warning("configure_dependency_settings 函数仅用于兼容性,配置更改不会持久化") 84 | -------------------------------------------------------------------------------- /src/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | """插件系统包""" 2 | -------------------------------------------------------------------------------- /src/plugins/built_in/affinity_flow_chatter/README.md: -------------------------------------------------------------------------------- 1 | # 亲和力聊天处理器插件 2 | 3 | ## 概述 4 | 5 | 这是一个内置的chatter插件,实现了基于亲和力流的智能聊天处理器,具有兴趣度评分和人物关系构建功能。 6 | 7 | ## 功能特性 8 | 9 | - **智能兴趣度评分**: 自动识别和评估用户兴趣话题 10 | - **人物关系系统**: 根据互动历史建立和维持用户关系 11 | - **多聊天类型支持**: 支持私聊和群聊场景 12 | - **插件化架构**: 完全集成到插件系统中 13 | 14 | ## 组件架构 15 | 16 | ### BaseChatter (抽象基类) 17 | - 位置: `src/plugin_system/base/base_chatter.py` 18 | - 功能: 定义所有chatter组件的基础接口 19 | - 必须实现的方法: `execute(context: StreamContext) -> dict` 20 | 21 | ### ChatterManager (管理器) 22 | - 位置: `src/chat/chatter_manager.py` 23 | - 功能: 管理和调度所有chatter组件 24 | - 特性: 自动从插件系统注册和发现chatter组件 25 | 26 | ### AffinityChatter (具体实现) 27 | - 位置: `src/plugins/built_in/chatter/affinity_chatter.py` 28 | - 功能: 亲和力流聊天处理器的具体实现 29 | - 支持的聊天类型: PRIVATE, GROUP 30 | 31 | ## 使用方法 32 | 33 | ### 1. 基本使用 34 | 35 | ```python 36 | from src.chat.chatter_manager import ChatterManager 37 | from src.chat.planner_actions.action_manager import ChatterActionManager 38 | 39 | # 初始化 40 | action_manager = ChatterActionManager() 41 | chatter_manager = ChatterManager(action_manager) 42 | 43 | # 处理消息流 44 | result = await chatter_manager.process_stream_context(stream_id, context) 45 | ``` 46 | 47 | ### 2. 创建自定义Chatter 48 | 49 | ```python 50 | from src.plugin_system.base.base_chatter import BaseChatter 51 | from src.plugin_system.base.component_types import ChatType, ComponentType 52 | from src.plugin_system.base.component_types import ChatterInfo 53 | 54 | class CustomChatter(BaseChatter): 55 | chat_types = [ChatType.PRIVATE] # 只支持私聊 56 | 57 | async def execute(self, context: StreamContext) -> dict: 58 | # 实现你的聊天逻辑 59 | return {"success": True, "message": "处理完成"} 60 | 61 | # 在插件中注册 62 | async def on_load(self): 63 | chatter_info = ChatterInfo( 64 | name="custom_chatter", 65 | component_type=ComponentType.CHATTER, 66 | description="自定义聊天处理器", 67 | enabled=True, 68 | plugin_name=self.name, 69 | chat_type_allow=ChatType.PRIVATE 70 | ) 71 | 72 | ComponentRegistry.register_component( 73 | component_info=chatter_info, 74 | component_class=CustomChatter 75 | ) 76 | ``` 77 | 78 | ## 配置 79 | 80 | ### 插件配置文件 81 | - 位置: `src/plugins/built_in/chatter/_manifest.json` 82 | - 包含插件信息和组件配置 83 | 84 | ### 聊天类型 85 | - `PRIVATE`: 私聊 86 | - `GROUP`: 群聊 87 | - `ALL`: 所有类型 88 | 89 | ## 核心概念 90 | 91 | ### 1. 兴趣值系统 92 | - 自动识别同类话题 93 | - 兴趣值会根据聊天频率增减 94 | - 支持新话题的自动学习 95 | 96 | ### 2. 人物关系系统 97 | - 根据互动质量建立关系分 98 | - 不同关系分对应不同的回复风格 99 | - 支持情感化的交流 100 | 101 | ### 3. 执行流程 102 | 1. 接收StreamContext 103 | 2. 使用ActionPlanner进行规划 104 | 3. 执行相应的Action 105 | 4. 返回处理结果 106 | 107 | ## 扩展开发 108 | 109 | ### 添加新的Chatter类型 110 | 1. 继承BaseChatter类 111 | 2. 实现execute方法 112 | 3. 在插件中注册组件 113 | 4. 配置支持的聊天类型 114 | 115 | ### 集成现有功能 116 | - 使用ActionPlanner进行动作规划 117 | - 通过ActionManager执行动作 118 | - 利用现有的记忆和知识系统 119 | 120 | ## 注意事项 121 | 122 | 1. 所有chatter组件必须实现`execute`方法 123 | 2. 插件注册时需要指定支持的聊天类型 124 | 3. 组件名称不能包含点号(.) 125 | 4. 确保在插件卸载时正确清理资源 -------------------------------------------------------------------------------- /src/plugins/built_in/affinity_flow_chatter/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="Affinity Flow Chatter", 5 | description="Built-in chatter plugin for affinity flow with interest scoring and relationship building", 6 | usage="This plugin is automatically triggered by the system.", 7 | version="1.0.0", 8 | author="MoFox", 9 | keywords=["chatter", "affinity", "conversation"], 10 | categories=["Chat", "AI"], 11 | extra={"is_built_in": True}, 12 | ) 13 | -------------------------------------------------------------------------------- /src/plugins/built_in/affinity_flow_chatter/plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | 亲和力聊天处理器插件(包含兴趣计算器功能) 3 | """ 4 | 5 | from src.common.logger import get_logger 6 | from src.plugin_system.apis.plugin_register_api import register_plugin 7 | from src.plugin_system.base.base_plugin import BasePlugin 8 | from src.plugin_system.base.component_types import ComponentInfo 9 | 10 | logger = get_logger("affinity_chatter_plugin") 11 | 12 | 13 | @register_plugin 14 | class AffinityChatterPlugin(BasePlugin): 15 | """亲和力聊天处理器插件 16 | 17 | - 延迟导入 `AffinityChatter` 并通过组件注册器注册为聊天处理器 18 | - 延迟导入 `AffinityInterestCalculator` 并通过组件注册器注册为兴趣计算器 19 | - 提供 `get_plugin_components` 以兼容插件注册机制 20 | """ 21 | 22 | plugin_name: str = "affinity_chatter" 23 | enable_plugin: bool = True 24 | dependencies: list[str] = [] 25 | python_dependencies: list[str] = [] 26 | config_file_name: str = "" 27 | 28 | # 简单的 config_schema 占位(如果将来需要配置可扩展) 29 | config_schema = {} 30 | 31 | def get_plugin_components(self) -> list[tuple[ComponentInfo, type]]: 32 | """返回插件包含的组件列表 33 | 34 | 这里采用延迟导入以避免循环依赖和启动顺序问题。 35 | 如果导入失败则返回空列表以让注册过程继续而不崩溃。 36 | """ 37 | components = [] 38 | 39 | try: 40 | # 延迟导入 AffinityChatter 41 | from .affinity_chatter import AffinityChatter 42 | 43 | components.append((AffinityChatter.get_chatter_info(), AffinityChatter)) 44 | except Exception as e: 45 | logger.error(f"加载 AffinityChatter 时出错: {e}") 46 | 47 | try: 48 | # 延迟导入 AffinityInterestCalculator 49 | from .affinity_interest_calculator import AffinityInterestCalculator 50 | 51 | components.append((AffinityInterestCalculator.get_interest_calculator_info(), AffinityInterestCalculator)) 52 | except Exception as e: 53 | logger.error(f"加载 AffinityInterestCalculator 时出错: {e}") 54 | 55 | return components 56 | -------------------------------------------------------------------------------- /src/plugins/built_in/core_actions/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="Emoji插件 (Emoji Actions)", 5 | description="可以发送和管理Emoji", 6 | usage="该插件提供 `emoji` action。", 7 | version="1.0.0", 8 | author="SengokuCola", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/MaiM-with-u/maibot", 11 | keywords=["emoji", "action", "built-in"], 12 | categories=["Emoji"], 13 | extra={ 14 | "is_built_in": True, 15 | "plugin_type": "action_provider", 16 | }, 17 | ) 18 | -------------------------------------------------------------------------------- /src/plugins/built_in/core_actions/anti_injector_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | 反注入系统管理命令插件 3 | 4 | 提供管理和监控反注入系统的命令接口,包括: 5 | - 系统状态查看 6 | - 配置修改 7 | - 统计信息查看 8 | - 测试功能 9 | """ 10 | 11 | from src.chat.antipromptinjector import get_anti_injector 12 | from src.common.logger import get_logger 13 | from src.plugin_system.base import BaseCommand 14 | 15 | logger = get_logger("anti_injector.commands") 16 | 17 | 18 | class AntiInjectorStatusCommand(BaseCommand): 19 | """反注入系统状态查看命令""" 20 | 21 | command_name = "反注入状态" # 命令名称,作为唯一标识符 22 | command_description = "查看反注入系统状态和统计信息" # 命令描述 23 | command_pattern = r"^/反注入状态$" # 命令匹配的正则表达式 24 | 25 | async def execute(self) -> tuple[bool, str, bool]: 26 | try: 27 | anti_injector = get_anti_injector() 28 | stats = await anti_injector.get_stats() 29 | 30 | # 检查反注入系统是否禁用 31 | if stats.get("status") == "disabled": 32 | await self.send_text("❌ 反注入系统未启用\n\n💡 请在配置文件中启用反注入功能后重试") 33 | return True, "反注入系统未启用", True 34 | 35 | if stats.get("error"): 36 | await self.send_text(f"❌ 获取状态失败: {stats['error']}") 37 | return False, f"获取状态失败: {stats['error']}", True 38 | 39 | status_text = f"""🛡️ 反注入系统状态报告 40 | 41 | 📊 运行统计: 42 | • 运行时间: {stats["uptime"]} 43 | • 处理消息总数: {stats["total_messages"]} 44 | • 检测到注入: {stats["detected_injections"]} 45 | • 阻止消息: {stats["blocked_messages"]} 46 | • 加盾消息: {stats["shielded_messages"]} 47 | 48 | 📈 性能指标: 49 | • 检测率: {stats["detection_rate"]} 50 | • 平均处理时间: {stats["average_processing_time"]} 51 | • 最后处理时间: {stats["last_processing_time"]} 52 | 53 | ⚠️ 错误计数: {stats["error_count"]}""" 54 | await self.send_text(status_text) 55 | return True, status_text, True 56 | 57 | except Exception as e: 58 | logger.error(f"获取反注入系统状态失败: {e}") 59 | await self.send_text(f"获取状态失败: {e!s}") 60 | return False, f"获取状态失败: {e!s}", True 61 | -------------------------------------------------------------------------------- /src/plugins/built_in/core_actions/plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | 核心动作插件 3 | 4 | 将系统核心动作(reply、no_reply、emoji)转换为新插件系统格式 5 | 这是系统的内置插件,提供基础的聊天交互功能 6 | """ 7 | 8 | # 导入依赖的系统组件 9 | from src.common.logger import get_logger 10 | 11 | # 导入新插件系统 12 | from src.plugin_system import BasePlugin, ComponentInfo, register_plugin 13 | from src.plugin_system.base.config_types import ConfigField 14 | from src.plugins.built_in.core_actions.anti_injector_manager import AntiInjectorStatusCommand 15 | 16 | # 导入API模块 - 标准Python包方式 17 | from src.plugins.built_in.core_actions.emoji import EmojiAction 18 | 19 | logger = get_logger("core_actions") 20 | 21 | 22 | @register_plugin 23 | class CoreActionsPlugin(BasePlugin): 24 | """核心动作插件 25 | 26 | 系统内置插件,提供基础的聊天交互功能: 27 | - Reply: 回复动作 28 | - NoReply: 不回复动作 29 | - Emoji: 表情动作 30 | 31 | 注意:插件基本信息优先从_manifest.json文件中读取 32 | """ 33 | 34 | # 插件基本信息 35 | plugin_name: str = "core_actions" # 内部标识符 36 | enable_plugin: bool = True 37 | dependencies: list[str] = [] # 插件依赖列表 38 | python_dependencies: list[str] = [] # Python包依赖列表 39 | config_file_name: str = "config.toml" 40 | 41 | # 配置节描述 42 | config_section_descriptions = { 43 | "plugin": "插件启用配置", 44 | "components": "核心组件启用配置", 45 | } 46 | 47 | # 配置Schema定义 48 | config_schema: dict = { 49 | "plugin": { 50 | "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), 51 | "config_version": ConfigField(type=str, default="0.6.0", description="配置文件版本"), 52 | }, 53 | "components": { 54 | "enable_reply": ConfigField(type=bool, default=True, description="是否启用基本回复动作"), 55 | "enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"), 56 | "enable_anti_injector_manager": ConfigField( 57 | type=bool, default=True, description="是否启用反注入系统管理命令" 58 | ), 59 | }, 60 | } 61 | 62 | def get_plugin_components(self) -> list[tuple[ComponentInfo, type]]: 63 | """返回插件包含的组件列表""" 64 | 65 | # --- 根据配置注册组件 --- 66 | components = [] 67 | if self.get_config("components.enable_emoji", True): 68 | components.append((EmojiAction.get_action_info(), EmojiAction)) 69 | if self.get_config("components.enable_anti_injector_manager", True): 70 | components.append((AntiInjectorStatusCommand.get_command_info(), AntiInjectorStatusCommand)) 71 | 72 | return components 73 | -------------------------------------------------------------------------------- /src/plugins/built_in/knowledge/lpmm_get_knowledge.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from src.chat.knowledge.knowledge_lib import qa_manager 4 | from src.common.logger import get_logger 5 | from src.config.config import global_config 6 | from src.plugin_system import BaseTool, ToolParamType 7 | 8 | logger = get_logger("lpmm_get_knowledge_tool") 9 | 10 | 11 | class SearchKnowledgeFromLPMMTool(BaseTool): 12 | """从LPMM知识库中搜索相关信息的工具""" 13 | 14 | name = "lpmm_search_knowledge" 15 | description = "从知识库中搜索相关信息,如果你需要知识,就使用这个工具" 16 | parameters = [ 17 | ("query", ToolParamType.STRING, "搜索查询关键词", True, None), 18 | ("threshold", ToolParamType.FLOAT, "相似度阈值,0.0到1.0之间", False, None), 19 | ] 20 | available_for_llm = global_config.lpmm_knowledge.enable 21 | 22 | async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]: 23 | """执行知识库搜索 24 | 25 | Args: 26 | function_args: 工具参数 27 | 28 | Returns: 29 | Dict: 工具执行结果 30 | """ 31 | try: 32 | query: str = function_args.get("query") # type: ignore 33 | # threshold = function_args.get("threshold", 0.4) 34 | 35 | # 检查LPMM知识库是否启用 36 | if qa_manager is None: 37 | logger.debug("LPMM知识库已禁用,跳过知识获取") 38 | return {"type": "info", "id": query, "content": "LPMM知识库已禁用"} 39 | 40 | # 调用知识库搜索 41 | 42 | knowledge_info = await qa_manager.get_knowledge(query) 43 | 44 | logger.debug(f"知识库查询结果: {knowledge_info}") 45 | 46 | if knowledge_info and knowledge_info.get("knowledge_items"): 47 | knowledge_parts = [] 48 | for i, item in enumerate(knowledge_info["knowledge_items"]): 49 | knowledge_parts.append(f"- {item.get('content', 'N/A')}") 50 | 51 | knowledge_text = "\n".join(knowledge_parts) 52 | summary = knowledge_info.get("summary", "无总结") 53 | content = f"关于 '{query}', 你知道以下信息:\n{knowledge_text}\n\n总结: {summary}" 54 | else: 55 | content = f"关于 '{query}',你的知识库里好像没有相关的信息呢" 56 | return {"type": "lpmm_knowledge", "id": query, "content": content} 57 | except Exception as e: 58 | # 捕获异常并记录错误 59 | logger.error(f"知识库搜索工具执行失败: {e!s}") 60 | # 在其他异常情况下,确保 id 仍然是 query (如果它被定义了) 61 | query_id = query if "query" in locals() else "unknown_query" 62 | return {"type": "info", "id": query_id, "content": f"lpmm知识库搜索失败,炸了: {e!s}"} 63 | -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="MaiZone(麦麦空间)- 重构版", 5 | description="(重构版)让你的麦麦发QQ空间说说、评论、点赞,支持AI配图、定时发送和自动监控功能", 6 | usage="该插件提供 `send_feed` 和 `read_feed` action,以及 `send_feed` command。", 7 | version="3.0.0", 8 | author="MoFox-Studio", 9 | license="GPL-v3.0", 10 | repository_url="https://github.com/MoFox-Studio", 11 | keywords=["QQ空间", "说说", "动态", "评论", "点赞", "自动化", "AI配图"], 12 | categories=["社交", "自动化", "QQ空间"], 13 | extra={ 14 | "is_built_in": False, 15 | "plugin_type": "social", 16 | }, 17 | ) 18 | -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/plugins/built_in/maizone_refactored/actions/__init__.py -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/actions/send_feed_action.py: -------------------------------------------------------------------------------- 1 | """ 2 | 发送说说动作组件 3 | """ 4 | 5 | from src.common.logger import get_logger 6 | from src.plugin_system import ActionActivationType, BaseAction, ChatMode 7 | from src.plugin_system.apis import generator_api 8 | from src.plugin_system.apis.permission_api import permission_api 9 | 10 | from ..services.manager import get_qzone_service 11 | 12 | logger = get_logger("MaiZone.SendFeedAction") 13 | 14 | 15 | class SendFeedAction(BaseAction): 16 | """ 17 | 当检测到用户意图是发送说说时,此动作被激活。 18 | """ 19 | 20 | action_name: str = "send_feed" 21 | action_description: str = "发送一条关于特定主题的说说" 22 | activation_type: ActionActivationType = ActionActivationType.KEYWORD 23 | mode_enable: ChatMode = ChatMode.ALL 24 | activation_keywords: list = ["发说说", "发空间", "发动态"] 25 | 26 | action_parameters = { 27 | "topic": "用户想要发送的说说主题", 28 | "user_name": "请求你发说说的好友的昵称", 29 | } 30 | 31 | def __init__(self, *args, **kwargs): 32 | super().__init__(*args, **kwargs) 33 | 34 | async def _check_permission(self) -> bool: 35 | """检查当前用户是否有权限执行此动作""" 36 | platform = self.chat_stream.platform 37 | user_id = self.chat_stream.user_info.user_id 38 | 39 | # 使用权限API检查用户是否有发送说说的权限 40 | return await permission_api.check_permission(platform, user_id, "plugin.maizone.send_feed") 41 | 42 | async def execute(self) -> tuple[bool, str]: 43 | """ 44 | 执行动作的核心逻辑。 45 | """ 46 | if not await self._check_permission(): 47 | _, reply_set, _ = await generator_api.generate_reply( 48 | chat_stream=self.chat_stream, 49 | action_data={"extra_info_block": "无权命令你发送说说,请用符合你人格特点的方式拒绝请求"}, 50 | ) 51 | if reply_set and isinstance(reply_set, list): 52 | for reply_type, reply_content in reply_set: 53 | if reply_type == "text": 54 | await self.send_text(reply_content) 55 | return False, "权限不足" 56 | 57 | topic = self.action_data.get("topic", "") 58 | stream_id = self.chat_stream.stream_id 59 | 60 | try: 61 | qzone_service = get_qzone_service() 62 | result = await qzone_service.send_feed(topic, stream_id) 63 | 64 | if result.get("success"): 65 | _, reply_set, _ = await generator_api.generate_reply( 66 | chat_stream=self.chat_stream, 67 | action_data={ 68 | "extra_info_block": f"你刚刚成功发送了一条关于“{topic or '随机'}”的说说,内容是:{result.get('message', '')}" 69 | }, 70 | ) 71 | if reply_set and isinstance(reply_set, list): 72 | for reply_type, reply_content in reply_set: 73 | if reply_type == "text": 74 | await self.send_text(reply_content) 75 | else: 76 | await self.send_text("我发完说说啦,快去看看吧!") 77 | return True, "发送成功" 78 | else: 79 | await self.send_text(f"发送失败了呢,原因好像是:{result.get('message', '未知错误')}") 80 | return False, result.get("message", "未知错误") 81 | 82 | except Exception as e: 83 | logger.error(f"执行发送说说动作时发生未知异常: {e}", exc_info=True) 84 | await self.send_text("糟糕,发送的时候网络好像波动了一下...") 85 | return False, "动作执行异常" 86 | -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/plugins/built_in/maizone_refactored/commands/__init__.py -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/commands/send_feed_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | 发送说说命令 await self.send_text(f"收到!正在为你生成关于"{topic or '随机'}"的说说,请稍候...【热重载测试成功】")件 3 | """ 4 | 5 | from src.common.logger import get_logger 6 | from src.plugin_system.base.command_args import CommandArgs 7 | from src.plugin_system.base.plus_command import PlusCommand 8 | from src.plugin_system.utils.permission_decorators import require_permission 9 | 10 | from ..services.manager import get_config_getter, get_qzone_service 11 | 12 | logger = get_logger("MaiZone.SendFeedCommand") 13 | 14 | 15 | class SendFeedCommand(PlusCommand): 16 | """ 17 | 响应用户通过 `/send_feed` 命令发送说说的请求。 18 | 测试热重载功能 - 这是一个测试注释,现在应该可以正常工作了! 19 | """ 20 | 21 | command_name: str = "send_feed" 22 | command_description: str = "发一条QQ空间说说" 23 | command_aliases = ["发空间"] 24 | 25 | def __init__(self, *args, **kwargs): 26 | super().__init__(*args, **kwargs) 27 | 28 | @require_permission("plugin.maizone.send_feed") 29 | async def execute(self, args: CommandArgs) -> tuple[bool, str, bool]: 30 | """ 31 | 执行命令的核心逻辑。 32 | """ 33 | 34 | topic = args.get_remaining() 35 | stream_id = self.message.chat_stream.stream_id 36 | 37 | await self.send_text(f"收到!正在为你生成关于“{topic or '随机'}”的说说,请稍候...") 38 | 39 | try: 40 | qzone_service = get_qzone_service() 41 | result = await qzone_service.send_feed(topic, stream_id) 42 | 43 | if result.get("success"): 44 | reply_message = f"已经成功发送说说:\n{result.get('message', '')}" 45 | get_config = get_config_getter() 46 | if get_config("send.enable_reply", True): 47 | await self.send_text(reply_message) 48 | return True, "发送成功", True 49 | else: 50 | await self.send_text(f"哎呀,发送失败了:{result.get('message', '未知错误')}") 51 | return False, result.get("message", "未知错误"), True 52 | 53 | except Exception as e: 54 | logger.error(f"执行发送说说命令时发生未知异常: {e},它的类型是:{type(e)}", exc_info=True) 55 | await self.send_text("呜... 发送过程中好像出了点问题。") 56 | return False, "命令执行异常", True 57 | -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/plugins/built_in/maizone_refactored/services/__init__.py -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/services/manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | 服务管理器/定位器 3 | 这是一个独立的模块,用于注册和获取插件内的全局服务实例,以避免循环导入。 4 | """ 5 | 6 | from collections.abc import Callable 7 | from typing import Any 8 | 9 | from .qzone_service import QZoneService 10 | 11 | # --- 全局服务注册表 --- 12 | _services: dict[str, Any] = {} 13 | 14 | 15 | def register_service(name: str, instance: Any): 16 | """将一个服务实例注册到全局注册表。""" 17 | _services[name] = instance 18 | 19 | 20 | def get_qzone_service() -> QZoneService: 21 | """全局可用的QZone服务获取函数""" 22 | return _services["qzone"] 23 | 24 | 25 | def get_config_getter() -> Callable: 26 | """全局可用的配置获取函数""" 27 | return _services["get_config"] 28 | -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/services/monitor_service.py: -------------------------------------------------------------------------------- 1 | """ 2 | 好友动态监控服务 3 | """ 4 | 5 | import asyncio 6 | import traceback 7 | from collections.abc import Callable 8 | 9 | from src.common.logger import get_logger 10 | 11 | from .qzone_service import QZoneService 12 | 13 | logger = get_logger("MaiZone.MonitorService") 14 | 15 | 16 | class MonitorService: 17 | """好友动态监控服务""" 18 | 19 | def __init__(self, get_config: Callable, qzone_service: QZoneService): 20 | self.get_config = get_config 21 | self.qzone_service = qzone_service 22 | self.is_running = False 23 | self.task = None 24 | 25 | async def start(self): 26 | """启动监控任务""" 27 | if self.is_running: 28 | return 29 | self.is_running = True 30 | self.task = asyncio.create_task(self._monitor_loop()) 31 | logger.info("好友动态监控任务已启动") 32 | 33 | async def stop(self): 34 | """停止监控任务""" 35 | if not self.is_running: 36 | return 37 | self.is_running = False 38 | if self.task: 39 | self.task.cancel() 40 | try: 41 | await self.task 42 | except asyncio.CancelledError: 43 | pass 44 | logger.info("好友动态监控任务已停止") 45 | 46 | async def _monitor_loop(self): 47 | """监控任务主循环""" 48 | # 插件启动后,延迟一段时间再开始第一次监控 49 | await asyncio.sleep(60) 50 | 51 | while self.is_running: 52 | try: 53 | if not self.get_config("monitor.enable_auto_monitor", False): 54 | await asyncio.sleep(60) 55 | continue 56 | 57 | interval_minutes = self.get_config("monitor.interval_minutes", 10) 58 | 59 | await self.qzone_service.monitor_feeds() 60 | 61 | logger.info(f"本轮监控完成,将在 {interval_minutes} 分钟后进行下一次检查。") 62 | await asyncio.sleep(interval_minutes * 60) 63 | 64 | except asyncio.CancelledError: 65 | break 66 | except Exception as e: 67 | logger.error(f"监控任务循环出错: {e}\n{traceback.format_exc()}") 68 | await asyncio.sleep(300) 69 | -------------------------------------------------------------------------------- /src/plugins/built_in/maizone_refactored/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoFox-Studio/MoFox_Bot/3f9b3509d3c7d6fa4ea3ae40fdcb4b53b3196450/src/plugins/built_in/maizone_refactored/utils/__init__.py -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="napcat_plugin", 5 | description="基于OneBot 11协议的NapCat QQ协议插件,提供完整的QQ机器人API接口,使用现有adapter连接", 6 | usage="该插件提供 `napcat_tool` tool。", 7 | version="1.0.0", 8 | author="Windpicker_owo", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/Windpicker-owo/InternetSearchPlugin", 11 | keywords=["qq", "bot", "napcat", "onebot", "api", "websocket"], 12 | categories=["protocol"], 13 | extra={ 14 | "is_built_in": False, 15 | }, 16 | ) 17 | -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "MaiBotNapcatAdapter" 3 | version = "0.4.8" 4 | description = "A MaiBot adapter for Napcat" 5 | dependencies = [ 6 | "ruff>=0.12.9", 7 | ] 8 | 9 | [tool.ruff] 10 | 11 | include = ["*.py"] 12 | 13 | # 行长度设置 14 | line-length = 120 15 | 16 | [tool.ruff.lint] 17 | fixable = ["ALL"] 18 | unfixable = [] 19 | 20 | # 启用的规则 21 | select = [ 22 | "E", # pycodestyle 错误 23 | "F", # pyflakes 24 | "B", # flake8-bugbear 25 | ] 26 | 27 | ignore = ["E711","E501"] 28 | 29 | [tool.ruff.format] 30 | docstring-code-format = true 31 | indent-style = "space" 32 | 33 | 34 | # 使用双引号表示字符串 35 | quote-style = "double" 36 | 37 | # 尊重魔法尾随逗号 38 | # 例如: 39 | # items = [ 40 | # "apple", 41 | # "banana", 42 | # "cherry", 43 | # ] 44 | skip-magic-trailing-comma = false 45 | 46 | # 自动检测合适的换行符 47 | line-ending = "auto" 48 | -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/src/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import tomlkit 3 | import os 4 | from src.common.logger import get_logger 5 | 6 | logger = get_logger("napcat_adapter") 7 | 8 | 9 | class CommandType(Enum): 10 | """命令类型""" 11 | 12 | GROUP_BAN = "set_group_ban" # 禁言用户 13 | GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 14 | GROUP_KICK = "set_group_kick" # 踢出群聊 15 | SEND_POKE = "send_poke" # 戳一戳 16 | DELETE_MSG = "delete_msg" # 撤回消息 17 | AI_VOICE_SEND = "send_group_ai_record" # 发送群AI语音 18 | SET_EMOJI_LIKE = "set_msg_emoji_like" # 设置表情回应 19 | SEND_AT_MESSAGE = "send_at_message" # 艾特用户并发送消息 20 | SEND_LIKE = "send_like" # 点赞 21 | 22 | def __str__(self) -> str: 23 | return self.value 24 | 25 | 26 | pyproject_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pyproject.toml") 27 | toml_data = tomlkit.parse(open(pyproject_path, "r", encoding="utf-8").read()) 28 | project_data = toml_data.get("project", {}) 29 | version = project_data.get("version", "unknown") 30 | logger.info(f"版本\n\nMaiBot-Napcat-Adapter 版本: {version}\n喜欢的话点个star喵~\n") 31 | -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/src/mmc_com_layer.py: -------------------------------------------------------------------------------- 1 | from maim_message import Router, RouteConfig, TargetConfig 2 | from src.common.logger import get_logger 3 | import os 4 | from .send_handler import send_handler 5 | from src.plugin_system.apis import config_api 6 | 7 | logger = get_logger("napcat_adapter") 8 | 9 | router = None 10 | 11 | 12 | def create_router(plugin_config: dict): 13 | """创建路由器实例""" 14 | global router 15 | platform_name = config_api.get_plugin_config(plugin_config, "maibot_server.platform_name", "qq") 16 | host = os.getenv("HOST", "127.0.0.1") 17 | port = os.getenv("PORT", "8000") 18 | logger.debug(f"初始化MaiBot连接,使用地址:{host}:{port}") 19 | route_config = RouteConfig( 20 | route_config={ 21 | platform_name: TargetConfig( 22 | url=f"ws://{host}:{port}/ws", 23 | token=None, 24 | ) 25 | } 26 | ) 27 | router = Router(route_config) 28 | return router 29 | 30 | 31 | async def mmc_start_com(plugin_config: dict | None = None): 32 | """启动MaiBot连接""" 33 | logger.debug("正在连接MaiBot") 34 | if plugin_config: 35 | create_router(plugin_config) 36 | 37 | if router: 38 | router.register_class_handler(send_handler.handle_message) 39 | await router.run() 40 | 41 | 42 | async def mmc_stop_com(): 43 | """停止MaiBot连接""" 44 | if router: 45 | await router.stop() 46 | -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MetaEventType: 5 | lifecycle = "lifecycle" # 生命周期 6 | 7 | class Lifecycle: 8 | connect = "connect" # 生命周期 - WebSocket 连接成功 9 | 10 | heartbeat = "heartbeat" # 心跳 11 | 12 | 13 | class MessageType: # 接受消息大类 14 | private = "private" # 私聊消息 15 | 16 | class Private: 17 | friend = "friend" # 私聊消息 - 好友 18 | group = "group" # 私聊消息 - 群临时 19 | group_self = "group_self" # 私聊消息 - 群中自身发送 20 | other = "other" # 私聊消息 - 其他 21 | 22 | group = "group" # 群聊消息 23 | 24 | class Group: 25 | normal = "normal" # 群聊消息 - 普通 26 | anonymous = "anonymous" # 群聊消息 - 匿名消息 27 | notice = "notice" # 群聊消息 - 系统提示 28 | 29 | 30 | class NoticeType: # 通知事件 31 | friend_recall = "friend_recall" # 私聊消息撤回 32 | group_recall = "group_recall" # 群聊消息撤回 33 | notify = "notify" 34 | group_ban = "group_ban" # 群禁言 35 | group_msg_emoji_like = "group_msg_emoji_like" # 群聊表情回复 36 | 37 | class Notify: 38 | poke = "poke" # 戳一戳 39 | input_status = "input_status" # 正在输入 40 | 41 | class GroupBan: 42 | ban = "ban" # 禁言 43 | lift_ban = "lift_ban" # 解除禁言 44 | 45 | 46 | class RealMessageType: # 实际消息分类 47 | text = "text" # 纯文本 48 | face = "face" # qq表情 49 | image = "image" # 图片 50 | record = "record" # 语音 51 | video = "video" # 视频 52 | at = "at" # @某人 53 | rps = "rps" # 猜拳魔法表情 54 | dice = "dice" # 骰子 55 | shake = "shake" # 私聊窗口抖动(只收) 56 | poke = "poke" # 群聊戳一戳 57 | share = "share" # 链接分享(json形式) 58 | reply = "reply" # 回复消息 59 | forward = "forward" # 转发消息 60 | node = "node" # 转发消息节点 61 | json = "json" # json消息 62 | 63 | 64 | class MessageSentType: 65 | private = "private" 66 | 67 | class Private: 68 | friend = "friend" 69 | group = "group" 70 | 71 | group = "group" 72 | 73 | class Group: 74 | normal = "normal" 75 | 76 | 77 | class CommandType(Enum): 78 | """命令类型""" 79 | 80 | GROUP_BAN = "set_group_ban" # 禁言用户 81 | GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 82 | GROUP_KICK = "set_group_kick" # 踢出群聊 83 | SEND_POKE = "send_poke" # 戳一戳 84 | DELETE_MSG = "delete_msg" # 撤回消息 85 | 86 | def __str__(self) -> str: 87 | return self.value 88 | 89 | 90 | ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl", "file"] 91 | -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/src/recv_handler/meta_event_handler.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | 3 | logger = get_logger("napcat_adapter") 4 | from src.plugin_system.apis import config_api 5 | import time 6 | import asyncio 7 | 8 | from . import MetaEventType 9 | 10 | 11 | class MetaEventHandler: 12 | """ 13 | 处理Meta事件 14 | """ 15 | 16 | def __init__(self): 17 | self.interval = 5.0 # 默认值,稍后通过set_plugin_config设置 18 | self._interval_checking = False 19 | self.plugin_config = None 20 | 21 | def set_plugin_config(self, plugin_config: dict): 22 | """设置插件配置""" 23 | self.plugin_config = plugin_config 24 | # 更新interval值 25 | self.interval = ( 26 | config_api.get_plugin_config(self.plugin_config, "napcat_server.heartbeat_interval", 5000) / 1000 27 | ) 28 | 29 | async def handle_meta_event(self, message: dict) -> None: 30 | event_type = message.get("meta_event_type") 31 | if event_type == MetaEventType.lifecycle: 32 | sub_type = message.get("sub_type") 33 | if sub_type == MetaEventType.Lifecycle.connect: 34 | self_id = message.get("self_id") 35 | self.last_heart_beat = time.time() 36 | logger.info(f"Bot {self_id} 连接成功") 37 | asyncio.create_task(self.check_heartbeat(self_id)) 38 | elif event_type == MetaEventType.heartbeat: 39 | if message["status"].get("online") and message["status"].get("good"): 40 | if not self._interval_checking: 41 | asyncio.create_task(self.check_heartbeat()) 42 | self.last_heart_beat = time.time() 43 | self.interval = message.get("interval") / 1000 44 | else: 45 | self_id = message.get("self_id") 46 | logger.warning(f"Bot {self_id} Napcat 端异常!") 47 | 48 | async def check_heartbeat(self, id: int) -> None: 49 | self._interval_checking = True 50 | while True: 51 | now_time = time.time() 52 | if now_time - self.last_heart_beat > self.interval * 2: 53 | logger.error(f"Bot {id} 可能发生了连接断开,被下线,或者Napcat卡死!") 54 | break 55 | else: 56 | logger.debug("心跳正常") 57 | await asyncio.sleep(self.interval) 58 | 59 | 60 | meta_event_handler = MetaEventHandler() 61 | -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/src/response_pool.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | from typing import Dict 4 | from src.common.logger import get_logger 5 | from src.plugin_system.apis import config_api 6 | 7 | logger = get_logger("napcat_adapter") 8 | 9 | response_dict: Dict = {} 10 | response_time_dict: Dict = {} 11 | plugin_config = None 12 | 13 | 14 | def set_plugin_config(config: dict): 15 | """设置插件配置""" 16 | global plugin_config 17 | plugin_config = config 18 | 19 | 20 | async def get_response(request_id: str, timeout: int = 10) -> dict: 21 | response = await asyncio.wait_for(_get_response(request_id), timeout) 22 | _ = response_time_dict.pop(request_id) 23 | logger.debug(f"响应信息id: {request_id} 已从响应字典中取出") 24 | return response 25 | 26 | 27 | async def _get_response(request_id: str) -> dict: 28 | """ 29 | 内部使用的获取响应函数,主要用于在需要时获取响应 30 | """ 31 | while request_id not in response_dict: 32 | await asyncio.sleep(0.2) 33 | return response_dict.pop(request_id) 34 | 35 | 36 | async def put_response(response: dict): 37 | echo_id = response.get("echo") 38 | now_time = time.time() 39 | response_dict[echo_id] = response 40 | response_time_dict[echo_id] = now_time 41 | logger.debug(f"响应信息id: {echo_id} 已存入响应字典") 42 | 43 | 44 | async def check_timeout_response() -> None: 45 | while True: 46 | cleaned_message_count: int = 0 47 | now_time = time.time() 48 | 49 | # 获取心跳间隔配置 50 | heartbeat_interval = 30 # 默认值 51 | if plugin_config: 52 | heartbeat_interval = config_api.get_plugin_config(plugin_config, "napcat_server.heartbeat_interval", 30) 53 | 54 | for echo_id, response_time in list(response_time_dict.items()): 55 | if now_time - response_time > heartbeat_interval: 56 | cleaned_message_count += 1 57 | response_dict.pop(echo_id) 58 | response_time_dict.pop(echo_id) 59 | logger.warning(f"响应消息 {echo_id} 超时,已删除") 60 | if cleaned_message_count > 0: 61 | logger.info(f"已删除 {cleaned_message_count} 条超时响应消息") 62 | await asyncio.sleep(heartbeat_interval) 63 | -------------------------------------------------------------------------------- /src/plugins/built_in/napcat_adapter_plugin/todo.md: -------------------------------------------------------------------------------- 1 | # TODO List: 2 | 3 | - [x] logger使用主程序的 4 | - [ ] 使用插件系统的config系统 5 | - [x] 接收从napcat传递的所有信息 6 | - [ ] 优化架构,各模块解耦,暴露关键方法用于提供接口 7 | - [ ] 单独一个模块负责与主程序通信 8 | - [ ] 使用event系统完善接口api 9 | 10 | 11 | --- 12 | Event分为两种,一种是对外输出的event,由napcat插件自主触发并传递参数,另一种是接收外界输入的event,由外部插件触发并向napcat传递参数 13 | 14 | 15 | ## 例如, 16 | 17 | ### 对外输出的event: 18 | 19 | napcat_on_received_text -> (message_seg: Seg) 接受到qq的文字消息,会向handler传递一个Seg 20 | napcat_on_received_face -> (message_seg: Seg) 接受到qq的表情消息,会向handler传递一个Seg 21 | napcat_on_received_reply -> (message_seg: Seg) 接受到qq的回复消息,会向handler传递一个Seg 22 | napcat_on_received_image -> (message_seg: Seg) 接受到qq的图片消息,会向handler传递一个Seg 23 | napcat_on_received_image -> (message_seg: Seg) 接受到qq的图片消息,会向handler传递一个Seg 24 | napcat_on_received_record -> (message_seg: Seg) 接受到qq的语音消息,会向handler传递一个Seg 25 | napcat_on_received_rps -> (message_seg: Seg) 接受到qq的猜拳魔法表情,会向handler传递一个Seg 26 | napcat_on_received_friend_invitation -> (user_id: str) 接受到qq的好友请求,会向handler传递一个user_id 27 | ... 28 | 29 | 此类event不接受外部插件的触发,只能由napcat插件统一触发。 30 | 31 | 外部插件需要编写handler并订阅此类事件。 32 | ```python 33 | from src.plugin_system.core.event_manager import event_manager 34 | from src.plugin_system.base.base_event import HandlerResult 35 | 36 | class MyEventHandler(BaseEventHandler): 37 | handler_name = "my_handler" 38 | handler_description = "我的自定义事件处理器" 39 | weight = 10 # 权重,越大越先执行 40 | intercept_message = False # 是否拦截消息 41 | init_subscribe = ["napcat_on_received_text"] # 初始订阅的事件 42 | 43 | async def execute(self, params: dict) -> HandlerResult: 44 | """处理事件""" 45 | try: 46 | message = params.get("message_seg") 47 | print(f"收到消息: {message.data}") 48 | 49 | # 业务逻辑处理 50 | # ... 51 | 52 | return HandlerResult( 53 | success=True, 54 | continue_process=True, # 是否继续让其他处理器处理 55 | message="处理成功", 56 | handler_name=self.handler_name 57 | ) 58 | except Exception as e: 59 | return HandlerResult( 60 | success=False, 61 | continue_process=True, 62 | message=f"处理失败: {str(e)}", 63 | handler_name=self.handler_name 64 | ) 65 | 66 | ``` 67 | 68 | ### 接收外界输入的event: 69 | 70 | napcat_kick_group <- (user_id, group_id) 踢出某个群组中的某个用户 71 | napcat_mute_user <- (user_id, group_id, time) 禁言某个群组中的某个用户 72 | napcat_unmute_user <- (user_id, group_id) 取消禁言某个群组中的某个用户 73 | napcat_mute_group <- (user_id, group_id) 禁言某个群组 74 | napcat_unmute_group <- (user_id, group_id) 取消禁言某个群组 75 | napcat_add_friend <- (user_id) 向某个用户发出好友请求 76 | napcat_accept_friend <- (user_id) 接收某个用户的好友请求 77 | napcat_reject_friend <- (user_id) 拒绝某个用户的好友请求 78 | ... 79 | 此类事件只由外部插件触发并传递参数,由napcat完成请求任务。 80 | 81 | 外部插件需要触发此类的event并传递正确的参数。 82 | 83 | ```python 84 | from src.plugin_system.core.event_manager import event_manager 85 | 86 | # 触发事件 87 | await event_manager.trigger_event("napcat_accept_friend", user_id = 1234123) 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /src/plugins/built_in/permission_management/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="权限管理插件(Permission Management)", 5 | description="通过系统API管理权限", 6 | usage="该插件提供 `permission_management` command。", 7 | version="1.0.0", 8 | author="MoFox-Studio", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/MoFox-Studio", 11 | keywords=["plugins", "permission", "management", "built-in"], 12 | extra={ 13 | "is_built_in": True, 14 | "plugin_type": "permission", 15 | }, 16 | ) 17 | -------------------------------------------------------------------------------- /src/plugins/built_in/plugin_management/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="插件和组件管理 (Plugin and Component Management)", 5 | description="通过系统API管理插件和组件的生命周期,包括加载、卸载、启用和禁用等操作。", 6 | usage="该插件提供 `plugin_management` command。", 7 | version="1.0.0", 8 | author="MaiBot团队", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/MaiM-with-u/maibot", 11 | keywords=["plugins", "components", "management", "built-in"], 12 | categories=["Core System", "Plugin Management"], 13 | extra={ 14 | "is_built_in": True, 15 | "plugin_type": "plugin_management", 16 | }, 17 | ) 18 | -------------------------------------------------------------------------------- /src/plugins/built_in/proactive_thinker/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="MoFox-Bot主动思考", 5 | description="主动思考插件", 6 | usage="该插件由系统自动触发。", 7 | version="1.0.0", 8 | author="MoFox-Studio", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/MoFox-Studio", 11 | keywords=["主动思考", "自己发消息"], 12 | categories=["Chat", "Integration"], 13 | extra={"is_built_in": True, "plugin_type": "functional"}, 14 | ) 15 | -------------------------------------------------------------------------------- /src/plugins/built_in/proactive_thinker/plugin.py: -------------------------------------------------------------------------------- 1 | from src.common.logger import get_logger 2 | from src.plugin_system import ( 3 | BaseEventHandler, 4 | BasePlugin, 5 | ConfigField, 6 | EventHandlerInfo, 7 | register_plugin, 8 | ) 9 | from src.plugin_system.base.base_plugin import BasePlugin 10 | 11 | from .proacive_thinker_event import ProactiveThinkerEventHandler 12 | 13 | logger = get_logger(__name__) 14 | 15 | 16 | @register_plugin 17 | class ProactiveThinkerPlugin(BasePlugin): 18 | """一个主动思考的插件,但现在还只是个空壳子""" 19 | 20 | plugin_name: str = "proactive_thinker" 21 | enable_plugin: bool = True 22 | dependencies: list[str] = [] 23 | python_dependencies: list[str] = [] 24 | config_file_name: str = "config.toml" 25 | config_schema: dict = { 26 | "plugin": { 27 | "config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"), 28 | }, 29 | } 30 | 31 | def __init__(self, *args, **kwargs): 32 | super().__init__(*args, **kwargs) 33 | 34 | def get_plugin_components(self) -> list[tuple[EventHandlerInfo, type[BaseEventHandler]]]: 35 | """返回插件的EventHandler组件""" 36 | components: list[tuple[EventHandlerInfo, type[BaseEventHandler]]] = [ 37 | (ProactiveThinkerEventHandler.get_handler_info(), ProactiveThinkerEventHandler) 38 | ] 39 | return components 40 | -------------------------------------------------------------------------------- /src/plugins/built_in/social_toolkit_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="MoFox-Bot工具箱", 5 | description="一个集合多种实用功能的插件,旨在提升聊天体验和效率。", 6 | usage="该插件提供多种命令,详情请查阅文档。", 7 | version="1.0.0", 8 | author="MoFox-Studio", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/MoFox-Studio", 11 | keywords=["emoji", "reaction", "like", "表情", "回应", "点赞"], 12 | categories=["Chat", "Integration"], 13 | extra={"is_built_in": "true", "plugin_type": "functional"}, 14 | ) 15 | -------------------------------------------------------------------------------- /src/plugins/built_in/tts_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="文本转语音插件 (Text-to-Speech)", 5 | description="将文本转换为语音进行播放的插件,支持多种语音模式和智能语音输出场景判断。", 6 | usage="该插件提供 `tts_action` action。", 7 | version="0.1.0", 8 | author="MaiBot团队", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/MaiM-with-u/maibot", 11 | keywords=["tts", "voice", "audio", "speech", "accessibility"], 12 | categories=["Audio Tools", "Accessibility", "Voice Assistant"], 13 | extra={ 14 | "is_built_in": True, 15 | "plugin_type": "audio_processor", 16 | }, 17 | ) 18 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/__init__.py: -------------------------------------------------------------------------------- 1 | from src.plugin_system.base.plugin_metadata import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="Web Search Tool", 5 | description="A tool for searching the web.", 6 | usage="This plugin provides a `web_search` tool.", 7 | version="1.0.0", 8 | author="MoFox-Studio", 9 | license="GPL-v3.0-or-later", 10 | repository_url="https://github.com/MoFox-Studio", 11 | keywords=["web", "search", "tool"], 12 | categories=["Tools"], 13 | extra={ 14 | "is_built_in": True, 15 | }, 16 | ) 17 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/engines/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Search engines package 3 | """ 4 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/engines/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base search engine interface 3 | """ 4 | 5 | from abc import ABC, abstractmethod 6 | from typing import Any 7 | 8 | 9 | class BaseSearchEngine(ABC): 10 | """ 11 | 搜索引擎基类 12 | """ 13 | 14 | @abstractmethod 15 | async def search(self, args: dict[str, Any]) -> list[dict[str, Any]]: 16 | """ 17 | 执行搜索 18 | 19 | Args: 20 | args: 搜索参数,包含 query、num_results、time_range 等 21 | 22 | Returns: 23 | 搜索结果列表,每个结果包含 title、url、snippet、provider 字段 24 | """ 25 | pass 26 | 27 | @abstractmethod 28 | def is_available(self) -> bool: 29 | """ 30 | 检查搜索引擎是否可用 31 | """ 32 | pass 33 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/engines/ddg_engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | DuckDuckGo search engine implementation 3 | """ 4 | 5 | from typing import Any 6 | 7 | from asyncddgs import aDDGS 8 | 9 | from src.common.logger import get_logger 10 | 11 | from .base import BaseSearchEngine 12 | 13 | logger = get_logger("ddg_engine") 14 | 15 | 16 | class DDGSearchEngine(BaseSearchEngine): 17 | """ 18 | DuckDuckGo搜索引擎实现 19 | """ 20 | 21 | def is_available(self) -> bool: 22 | """检查DuckDuckGo搜索引擎是否可用""" 23 | return True # DuckDuckGo不需要API密钥,总是可用 24 | 25 | async def search(self, args: dict[str, Any]) -> list[dict[str, Any]]: 26 | """执行DuckDuckGo搜索""" 27 | query = args["query"] 28 | num_results = args.get("num_results", 3) 29 | 30 | try: 31 | async with aDDGS() as ddgs: 32 | search_response = await ddgs.text(query, max_results=num_results) 33 | 34 | return [ 35 | {"title": r.get("title"), "url": r.get("href"), "snippet": r.get("body"), "provider": "DuckDuckGo"} 36 | for r in search_response 37 | ] 38 | except Exception as e: 39 | logger.error(f"DuckDuckGo 搜索失败: {e}") 40 | return [] 41 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/engines/exa_engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exa search engine implementation 3 | """ 4 | 5 | import asyncio 6 | import functools 7 | from datetime import datetime, timedelta 8 | from typing import Any 9 | 10 | from exa_py import Exa 11 | 12 | from src.common.logger import get_logger 13 | from src.plugin_system.apis import config_api 14 | 15 | from ..utils.api_key_manager import create_api_key_manager_from_config 16 | from .base import BaseSearchEngine 17 | 18 | logger = get_logger("exa_engine") 19 | 20 | 21 | class ExaSearchEngine(BaseSearchEngine): 22 | """ 23 | Exa搜索引擎实现 24 | """ 25 | 26 | def __init__(self): 27 | self._initialize_clients() 28 | 29 | def _initialize_clients(self): 30 | """初始化Exa客户端""" 31 | # 从主配置文件读取API密钥 32 | exa_api_keys = config_api.get_global_config("web_search.exa_api_keys", None) 33 | 34 | # 创建API密钥管理器 35 | self.api_manager = create_api_key_manager_from_config(exa_api_keys, lambda key: Exa(api_key=key), "Exa") 36 | 37 | def is_available(self) -> bool: 38 | """检查Exa搜索引擎是否可用""" 39 | return self.api_manager.is_available() 40 | 41 | async def search(self, args: dict[str, Any]) -> list[dict[str, Any]]: 42 | """执行Exa搜索""" 43 | if not self.is_available(): 44 | return [] 45 | 46 | query = args["query"] 47 | num_results = args.get("num_results", 3) 48 | time_range = args.get("time_range", "any") 49 | 50 | exa_args = {"num_results": num_results, "text": True, "highlights": True} 51 | if time_range != "any": 52 | today = datetime.now() 53 | start_date = today - timedelta(days=7 if time_range == "week" else 30) 54 | exa_args["start_published_date"] = start_date.strftime("%Y-%m-%d") 55 | 56 | try: 57 | # 使用API密钥管理器获取下一个客户端 58 | exa_client = self.api_manager.get_next_client() 59 | if not exa_client: 60 | logger.error("无法获取Exa客户端") 61 | return [] 62 | 63 | loop = asyncio.get_running_loop() 64 | func = functools.partial(exa_client.search_and_contents, query, **exa_args) 65 | search_response = await loop.run_in_executor(None, func) 66 | 67 | return [ 68 | { 69 | "title": res.title, 70 | "url": res.url, 71 | "snippet": " ".join(getattr(res, "highlights", [])) or (getattr(res, "text", "")[:250] + "..."), 72 | "provider": "Exa", 73 | } 74 | for res in search_response.results 75 | ] 76 | except Exception as e: 77 | logger.error(f"Exa 搜索失败: {e}") 78 | return [] 79 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/engines/tavily_engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tavily search engine implementation 3 | """ 4 | 5 | import asyncio 6 | import functools 7 | from typing import Any 8 | 9 | from tavily import TavilyClient 10 | 11 | from src.common.logger import get_logger 12 | from src.plugin_system.apis import config_api 13 | 14 | from ..utils.api_key_manager import create_api_key_manager_from_config 15 | from .base import BaseSearchEngine 16 | 17 | logger = get_logger("tavily_engine") 18 | 19 | 20 | class TavilySearchEngine(BaseSearchEngine): 21 | """ 22 | Tavily搜索引擎实现 23 | """ 24 | 25 | def __init__(self): 26 | self._initialize_clients() 27 | 28 | def _initialize_clients(self): 29 | """初始化Tavily客户端""" 30 | # 从主配置文件读取API密钥 31 | tavily_api_keys = config_api.get_global_config("web_search.tavily_api_keys", None) 32 | 33 | # 创建API密钥管理器 34 | self.api_manager = create_api_key_manager_from_config( 35 | tavily_api_keys, lambda key: TavilyClient(api_key=key), "Tavily" 36 | ) 37 | 38 | def is_available(self) -> bool: 39 | """检查Tavily搜索引擎是否可用""" 40 | return self.api_manager.is_available() 41 | 42 | async def search(self, args: dict[str, Any]) -> list[dict[str, Any]]: 43 | """执行Tavily搜索""" 44 | if not self.is_available(): 45 | return [] 46 | 47 | query = args["query"] 48 | num_results = args.get("num_results", 3) 49 | time_range = args.get("time_range", "any") 50 | 51 | try: 52 | # 使用API密钥管理器获取下一个客户端 53 | tavily_client = self.api_manager.get_next_client() 54 | if not tavily_client: 55 | logger.error("无法获取Tavily客户端") 56 | return [] 57 | 58 | # 构建Tavily搜索参数 59 | search_params = { 60 | "query": query, 61 | "max_results": num_results, 62 | "search_depth": "basic", 63 | "include_answer": False, 64 | "include_raw_content": False, 65 | } 66 | 67 | # 根据时间范围调整搜索参数 68 | if time_range == "week": 69 | search_params["days"] = 7 70 | elif time_range == "month": 71 | search_params["days"] = 30 72 | 73 | loop = asyncio.get_running_loop() 74 | func = functools.partial(tavily_client.search, **search_params) 75 | search_response = await loop.run_in_executor(None, func) 76 | 77 | results = [] 78 | if search_response and "results" in search_response: 79 | for res in search_response["results"]: 80 | results.append( 81 | { 82 | "title": res.get("title", "无标题"), 83 | "url": res.get("url", ""), 84 | "snippet": res.get("content", "")[:300] + "..." if res.get("content") else "无摘要", 85 | "provider": "Tavily", 86 | } 87 | ) 88 | 89 | return results 90 | 91 | except Exception as e: 92 | logger.error(f"Tavily 搜索失败: {e}") 93 | return [] 94 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/tools/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tools package 3 | """ 4 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Web search tool utilities package 3 | """ 4 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/utils/api_key_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | API密钥管理器,提供轮询机制 3 | """ 4 | 5 | import itertools 6 | from collections.abc import Callable 7 | from typing import Generic, TypeVar 8 | 9 | from src.common.logger import get_logger 10 | 11 | logger = get_logger("api_key_manager") 12 | 13 | T = TypeVar("T") 14 | 15 | 16 | class APIKeyManager(Generic[T]): 17 | """ 18 | API密钥管理器,支持轮询机制 19 | """ 20 | 21 | def __init__(self, api_keys: list[str], client_factory: Callable[[str], T], service_name: str = "Unknown"): 22 | """ 23 | 初始化API密钥管理器 24 | 25 | Args: 26 | api_keys: API密钥列表 27 | client_factory: 客户端工厂函数,接受API密钥参数并返回客户端实例 28 | service_name: 服务名称,用于日志记录 29 | """ 30 | self.service_name = service_name 31 | self.clients: list[T] = [] 32 | self.client_cycle: itertools.cycle | None = None 33 | 34 | if api_keys: 35 | # 过滤有效的API密钥,排除None、空字符串、"None"字符串等 36 | valid_keys = [] 37 | for key in api_keys: 38 | if isinstance(key, str) and key.strip() and key.strip().lower() not in ("none", "null", ""): 39 | valid_keys.append(key.strip()) 40 | 41 | if valid_keys: 42 | try: 43 | self.clients = [client_factory(key) for key in valid_keys] 44 | self.client_cycle = itertools.cycle(self.clients) 45 | logger.info(f"🔑 {service_name} 成功加载 {len(valid_keys)} 个 API 密钥") 46 | except Exception as e: 47 | logger.error(f"❌ 初始化 {service_name} 客户端失败: {e}") 48 | self.clients = [] 49 | self.client_cycle = None 50 | else: 51 | logger.warning(f"⚠️ {service_name} API Keys 配置无效(包含None或空值),{service_name} 功能将不可用") 52 | else: 53 | logger.warning(f"⚠️ {service_name} API Keys 未配置,{service_name} 功能将不可用") 54 | 55 | def is_available(self) -> bool: 56 | """检查是否有可用的客户端""" 57 | return bool(self.clients and self.client_cycle) 58 | 59 | def get_next_client(self) -> T | None: 60 | """获取下一个客户端(轮询)""" 61 | if not self.is_available(): 62 | return None 63 | return next(self.client_cycle) 64 | 65 | def get_client_count(self) -> int: 66 | """获取可用客户端数量""" 67 | return len(self.clients) 68 | 69 | 70 | def create_api_key_manager_from_config( 71 | config_keys: list[str] | None, client_factory: Callable[[str], T], service_name: str 72 | ) -> APIKeyManager[T]: 73 | """ 74 | 从配置创建API密钥管理器的便捷函数 75 | 76 | Args: 77 | config_keys: 从配置读取的API密钥列表 78 | client_factory: 客户端工厂函数 79 | service_name: 服务名称 80 | 81 | Returns: 82 | API密钥管理器实例 83 | """ 84 | api_keys = config_keys if isinstance(config_keys, list) else [] 85 | return APIKeyManager(api_keys, client_factory, service_name) 86 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/utils/formatters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formatters for web search results 3 | """ 4 | 5 | from typing import Any 6 | 7 | 8 | def format_search_results(results: list[dict[str, Any]]) -> str: 9 | """ 10 | 格式化搜索结果为字符串 11 | """ 12 | if not results: 13 | return "没有找到相关的网络信息。" 14 | 15 | formatted_string = "根据网络搜索结果:\n\n" 16 | for i, res in enumerate(results, 1): 17 | title = res.get("title", "无标题") 18 | url = res.get("url", "#") 19 | snippet = res.get("snippet", "无摘要") 20 | provider = res.get("provider", "未知来源") 21 | 22 | formatted_string += f"{i}. **{title}** (来自: {provider})\n" 23 | formatted_string += f" - 摘要: {snippet}\n" 24 | formatted_string += f" - 来源: {url}\n\n" 25 | 26 | return formatted_string 27 | 28 | 29 | def format_url_parse_results(results: list[dict[str, Any]]) -> str: 30 | """ 31 | 将成功解析的URL结果列表格式化为一段简洁的文本。 32 | """ 33 | formatted_parts = [] 34 | for res in results: 35 | title = res.get("title", "无标题") 36 | url = res.get("url", "#") 37 | snippet = res.get("snippet", "无摘要") 38 | source = res.get("source", "未知") 39 | 40 | formatted_string = f"**{title}**\n" 41 | formatted_string += f"**内容摘要**:\n{snippet}\n" 42 | formatted_string += f"**来源**: {url} (由 {source} 解析)\n" 43 | formatted_parts.append(formatted_string) 44 | 45 | return "\n---\n".join(formatted_parts) 46 | 47 | 48 | def deduplicate_results(results: list[dict[str, Any]]) -> list[dict[str, Any]]: 49 | """ 50 | 根据URL去重搜索结果 51 | """ 52 | unique_urls = set() 53 | unique_results = [] 54 | for res in results: 55 | if isinstance(res, dict) and res.get("url") and res["url"] not in unique_urls: 56 | unique_urls.add(res["url"]) 57 | unique_results.append(res) 58 | return unique_results 59 | -------------------------------------------------------------------------------- /src/plugins/built_in/web_search_tool/utils/url_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL processing utilities 3 | """ 4 | 5 | import re 6 | 7 | 8 | def parse_urls_from_input(urls_input) -> list[str]: 9 | """ 10 | 从输入中解析URL列表 11 | """ 12 | if isinstance(urls_input, str): 13 | # 如果是字符串,尝试解析为URL列表 14 | # 提取所有HTTP/HTTPS URL 15 | url_pattern = r"https?://[^\s\],]+" 16 | urls = re.findall(url_pattern, urls_input) 17 | if not urls: 18 | # 如果没有找到标准URL,将整个字符串作为单个URL 19 | if urls_input.strip().startswith(("http://", "https://")): 20 | urls = [urls_input.strip()] 21 | else: 22 | return [] 23 | elif isinstance(urls_input, list): 24 | urls = [url.strip() for url in urls_input if isinstance(url, str) and url.strip()] 25 | else: 26 | return [] 27 | 28 | return urls 29 | 30 | 31 | def validate_urls(urls: list[str]) -> list[str]: 32 | """ 33 | 验证URL格式,返回有效的URL列表 34 | """ 35 | valid_urls = [] 36 | for url in urls: 37 | if url.startswith(("http://", "https://")): 38 | valid_urls.append(url) 39 | return valid_urls 40 | -------------------------------------------------------------------------------- /src/schedule/monthly_plan_manager.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from datetime import datetime, timedelta 3 | 4 | from src.common.logger import get_logger 5 | from src.manager.async_task_manager import AsyncTask, async_task_manager 6 | 7 | from .plan_manager import PlanManager 8 | 9 | logger = get_logger("monthly_plan_manager") 10 | 11 | 12 | class MonthlyPlanManager: 13 | def __init__(self): 14 | self.plan_manager = PlanManager() 15 | self.monthly_task_started = False 16 | 17 | async def initialize(self): 18 | logger.info("正在初始化月度计划管理器...") 19 | await self.start_monthly_plan_generation() 20 | logger.info("月度计划管理器初始化成功") 21 | 22 | async def start_monthly_plan_generation(self): 23 | if not self.monthly_task_started: 24 | logger.info(" 正在启动每月月度计划生成任务...") 25 | task = MonthlyPlanGenerationTask(self) 26 | await async_task_manager.add_task(task) 27 | self.monthly_task_started = True 28 | logger.info(" 每月月度计划生成任务已成功启动。") 29 | logger.info(" 执行启动时月度计划检查...") 30 | await self.plan_manager.ensure_and_generate_plans_if_needed() 31 | else: 32 | logger.info(" 每月月度计划生成任务已在运行中。") 33 | 34 | async def ensure_and_generate_plans_if_needed(self, target_month: str | None = None) -> bool: 35 | return await self.plan_manager.ensure_and_generate_plans_if_needed(target_month) 36 | 37 | 38 | class MonthlyPlanGenerationTask(AsyncTask): 39 | def __init__(self, monthly_plan_manager: MonthlyPlanManager): 40 | super().__init__(task_name="MonthlyPlanGenerationTask") 41 | self.monthly_plan_manager = monthly_plan_manager 42 | 43 | async def run(self): 44 | while True: 45 | try: 46 | now = datetime.now() 47 | if now.month == 12: 48 | next_month = datetime(now.year + 1, 1, 1) 49 | else: 50 | next_month = datetime(now.year, now.month + 1, 1) 51 | sleep_seconds = (next_month - now).total_seconds() 52 | logger.info( 53 | f" 下一次月度计划生成任务将在 {sleep_seconds:.2f} 秒后运行 (北京时间 {next_month.strftime('%Y-%m-%d %H:%M:%S')})" 54 | ) 55 | await asyncio.sleep(sleep_seconds) 56 | last_month = (next_month - timedelta(days=1)).strftime("%Y-%m") 57 | await self.monthly_plan_manager.plan_manager.archive_current_month_plans(last_month) 58 | current_month = next_month.strftime("%Y-%m") 59 | logger.info(f" 到达月初,开始生成 {current_month} 的月度计划...") 60 | await self.monthly_plan_manager.plan_manager._generate_monthly_plans_logic(current_month) 61 | except asyncio.CancelledError: 62 | logger.info(" 每月月度计划生成任务被取消。") 63 | break 64 | except Exception as e: 65 | logger.error(f" 每月月度计划生成任务发生未知错误: {e}") 66 | await asyncio.sleep(3600) 67 | 68 | 69 | monthly_plan_manager = MonthlyPlanManager() 70 | -------------------------------------------------------------------------------- /src/schedule/schemas.py: -------------------------------------------------------------------------------- 1 | # mmc/src/schedule/schemas.py 2 | 3 | from datetime import datetime, time 4 | 5 | from pydantic import BaseModel, validator 6 | 7 | 8 | class ScheduleItem(BaseModel): 9 | """单个日程项的Pydantic模型""" 10 | 11 | time_range: str 12 | activity: str 13 | 14 | @validator("time_range") 15 | def validate_time_range(cls, v): 16 | """验证时间范围格式""" 17 | if not v or "-" not in v: 18 | raise ValueError("时间范围必须包含'-'分隔符") 19 | 20 | try: 21 | start_str, end_str = v.split("-", 1) 22 | start_str = start_str.strip() 23 | end_str = end_str.strip() 24 | 25 | # 验证时间格式 26 | datetime.strptime(start_str, "%H:%M") 27 | datetime.strptime(end_str, "%H:%M") 28 | 29 | return v 30 | except ValueError as e: 31 | raise ValueError(f"时间格式无效,应为HH:MM-HH:MM格式: {e}") from e 32 | 33 | @validator("activity") 34 | def validate_activity(cls, v): 35 | """验证活动描述""" 36 | if not v or not v.strip(): 37 | raise ValueError("活动描述不能为空") 38 | return v.strip() 39 | 40 | 41 | class ScheduleData(BaseModel): 42 | """完整日程数据的Pydantic模型""" 43 | 44 | schedule: list[ScheduleItem] 45 | 46 | @validator("schedule") 47 | def validate_schedule_completeness(cls, v): 48 | """验证日程是否覆盖24小时""" 49 | if not v: 50 | raise ValueError("日程不能为空") 51 | 52 | # 收集所有时间段 53 | time_ranges = [] 54 | for item in v: 55 | try: 56 | start_str, end_str = item.time_range.split("-", 1) 57 | start_time = datetime.strptime(start_str.strip(), "%H:%M").time() 58 | end_time = datetime.strptime(end_str.strip(), "%H:%M").time() 59 | time_ranges.append((start_time, end_time)) 60 | except ValueError: 61 | continue 62 | 63 | # 检查是否覆盖24小时 64 | if not cls._check_24_hour_coverage(time_ranges): 65 | raise ValueError("日程必须覆盖完整的24小时") 66 | 67 | return v 68 | 69 | @staticmethod 70 | def _check_24_hour_coverage(time_ranges: list[tuple]) -> bool: 71 | """检查时间段是否覆盖24小时""" 72 | if not time_ranges: 73 | return False 74 | 75 | # 将时间转换为分钟数进行计算 76 | def time_to_minutes(t: time) -> int: 77 | return t.hour * 60 + t.minute 78 | 79 | # 创建覆盖情况数组 (1440分钟 = 24小时) 80 | covered = [False] * 1440 81 | 82 | for start_time, end_time in time_ranges: 83 | start_min = time_to_minutes(start_time) 84 | end_min = time_to_minutes(end_time) 85 | 86 | if start_min <= end_min: 87 | # 同一天内的时间段 88 | for i in range(start_min, end_min): 89 | if i < 1440: 90 | covered[i] = True 91 | else: 92 | # 跨天的时间段 93 | for i in range(start_min, 1440): 94 | covered[i] = True 95 | for i in range(0, end_min): 96 | covered[i] = True 97 | 98 | # 检查是否所有分钟都被覆盖 99 | return all(covered) 100 | -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 工具模块 3 | """ 4 | -------------------------------------------------------------------------------- /template/template.env: -------------------------------------------------------------------------------- 1 | HOST=127.0.0.1 2 | PORT=8000 3 | EULA_CONFIRMED=false --------------------------------------------------------------------------------