diff --git a/docs-src/lpmm_parameters_guide.md b/docs-src/lpmm_parameters_guide.md index aad9d874..5d190589 100644 --- a/docs-src/lpmm_parameters_guide.md +++ b/docs-src/lpmm_parameters_guide.md @@ -75,7 +75,6 @@ max_embedding_workers = 12 # 嵌入/抽取并发线程数 embedding_chunk_size = 16 # 每批嵌入的条数 info_extraction_workers = 3 # 实体抽取同时执行线程数 enable_ppr = true # 是否启用PPR,低配机器可关闭 -ppr_node_cap = 8000 # 图节点数超过该值时自动跳过PPR ``` - `embedding_dimension` @@ -101,8 +100,6 @@ ppr_node_cap = 8000 # 图节点数超过该值时自动跳过PPR - `true`:检索会结合向量+知识图,效果更好,但略慢; - `false`:只用向量检索,牺牲一定效果,性能更稳定。 -- `ppr_node_cap` - 安全阈值:当图节点数超过阈值时自动跳过 PPR,以避免“大图”导致卡顿。 > 调参建议: > - 若导入/检索阶段机器明显“顶不住”(>=1MB的大文本,且分配配置<4C),优先调低: diff --git a/src/chat/knowledge/__init__.py b/src/chat/knowledge/__init__.py index a570277c..57e94472 100644 --- a/src/chat/knowledge/__init__.py +++ b/src/chat/knowledge/__init__.py @@ -42,7 +42,10 @@ def lpmm_start_up(): # sourcery skip: extract-duplicate-method logger.info("创建LLM客户端") # 初始化Embedding库 - embed_manager = EmbeddingManager() + embed_manager = EmbeddingManager( + max_workers=global_config.lpmm_knowledge.max_embedding_workers, + chunk_size=global_config.lpmm_knowledge.embedding_chunk_size, + ) logger.info("正在从文件加载Embedding库") try: embed_manager.load_from_file() diff --git a/src/chat/knowledge/kg_manager.py b/src/chat/knowledge/kg_manager.py index 245d6e9e..8108c296 100644 --- a/src/chat/knowledge/kg_manager.py +++ b/src/chat/knowledge/kg_manager.py @@ -358,12 +358,9 @@ class KGManager: paragraph_search_result: ParagraphEmbedding的搜索结果(paragraph_hash, similarity) embed_manager: EmbeddingManager对象 """ - # 性能保护:关闭或超限时直接返回向量检索结果(仅基于节点规模与开关) - if ( - not global_config.lpmm_knowledge.enable_ppr - or len(self.graph.get_node_list()) > global_config.lpmm_knowledge.ppr_node_cap - ): - logger.info("PPR 已禁用或超出阈值,使用纯向量检索结果") + # 性能保护:关闭时直接返回向量检索结果 + if not global_config.lpmm_knowledge.enable_ppr: + logger.info("PPR 已禁用,使用纯向量检索结果") return paragraph_search_result, None # 图中存在的节点总集 existed_nodes = self.graph.get_node_list() diff --git a/src/config/config.py b/src/config/config.py index 949898ab..4001d4ff 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -34,6 +34,7 @@ from src.config.official_configs import ( MemoryConfig, DebugConfig, DreamConfig, + WebUIConfig, ) from .api_ada_configs import ( @@ -347,6 +348,7 @@ class Config(ConfigBase): response_post_process: ResponsePostProcessConfig response_splitter: ResponseSplitterConfig telemetry: TelemetryConfig + webui: WebUIConfig experimental: ExperimentalConfig maim_message: MaimMessageConfig lpmm_knowledge: LPMMKnowledgeConfig diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 62c851b4..1b6a73ae 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -597,6 +597,38 @@ class TelemetryConfig(ConfigBase): """是否启用遥测""" +@dataclass +class WebUIConfig(ConfigBase): + """WebUI配置类""" + + enabled: bool = True + """是否启用WebUI""" + + mode: Literal["development", "production"] = "production" + """运行模式:development(开发) 或 production(生产)""" + + host: str = "0.0.0.0" + """WebUI服务器监听地址""" + + port: int = 8001 + """WebUI服务器端口""" + + anti_crawler_mode: Literal["false", "strict", "loose", "basic"] = "basic" + """防爬虫模式:false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止)""" + + allowed_ips: str = "127.0.0.1" + """IP白名单(逗号分隔,支持精确IP、CIDR格式和通配符)""" + + trusted_proxies: str = "" + """信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任""" + + trust_xff: bool = False + """是否启用X-Forwarded-For代理解析(默认false)""" + + secure_cookie: bool = False + """是否启用安全Cookie(仅通过HTTPS传输,默认false)""" + + @dataclass class DebugConfig(ConfigBase): """调试配置类""" @@ -722,6 +754,18 @@ class LPMMKnowledgeConfig(ConfigBase): embedding_dimension: int = 1024 """嵌入向量维度,应该与模型的输出维度一致""" + max_embedding_workers: int = 3 + """嵌入/抽取并发线程数""" + + embedding_chunk_size: int = 4 + """每批嵌入的条数""" + + max_synonym_entities: int = 2000 + """同义边参与的实体数上限,超限则跳过""" + + enable_ppr: bool = True + """是否启用PPR,低配机器可关闭""" + @dataclass class DreamConfig(ConfigBase): diff --git a/src/main.py b/src/main.py index b6141ad4..57950bf9 100644 --- a/src/main.py +++ b/src/main.py @@ -44,10 +44,9 @@ class MainSystem: def _setup_webui_server(self): """设置独立的 WebUI 服务器""" - import os + from src.config.config import global_config - webui_enabled = os.getenv("WEBUI_ENABLED", "false").lower() == "true" - if not webui_enabled: + if not global_config.webui.enabled: logger.info("WebUI 已禁用") return diff --git a/src/webui/anti_crawler.py b/src/webui/anti_crawler.py index 7daf27f3..28d87170 100644 --- a/src/webui/anti_crawler.py +++ b/src/webui/anti_crawler.py @@ -124,10 +124,8 @@ SCANNER_SPECIFIC_HEADERS = { # strict: 严格模式(更严格的检测,更低的频率限制) # loose: 宽松模式(较宽松的检测,较高的频率限制) # basic: 基础模式(只记录恶意访问,不阻止,不限制请求数,不跟踪IP) -ANTI_CRAWLER_MODE = os.getenv("WEBUI_ANTI_CRAWLER_MODE", "basic").lower() - -# IP白名单配置(从环境变量读取,逗号分隔) +# IP白名单配置(从配置文件读取,逗号分隔) # 支持格式: # - 精确IP:127.0.0.1, 192.168.1.100 # - CIDR格式:192.168.1.0/24, 172.17.0.0/16 (适用于Docker网络) @@ -236,13 +234,23 @@ def _convert_wildcard_to_regex(wildcard_pattern: str) -> Optional[str]: return regex -ALLOWED_IPS = _parse_allowed_ips(os.getenv("WEBUI_ALLOWED_IPS", "")) +# 从配置读取防爬虫设置(延迟导入避免循环依赖) +def _get_anti_crawler_config(): + """获取防爬虫配置""" + from src.config.config import global_config + return { + 'mode': global_config.webui.anti_crawler_mode, + 'allowed_ips': _parse_allowed_ips(global_config.webui.allowed_ips), + 'trusted_proxies': _parse_allowed_ips(global_config.webui.trusted_proxies), + 'trust_xff': global_config.webui.trust_xff + } -# 信任的代理IP配置(从环境变量读取,逗号分隔) -# 只有在信任的代理IP下才使用X-Forwarded-For头 -# 默认关闭(空),不信任任何代理 -TRUSTED_PROXIES = _parse_allowed_ips(os.getenv("WEBUI_TRUSTED_PROXIES", "")) -TRUST_XFF = os.getenv("WEBUI_TRUST_XFF", "false").lower() == "true" +# 初始化配置(将在模块加载时执行) +_config = _get_anti_crawler_config() +ANTI_CRAWLER_MODE = _config['mode'] +ALLOWED_IPS = _config['allowed_ips'] +TRUSTED_PROXIES = _config['trusted_proxies'] +TRUST_XFF = _config['trust_xff'] def _get_mode_config(mode: str) -> dict: diff --git a/src/webui/auth.py b/src/webui/auth.py index 6f527a04..db0fc675 100644 --- a/src/webui/auth.py +++ b/src/webui/auth.py @@ -3,10 +3,10 @@ WebUI 认证模块 提供统一的认证依赖,支持 Cookie 和 Header 两种方式 """ -import os from typing import Optional from fastapi import HTTPException, Cookie, Header, Response, Request from src.common.logger import get_logger +from src.config.config import global_config from .token_manager import get_token_manager logger = get_logger("webui.auth") @@ -23,23 +23,18 @@ def _is_secure_environment() -> bool: Returns: bool: 如果应该使用 secure cookie 则返回 True """ - # 检查环境变量 - secure_cookie_env = os.environ.get("WEBUI_SECURE_COOKIE", "") - if secure_cookie_env.lower() in ("true", "1", "yes"): - logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},启用 secure cookie") + # 从配置读取 + if global_config.webui.secure_cookie: + logger.info("配置中启用了 secure_cookie") return True - if secure_cookie_env.lower() in ("false", "0", "no"): - logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},禁用 secure cookie") - return False - + # 检查是否是生产环境 - env = os.environ.get("WEBUI_MODE", "").lower() - if env in ("production", "prod"): - logger.info(f"WEBUI_MODE 设置为 {env},启用 secure cookie") + if global_config.webui.mode == "production": + logger.info("WebUI运行在生产模式,启用 secure cookie") return True # 默认:开发环境不启用(因为通常是 HTTP) - logger.debug(f"未设置特殊环境变量 (WEBUI_SECURE_COOKIE={secure_cookie_env}, WEBUI_MODE={env}),禁用 secure cookie") + logger.debug("WebUI运行在开发模式,禁用 secure cookie") return False @@ -111,7 +106,7 @@ def set_auth_cookie(response: Response, token: str, request: Optional[Request] = logger.warning("=" * 80) logger.warning("检测到 HTTP 连接但环境配置要求 HTTPS (secure cookie)") logger.warning("已自动禁用 secure 标志以允许登录,但建议修改配置:") - logger.warning("1. 在 .env 文件中设置: WEBUI_SECURE_COOKIE=false") + logger.warning("1. 在配置文件中设置: webui.secure_cookie = false") logger.warning("2. 如果使用反向代理,请确保正确配置 X-Forwarded-Proto 头") logger.warning("=" * 80) is_secure = False diff --git a/src/webui/webui_server.py b/src/webui/webui_server.py index 267787c4..d2dca8dd 100644 --- a/src/webui/webui_server.py +++ b/src/webui/webui_server.py @@ -1,6 +1,5 @@ """独立的 WebUI 服务器 - 运行在 0.0.0.0:8001""" -import os import asyncio import mimetypes from pathlib import Path @@ -130,9 +129,10 @@ class WebUIServer: """配置防爬虫中间件""" try: from src.webui.anti_crawler import AntiCrawlerMiddleware + from src.config.config import global_config - # 从环境变量读取防爬虫模式(false/strict/loose/basic) - anti_crawler_mode = os.getenv("WEBUI_ANTI_CRAWLER_MODE", "basic").lower() + # 从配置读取防爬虫模式 + anti_crawler_mode = global_config.webui.anti_crawler_mode # 注意:中间件按注册顺序反向执行,所以先注册的中间件后执行 # 我们需要在CORS之前注册,这样防爬虫检查会在CORS之前执行 @@ -186,7 +186,7 @@ class WebUIServer: error_msg = f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用" logger.error(error_msg) logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}") - logger.error("💡 可以通过环境变量 WEBUI_PORT 修改 WebUI 端口") + logger.error("💡 可以在配置文件中修改 webui.port 来更改 WebUI 端口") logger.error(f"💡 Windows 用户可以运行: netstat -ano | findstr :{self.port}") logger.error(f"💡 Linux/Mac 用户可以运行: lsof -i :{self.port}") raise OSError(f"端口 {self.port} 已被占用,无法启动 WebUI 服务器") @@ -201,9 +201,21 @@ class WebUIServer: self._server = UvicornServer(config=config) logger.info("🌐 WebUI 服务器启动中...") - logger.info(f"🌐 访问地址: http://{self.host}:{self.port}") - if self.host == "0.0.0.0": - logger.info(f"本机访问请使用 http://localhost:{self.port}") + + # 根据地址类型显示正确的访问地址 + if ':' in self.host: + # IPv6 地址需要用方括号包裹 + logger.info(f"🌐 访问地址: http://[{self.host}]:{self.port}") + if self.host == "::": + logger.info(f"💡 IPv6 本机访问: http://[::1]:{self.port}") + logger.info(f"💡 IPv4 本机访问: http://127.0.0.1:{self.port}") + elif self.host == "::1": + logger.info("💡 仅支持 IPv6 本地访问") + else: + # IPv4 地址 + logger.info(f"🌐 访问地址: http://{self.host}:{self.port}") + if self.host == "0.0.0.0": + logger.info(f"💡 本机访问: http://localhost:{self.port} 或 http://127.0.0.1:{self.port}") try: await self._server.serve() @@ -212,7 +224,7 @@ class WebUIServer: if "address already in use" in str(e).lower() or e.errno in (98, 10048): # 98: Linux, 10048: Windows logger.error(f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用") logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}") - logger.error("💡 可以通过环境变量 WEBUI_PORT 修改 WebUI 端口") + logger.error("💡 可以在配置文件中修改 webui.port 来更改 WebUI 端口") else: logger.error(f"❌ WebUI 服务器启动失败 (网络错误): {e}") raise @@ -221,14 +233,24 @@ class WebUIServer: raise def _check_port_available(self) -> bool: - """检查端口是否可用""" + """检查端口是否可用(支持 IPv4 和 IPv6)""" import socket + # 判断使用 IPv4 还是 IPv6 + if ':' in self.host: + # IPv6 地址 + family = socket.AF_INET6 + test_host = self.host if self.host != "::" else "::1" + else: + # IPv4 地址 + family = socket.AF_INET + test_host = self.host if self.host != "0.0.0.0" else "127.0.0.1" + try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + with socket.socket(family, socket.SOCK_STREAM) as s: s.settimeout(1) # 尝试绑定端口 - s.bind((self.host if self.host != "0.0.0.0" else "127.0.0.1", self.port)) + s.bind((test_host, self.port)) return True except OSError: return False @@ -257,8 +279,9 @@ def get_webui_server() -> WebUIServer: """获取全局 WebUI 服务器实例""" global _webui_server if _webui_server is None: - # 从环境变量读取配置 - host = os.getenv("WEBUI_HOST", "0.0.0.0") - port = int(os.getenv("WEBUI_PORT", "8001")) + # 从配置读取 + from src.config.config import global_config + host = global_config.webui.host + port = global_config.webui.port _webui_server = WebUIServer(host=host, port=port) return _webui_server diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index b4543433..4883bc51 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -196,8 +196,6 @@ max_embedding_workers = 3 # 嵌入/抽取并发线程数 embedding_chunk_size = 4 # 每批嵌入的条数 max_synonym_entities = 2000 # 同义边参与的实体数上限,超限则跳过 enable_ppr = true # 是否启用PPR,低配机器可关闭 -ppr_node_cap = 8000 # 图节点数超过该值时跳过PPR -webui_graph_default_limit = 200 # WebUI /graph 默认返回的最大节点数,避免大图负载 [keyword_reaction] keyword_rules = [ @@ -265,6 +263,25 @@ api_server_allowed_api_keys = [] # 新版API Server允许的API Key列表,为 [telemetry] #发送统计信息,主要是看全球有多少只麦麦 enable = true +[webui] # WebUI 独立服务器配置 +enabled = true # 是否启用WebUI +mode = "production" # 模式: development(开发) 或 production(生产) +host = "127.0.0.1" # WebUI 服务器监听地址 + # IPv4: 0.0.0.0 (所有IPv4接口) / 127.0.0.1 (仅本地) + # IPv6: :: (所有接口,支持IPv4+IPv6双栈) / ::1 (仅本地IPv6) + # 推荐使用 :: 实现双栈支持 +port = 8001 # WebUI 服务器端口 + +# 防爬虫配置 +anti_crawler_mode = "basic" # 防爬虫模式: false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止) +allowed_ips = "127.0.0.1" # IP白名单(逗号分隔,支持精确IP、CIDR格式和通配符) + # 示例: 127.0.0.1,192.168.1.0/24,172.17.0.0/16 +trusted_proxies = "" # 信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任 + # 示例: 127.0.0.1,192.168.1.1,172.17.0.1 +trust_xff = false # 是否启用X-Forwarded-For代理解析(默认false) + # 启用后,仍要求直连IP在trusted_proxies中才会信任XFF头 +secure_cookie = false # 是否启用安全Cookie(仅通过HTTPS传输,默认false) + [experimental] #实验性功能 # 为指定聊天添加额外的prompt配置 # 格式: ["platform:id:type:prompt内容", ...] diff --git a/template/template.env b/template/template.env index 932f9c82..091f4ddd 100644 --- a/template/template.env +++ b/template/template.env @@ -1,19 +1,3 @@ # 麦麦主程序配置 HOST=127.0.0.1 -PORT=8000 - -# WebUI 独立服务器配置 -WEBUI_ENABLED=true -WEBUI_MODE=production # 模式: development(开发) 或 production(生产) -WEBUI_HOST=0.0.0.0 # WebUI 服务器监听地址 -WEBUI_PORT=8001 # WebUI 服务器端口 - -# 防爬虫配置 -WEBUI_ANTI_CRAWLER_MODE=basic # 防爬虫模式: false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止) -WEBUI_ALLOWED_IPS=127.0.0.1 # IP白名单(逗号分隔,支持精确IP、CIDR格式和通配符) - # 示例: 127.0.0.1,192.168.1.0/24,172.17.0.0/16 -WEBUI_TRUSTED_PROXIES= # 信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任 - # 示例: 127.0.0.1,192.168.1.1,172.17.0.1 -WEBUI_TRUST_XFF=false # 是否启用X-Forwarded-For代理解析(默认false) - # 启用后,仍要求直连IP在TRUSTED_PROXIES中才会信任XFF头 -WEBUI_SECURE_COOKIE=false # 是否启用安全Cookie(仅通过HTTPS传输,默认false) \ No newline at end of file +PORT=8000 \ No newline at end of file diff --git a/webui/dist/assets/index-BNRBabgA.css b/webui/dist/assets/index-BNRBabgA.css new file mode 100644 index 00000000..b8a3172e Binary files /dev/null and b/webui/dist/assets/index-BNRBabgA.css differ diff --git a/webui/dist/assets/index-BNVFwwkr.js b/webui/dist/assets/index-BNVFwwkr.js new file mode 100644 index 00000000..34bf4b02 Binary files /dev/null and b/webui/dist/assets/index-BNVFwwkr.js differ diff --git a/webui/dist/assets/index-BwEmxTOV.css b/webui/dist/assets/index-BwEmxTOV.css deleted file mode 100644 index 313c52f5..00000000 Binary files a/webui/dist/assets/index-BwEmxTOV.css and /dev/null differ diff --git a/webui/dist/assets/index-C8KuzKjI.js b/webui/dist/assets/index-C8KuzKjI.js deleted file mode 100644 index 3f3f2310..00000000 Binary files a/webui/dist/assets/index-C8KuzKjI.js and /dev/null differ diff --git a/webui/dist/index.html b/webui/dist/index.html index f6f54614..f976fe86 100644 Binary files a/webui/dist/index.html and b/webui/dist/index.html differ