{"repo": "blackoutta/ai-engineer-training", "files": {"week04/p23/config.json": {"83a4bc18b95788ad4856433cda9af6566a52b7a1": {"t": 1785674211.7725334, "n": 897, "h": "1a57a2fa2fc79cf8", "c": "{\r\n \"intents\": [\r\n \"query_order\",\r\n \"refund_request\", \r\n \"issue_invoice\",\r\n \"logistics_inquiry\",\r\n \"cancel_order\",\r\n \"unknown\"\r\n ],\r\n \"enable_rule_engine\": true,\r\n \"enable_ml_model\": false,\r\n \"enable_llm_router\": true,\r\n \"voting_strategy\": \"priority\",\r\n \"rule_keywords\": {\r\n \"query_order\": [\"查订单\", \"订单号\", \"订单状态\", \"我的订单\"],\r\n \"refund_request\": [\"退钱\", \"退款\", \"取消\", \"申请退款\"],\r\n \"issue_invoice\": [\"开发票\", \"要发票\", \"报销\", \"发票\"],\r\n \"logistics_inquiry\": [\"物流\", \"快递\", \"配送\", \"运输\"],\r\n \"cancel_order\": [\"取消订单\", \"不要了\", \"取消\"]\r\n },\r\n \"ml_model\": {\r\n \"model_name\": \"qwen-turbo\",\r\n \"api_key\": \"your_dashscope_api_key_here\",\r\n \"base_url\": \"https://dashscope.aliyuncs.com/api/v1\"\r\n },\r\n \"llm\": {\r\n \"model\": \"qwen-plus\",\r\n \"temperature\": 0,\r\n \"api_key\": \"your_dashscope_api_key_here\",\r\n \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\r\n }\r\n}"}}, "projects/project4_2/demo.py": {"83a4bc18b95788ad4856433cda9af6566a52b7a1": {"t": 1785674244.253179, "n": 9363, "h": "41172cc298ab429d", "c": "import os\nimport sqlite3\nimport json\nimport time\nfrom typing import List, Dict, Any, Optional, TypedDict, Annotated\nfrom datetime import datetime\nimport operator\n\n# 第三方库导入\nfrom pydantic import BaseModel, Field\nimport dashscope\nfrom dashscope.audio.asr import Recognition\nfrom http import HTTPStatus\n\n# LangChain 导入\nfrom langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage, ToolMessage\nfrom langchain_core.tools import tool\nfrom langchain_core.prompts import ChatPromptTemplate\nfrom langchain_core.output_parsers import StrOutputParser\nfrom langchain_core.runnables import RunnableConfig\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.embeddings import FakeEmbeddings\nfrom langchain_community.vectorstores import InMemoryVectorStore\nfrom langchain_core.documents import Document\n\n# LangGraph 导入\nfrom langgraph.graph import StateGraph, START, END\nfrom langgraph.prebuilt import ToolNode\nfrom langgraph.checkpoint.sqlite import SqliteSaver\n\n# --- 配置 ---\n# 说明:实际使用时请在环境变量中设置 DASHSCOPE_API_KEY。\n# os.environ[\"DASHSCOPE_API_KEY\"] = \"sk-...\" \n# 如果未检测到密钥,本演示会使用简易规则引擎(Mock LLM)。\n\nDB_PATH = \"orders.db\"\n\n# --- 1. 数据库初始化 (SQLite) ---\ndef setup_database():\n \"\"\"初始化 SQLite 数据库并写入示例订单数据。\"\"\"\n conn = sqlite3.connect(DB_PATH)\n cursor = conn.cursor()\n \n # 创建订单表\n cursor.execute('''\n CREATE TABLE IF NOT EXISTS orders (\n order_id TEXT PRIMARY KEY,\n user_id TEXT,\n status TEXT,\n items TEXT,\n logistics_info TEXT,\n created_at TEXT\n )\n ''')\n \n # 检查是否已有数据\n cursor.execute('SELECT count(*) FROM orders')\n if cursor.fetchone()[0] == 0:\n print(\"正在写入示例订单数据...\")\n sample_orders = [\n (\"12345\", \"user_001\", \"shipped\", \"Wireless Headphones\", \"Arrived at Beijing Sorting Center\", datetime.now().isoformat()),\n (\"67890\", \"user_001\", \"pending_payment\", \"Smart Watch\", \"Waiting for payment\", datetime.now().isoformat()),\n (\"11223\", \"user_002\", \"delivered\", \"Laptop Stand\", \"Delivered to locker\", datetime.now().isoformat()),\n ]\n cursor.executemany('INSERT INTO orders VALUES (?,?,?,?,?,?)', sample_orders)\n conn.commit()\n \n # LangGraph 对话检查点持久化说明:\n # SqliteSaver 会自动创建所需的检查点表,本演示直接依赖其默认行为。\n \n conn.close()\n\n# --- 2. RAG 初始化 (知识库) ---\ndef setup_rag_retriever():\n \"\"\"初始化一个用于检索政策知识的简易 RAG 检索器。\"\"\"\n policies = [\n \"退款政策:自签收之日起 7 天内,且商品未拆封,可发起退款申请。\",\n \"物流政策:满 50 美元免邮,标准配送一般为 3-5 个工作日。\",\n \"质保政策:电子类商品享受 1 年制造商质保服务。\",\n \"支付政策:支持信用卡、PayPal 和支付宝。\",\n \"订单修改:订单状态变为“已发货”后不可再修改订单信息。\"\n ]\n \n documents = [Document(page_content=p, metadata={\"source\": \"policy_doc\"}) for p in policies]\n \n # 说明:演示环境使用 FakeEmbeddings,无需外部 API。\n # 生产环境可替换为 OpenAIEmbeddings 或 DashScopeEmbeddings。\n embeddings = FakeEmbeddings(size=768) \n \n vectorstore = InMemoryVectorStore.from_documents(documents, embeddings)\n return vectorstore.as_retriever()\n\n# --- 3. 工具定义 ---\n\n@tool\ndef check_order(order_id: str) -> str:\n \"\"\"根据订单号查询订单状态与物流信息。\"\"\"\n conn = sqlite3.connect(DB_PATH)\n cursor = conn.cursor()\n cursor.execute('SELECT status, items, logistics_info FROM orders WHERE order_id = ?', (order_id,))\n result = cursor.fetchone()\n conn.close()\n \n if result:\n status, items, logistics = result\n return f\"订单 {order_id}({items}):当前状态为『{status}』。物流信息:{logistics}。\"\n else:\n return f\"未查询到订单 {order_id},请检查订单号是否正确。\"\n\n@tool\ndef search_policy(query: str) -> str:\n \"\"\"查询与客服政策相关的知识(退款、物流等)。\"\"\"\n # 说明:真实环境可复用全局检索器;为保证工具无状态,这里简单重新初始化。\n retriever = setup_rag_retriever()\n docs = retriever.invoke(query)\n return \"\\n\".join([doc.page_content for doc in docs])\n\n# --- 4. 输入处理 (ASR/OCR) ---\n\ndef process_audio_input(file_path: str) -> str:\n \"\"\"使用 Qwen/Dashscope 进行语音转写(演示中为模拟)。\"\"\"\n print(f\"[系统] 正在处理音频文件:{file_path}\")\n api_key = os.getenv(\"DASHSCOPE_API_KEY\")\n if not api_key:\n print(\"[系统] 未检测到 API Key,返回模拟 ASR 结果。\")\n return \"查订单 12345\" # 模拟结果\n \n try:\n # 真实实现:使用 DashScope ASR\n # task = dashscope.audio.asr.Recognition.call(...)\n # 演示保持为模拟,返回固定文本。\n return \"查订单 12345\"\n except Exception as e:\n return f\"Error in ASR: {str(e)}\"\n\ndef process_image_input(file_path: str) -> str:\n \"\"\"使用 Qwen-VL 进行 OCR(演示中为模拟)。\"\"\"\n print(f\"[系统] 正在处理图片文件:{file_path}\")\n api_key = os.getenv(\"DASHSCOPE_API_KEY\")\n if not api_key:\n print(\"[系统] 未检测到 API Key,返回模拟 OCR 结果。\")\n return \"图片中订单号似乎是 67890\" # 模拟结果\n\n try:\n # 真实实现:使用 DashScope 多模态\n # messages = [{...}]\n # response = dashscope.MultiModalConversation.call(model='qwen-vl-max', messages=messages)\n return \"图片中订单号似乎是 67890\"\n except Exception as e:\n return f\"Error in OCR: {str(e)}\"\n\n# --- 5. LangGraph 状态与节点 ---\n\nclass AgentState(TypedDict):\n messages: Annotated[List[BaseMessage], operator.add]\n order_id: Optional[str]\n\n# LLM 初始化\n# 如果存在密钥则使用真实模型,否则使用简易规则引擎(Mock)。\napi_key = os.getenv(\"DASHSCOPE_API_KEY\") or os.getenv(\"OPENAI_API_KEY\")\nif api_key:\n # 使用 ChatOpenAI(如需对接 Qwen 可设置兼容的 base_url);此处默认使用 OpenAI。\n llm = ChatOpenAI(model=\"gpt-4o\", temperature=0)\nelse:\n # 未检测到密钥,使用规则引擎模拟。\n print(\"[系统] 未检测到 LLM API Key,使用规则引擎(Mock)进行演示。\")\n class MockLLM:\n def invoke(self, messages):\n last_msg = messages[-1].content.lower()\n if \"查订单\" in last_msg or \"订单\" in last_msg or \"12345\" in last_msg or \"67890\" in last_msg:\n # 需要调用工具时返回 tool_calls\n if \"12345\" in last_msg:\n return AIMessage(content=\"\", tool_calls=[{\"name\": \"check_order\", \"args\": {\"order_id\": \"12345\"}, \"id\": \"call_1\"}])\n elif \"67890\" in last_msg:\n return AIMessage(content=\"\", tool_calls=[{\"name\": \"check_order\", \"args\": {\"order_id\": \"67890\"}, \"id\": \"call_2\"}])\n else:\n return AIMessage(content=\"请提供订单号。\")\n elif \"政策\" in last_msg or \"退款\" in last_msg:\n return AIMessage(content=\"\", tool_calls=[{\"name\": \"search_policy\", \"args\": {\"query\": last_msg}, \"id\": \"call_3\"}])\n else:\n return AIMessage(content=\"我可以帮您查询订单或解答政策相关问题。\")\n \n def bind_tools(self, tools):\n return self\n\n llm = MockLLM()\n\n# 绑定工具\ntools = [check_order, search_policy]\nllm_with_tools = llm.bind_tools(tools)\n\ndef chatbot_node(state: AgentState):\n \"\"\"根据历史消息决定要采取的动作。\"\"\"\n return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n\ndef input_processing_node(state: AgentState):\n \"\"\"对输入进行预处理(文本/音频/图片)。\"\"\"\n # 说明:真实系统可拆分更细;此处假定用户消息已加入状态,若是文件路径则进行相应处理。\n last_message = state[\"messages\"][-1]\n if isinstance(last_message, HumanMessage):\n content = last_message.content\n if content.endswith(\".wav\") or content.endswith(\".mp3\"):\n text = process_audio_input(content)\n return {\"messages\": [HumanMessage(content=f\"音频转写:{text}\")]}\n elif content.endswith(\".jpg\") or content.endswith(\".png\"):\n text = process_image_input(content)\n return {\"messages\": [HumanMessage(content=f\"图片识别:{text}\")]}\n return {\"messages\": []} # Return empty update explicitly to avoid InvalidUpdateError if that's the cause\n\n# 定义图工作流\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"input_proc\", input_processing_node)\nworkflow.add_node(\"chatbot\", chatbot_node)\nworkflow.add_node(\"tools\", ToolNode(tools))\n\nworkflow.add_edge(START, \"input_proc\")\nworkflow.add_edge(\"input_proc\", \"chatbot\")\n\ndef route_tools(state: AgentState):\n \"\"\"条件路由:判断是否需要调用工具。\"\"\"\n if isinstance(state, list):\n ai_message = state[-1]\n elif isinstance(state, dict) and (messages := state.get(\"messages\", [])):\n ai_message = messages[-1]\n elif isinstance(state, BaseMessage):\n ai_message = state\n else:\n raise ValueError(f\"未在状态中找到消息,无法进行工具路由:{state}\")\n\n if hasattr(ai_message, \"tool_calls\") and len(ai_message.tool_calls) > 0:\n return \"tools\"\n return END\n\nworkflow.add_conditional_edges(\"chatbot\", route_tools)\nworkflow.add_edge(\"tools\", \"chatbot\")\n\n# 配置检查点持久化\nconn = sqlite3.connect(\"checkpoints.db\", check_same_thread=False)\nmemory = SqliteSaver(conn)\n\napp = workflow.compile(checkpointer=memory)\n\n# --- 6. 主流程执行与测试 ---\n\ndef run_demo():\n print(\"--- 订单查询客服演示 ---\")\n setup_database()\n \n # 对话会话 ID(用于状态持久化)\n config = {\"configurable\": {\"thread_id\": \"session_1\"}}\n \n # 模拟用户输入场景\n scenarios = [\n \"你好,我要查订单。\",\n \"订单号是 12345。\",\n \"如果不喜欢可以退货吗?\", # RAG 检索\n \"audio_sample.wav\", # 模拟音频\n \"order_image.jpg\" # 模拟图片\n ]\n \n for user_input in scenarios:\n print(f\"\\n用户:{user_input}\")\n \n # 判断是文件路径还是文本\n msg_content = user_input\n \n # 以流式方式运行图\n events = app.stream(\n {\"messages\": [HumanMessage(content=msg_content)]}, \n config, \n stream_mode=\"values\"\n )\n \n for event in events:\n if \"messages\" in event:\n last_msg = event[\"messages\"][-1]\n if isinstance(last_msg, AIMessage):\n print(f\"客服:{last_msg.content}\")\n if hasattr(last_msg, 'tool_calls') and last_msg.tool_calls:\n print(f\" [工具调用]:{last_msg.tool_calls[0]['name']}\")\n\nif __name__ == \"__main__\":\n run_demo()\n"}}, "week07/p08-vectorMEM.py": {"83a4bc18b95788ad4856433cda9af6566a52b7a1": {"t": 1785674247.7142684, "n": 6411, "h": "6954270d47c9ffca", "c": "# 初始化向量存储\nfrom tabnanny import check\nfrom langgraph.graph import StateGraph, MessagesState, START, END\nfrom langgraph.prebuilt import ToolNode\nfrom langchain_core.vectorstores import InMemoryVectorStore\nfrom langchain_community.embeddings import DashScopeEmbeddings\nfrom langchain_core.documents import Document\nfrom langchain_core.runnables import RunnableConfig\nfrom langchain_core.tools import tool\nfrom langchain_core.messages.utils import get_buffer_string\nfrom typing import List\nimport uuid\nimport os\nfrom langgraph.checkpoint.memory import MemorySaver\nfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\nfrom langchain_community.chat_models.tongyi import ChatTongyi\nfrom langchain_core.messages import AIMessage\n\n# 设置DashScope API密钥\n# os.environ[\"DASHSCOPE_API_KEY\"] = \"sk-your-dashscope-key\" # 请替换为你的实际API密钥\n\n# 初始化向量存储\nrecall_vector_store = InMemoryVectorStore(DashScopeEmbeddings())\n\n# 初始化模型和提示词\nmodel = ChatTongyi(\n model=\"qwen-max\", # 通义千问大模型\n temperature=0.7,\n streaming=True\n)\n\n# 创建提示词模板\nprompt = ChatPromptTemplate.from_messages([\n (\"system\", \"\"\"\n 你是一个有记忆能力的助手。请根据用户的问题和下面提供的记忆内容回答。\n 如果记忆中有相关信息,请利用这些信息来回答。\n 如果需要保存新的记忆,请使用save_recall_memory工具。\n \n {recall_memory}\n \"\"\"),\n MessagesPlaceholder(variable_name=\"messages\")\n])\n\n# 定义对记忆进行存储和检索的核心函数\ndef get_user_id(config: RunnableConfig) -> str:\n \"\"\"从RunnableConfig中提取用户ID\"\"\"\n user_id = config[\"configurable\"].get(\"user_id\")\n return user_id\n\n@tool\ndef save_recall_memory(memory: str, config: RunnableConfig) -> str:\n \"\"\"将用户记忆保存到向量存储中\"\"\"\n user_id = get_user_id(config)\n document = Document(\n page_content=memory,\n id = str(uuid.uuid4()),\n metadata={\"user_id\": user_id}\n )\n recall_vector_store.add_documents([document])\n return memory\n\n@tool\ndef search_recall_memories(query: str, config: RunnableConfig) -> List[str]:\n \"\"\"检索用户记忆\"\"\"\n user_id = get_user_id(config)\n def _filter_function(doc: Document) -> bool:\n return doc.metadata.get(\"user_id\") == user_id\n \n documents = recall_vector_store.similarity_search(\n query=query,\n k = 3,\n filter=_filter_function\n )\n return [doc.page_content for doc in documents]\n\n# 声明 State 用于存储对话相关的记忆\nclass State(MessagesState):\n \"\"\"对话状态\"\"\"\n recall_memories: List[str]\n\n# 处理当前状态并使用 LLM 生成回复\ndef agent(state: State) -> State:\n \"\"\"处理当前状态并生成回复\"\"\"\n # 从状态中提取记忆\n model_with_tools = model.bind_tools([save_recall_memory])\n \n # 准备记忆字符串\n recall_str = \"\"\n if \"recall_memories\" in state and state[\"recall_memories\"]:\n recall_str = \"\\n\".join(state[\"recall_memories\"])\n \n # 使用提示词模板和模型生成回复\n response = prompt.invoke({\n \"messages\": state[\"messages\"],\n \"recall_memory\": recall_str\n })\n \n # 使用绑定了工具的模型处理\n prediction = model_with_tools.invoke(response)\n \n # 返回更新后的状态\n return {\n \"messages\": state[\"messages\"] + [prediction],\n }\n\n# 添加记忆保存节点\ndef save_memory(state: State, config: RunnableConfig) -> State:\n \"\"\"保存当前对话内容到记忆中\"\"\"\n # 获取最后一条用户消息\n messages = state[\"messages\"]\n if len(messages) >= 2: # 确保有用户消息和AI回复\n user_msg = None\n ai_msg = None\n \n # 找到最近的用户消息和AI回复\n for msg in reversed(messages):\n if hasattr(msg, \"role\"):\n if msg.role == \"user\" and user_msg is None:\n user_msg = msg.content\n elif msg.role == \"assistant\" and ai_msg is None:\n ai_msg = msg.content\n elif isinstance(msg, dict):\n if msg.get(\"role\") == \"user\" and user_msg is None:\n user_msg = msg.get(\"content\", \"\")\n elif msg.get(\"role\") == \"assistant\" and ai_msg is None:\n ai_msg = msg.get(\"content\", \"\")\n \n if user_msg and ai_msg:\n break\n \n if user_msg and ai_msg:\n # 构建记忆内容\n memory_content = f\"用户问: {user_msg}\\n助手答: {ai_msg}\"\n # 保存到向量存储\n save_recall_memory.invoke(memory_content, config)\n \n return {}\n\n# 加载与当前对话相关的记忆\ndef load_memories(state: State, config: RunnableConfig) -> State:\n \"\"\"加载与当前对话相关的记忆\"\"\"\n # 获取当前对话内容字符串\n convo_str = get_buffer_string(state[\"messages\"])\n convo_str = convo_str[:800]\n recall_memories = search_recall_memories.invoke(convo_str, config)\n return {\n \"recall_memories\": recall_memories,\n }\ndef route_tools(state: State):\n \"\"\"根据最后一条消息决定下一步操作\"\"\"\n # 获取最后一条消息\n msg = state[\"messages\"][-1]\n if msg.tool_calls:\n return \"tools\"\n return END\n\n# 创建状态图并添加节点\nbuilder = StateGraph(State)\nbuilder.add_node(\"load_memories\", load_memories) # 加载记忆节点\nbuilder.add_node(\"agent\", agent) # 处理消息节点\nbuilder.add_node(\"save_memory\", save_memory) # 保存记忆节点\ntools = [save_recall_memory, search_recall_memories]\nbuilder.add_node(\"tools\", ToolNode(tools)) # 工具节点\n\n# 添加边\n# START -> load_memories -> agent -> save_memory -> route_tools\nbuilder.add_edge(START, \"load_memories\") # 从START开始加载记忆\nbuilder.add_edge(\"load_memories\", \"agent\") # 加载记忆后处理消息\nbuilder.add_edge(\"agent\", \"save_memory\") # 处理消息后保存记忆\nbuilder.add_conditional_edges(\n \"save_memory\",\n route_tools, # 保存记忆后路由工具\n [\"tools\", END] # 如果有工具调用,路由到tools节点;否则路由到END\n) # 保存记忆后路由工具\nbuilder.add_edge(\"tools\", \"agent\") # 工具节点处理完成后返回agent节点\n\nmemory = MemorySaver()\n\ngraph = builder.compile(checkpointer=memory)\n\n# 运行配置:设置线程与用户ID,供记忆检索使用\nconfig = {\n \"configurable\": {\n \"thread_id\": \"thread-1\",\n \"user_id\": \"user-1\",\n }\n}\n\n\n# 简单的流式输出处理器:打印每一步生成的消息或记忆\ndef get_stream_chunk(chunk):\n for node, update in chunk.items():\n if update is None:\n continue\n # 打印消息(若有)\n msgs = update.get(\"messages\")\n if msgs:\n last = msgs[-1]\n content = getattr(last, \"content\", None) or (last.get(\"content\") if isinstance(last, dict) else None)\n if content:\n print(content)\n # 打印检索到的记忆(若有)\n if \"recall_memories\" in update and update[\"recall_memories\"]:\n print(f\"[检索到的记忆] {update['recall_memories']}\")\n\n# 第一轮对话\nfor chunk in graph.stream(\n {\"messages\": [{\"role\": \"user\", \"content\": \"我喜欢吃苹果\"}]},\n config = config\n):\n get_stream_chunk(chunk)\n\n# 第二轮询问我喜欢吃什么\nfor chunk in graph.stream(\n {\"messages\": [{\"role\": \"user\", \"content\": \"我喜欢吃什么\"}]},\n config = config\n):\n get_stream_chunk(chunk)\n"}}, "week10/chatgpt-on-wechat/voice/xunfei/xunfei_voice.py": {"83a4bc18b95788ad4856433cda9af6566a52b7a1": {"t": 1785674331.0307403, "n": 3729, "h": "c9e9e7129243c83d", "c": "#####################################################################\r\n# xunfei voice service\r\n# Auth: njnuko\r\n# Email: njnuko@163.com\r\n#\r\n# 要使用本模块, 首先到 xfyun.cn 注册一个开发者账号,\r\n# 之后创建一个新应用, 然后在应用管理的语音识别或者语音合同右边可以查看APPID API Key 和 Secret Key\r\n# 然后在 config.json 中填入这三个值\r\n#\r\n# 配置说明:\r\n# {\r\n# \"APPID\":\"xxx71xxx\",\r\n# \"APIKey\":\"xxxx69058exxxxxx\", #讯飞xfyun.cn控制台语音合成或者听写界面的APIKey\r\n# \"APISecret\":\"xxxx697f0xxxxxx\", #讯飞xfyun.cn控制台语音合成或者听写界面的APIKey\r\n# \"BusinessArgsTTS\":{\"aue\": \"lame\", \"sfl\": 1, \"auf\": \"audio/L16;rate=16000\", \"vcn\": \"xiaoyan\", \"tte\": \"utf8\"}, #语音合成的参数,具体可以参考xfyun.cn的文档\r\n# \"BusinessArgsASR\":{\"domain\": \"iat\", \"language\": \"zh_cn\", \"accent\": \"mandarin\", \"vad_eos\":10000, \"dwa\": \"wpgs\"} #语音听写的参数,具体可以参考xfyun.cn的文档\r\n# }\r\n#####################################################################\r\n\r\nimport json\r\nimport os\r\nimport time\r\n\r\nfrom bridge.reply import Reply, ReplyType\r\nfrom common.log import logger\r\nfrom common.tmp_dir import TmpDir\r\nfrom config import conf\r\nfrom voice.voice import Voice\r\nfrom .xunfei_asr import xunfei_asr\r\nfrom .xunfei_tts import xunfei_tts\r\nfrom voice.audio_convert import any_to_mp3\r\nimport shutil\r\nfrom pydub import AudioSegment\r\n\r\n\r\nclass XunfeiVoice(Voice):\r\n def __init__(self):\r\n try:\r\n curdir = os.path.dirname(__file__)\r\n config_path = os.path.join(curdir, \"config.json\")\r\n conf = None\r\n with open(config_path, \"r\") as fr:\r\n conf = json.load(fr)\r\n print(conf)\r\n self.APPID = str(conf.get(\"APPID\"))\r\n self.APIKey = str(conf.get(\"APIKey\"))\r\n self.APISecret = str(conf.get(\"APISecret\"))\r\n self.BusinessArgsTTS = conf.get(\"BusinessArgsTTS\")\r\n self.BusinessArgsASR= conf.get(\"BusinessArgsASR\")\r\n\r\n except Exception as e:\r\n logger.warn(\"XunfeiVoice init failed: %s, ignore \" % e)\r\n\r\n def voiceToText(self, voice_file):\r\n # 识别本地文件\r\n try:\r\n logger.debug(\"[Xunfei] voice file name={}\".format(voice_file))\r\n #print(\"voice_file===========\",voice_file)\r\n #print(\"voice_file_type===========\",type(voice_file))\r\n #mp3_name, file_extension = os.path.splitext(voice_file)\r\n #mp3_file = mp3_name + \".mp3\"\r\n #pcm_data=get_pcm_from_wav(voice_file)\r\n #mp3_name, file_extension = os.path.splitext(voice_file)\r\n #AudioSegment.from_wav(voice_file).export(mp3_file, format=\"mp3\")\r\n #shutil.copy2(voice_file, 'tmp/test1.wav')\r\n #shutil.copy2(mp3_file, 'tmp/test1.mp3')\r\n #print(\"voice and mp3 file\",voice_file,mp3_file)\r\n text = xunfei_asr(self.APPID,self.APISecret,self.APIKey,self.BusinessArgsASR,voice_file)\r\n logger.info(\"讯飞语音识别到了: {}\".format(text))\r\n reply = Reply(ReplyType.TEXT, text)\r\n except Exception as e:\r\n logger.warn(\"XunfeiVoice init failed: %s, ignore \" % e)\r\n reply = Reply(ReplyType.ERROR, \"讯飞语音识别出错了;{0}\")\r\n return reply\r\n\r\n def textToVoice(self, text):\r\n try:\r\n # Avoid the same filename under multithreading\r\n fileName = TmpDir().path() + \"reply-\" + str(int(time.time())) + \"-\" + str(hash(text) & 0x7FFFFFFF) + \".mp3\"\r\n return_file = xunfei_tts(self.APPID,self.APIKey,self.APISecret,self.BusinessArgsTTS,text,fileName)\r\n logger.info(\"[Xunfei] textToVoice text={} voice file name={}\".format(text, fileName))\r\n reply = Reply(ReplyType.VOICE, fileName)\r\n except Exception as e:\r\n logger.error(\"[Xunfei] textToVoice error={}\".format(fileName))\r\n reply = Reply(ReplyType.ERROR, \"抱歉,讯飞语音合成失败\")\r\n return reply\r\n"}}, "week10/chatgpt-on-wechat/bot/xunfei/xunfei_spark_bot.py": {"83a4bc18b95788ad4856433cda9af6566a52b7a1": {"t": 1785809854.7938607, "n": 9331, "h": "9ba33e964fd41077", "c": "# encoding:utf-8\r\n\r\nimport requests, json\r\nfrom bot.bot import Bot\r\nfrom bot.session_manager import SessionManager\r\nfrom bot.chatgpt.chat_gpt_session import ChatGPTSession\r\nfrom bridge.context import ContextType, Context\r\nfrom bridge.reply import Reply, ReplyType\r\nfrom common.log import logger\r\nfrom config import conf\r\nfrom common import const\r\nimport time\r\nimport _thread as thread\r\nimport datetime\r\nfrom datetime import datetime\r\nfrom wsgiref.handlers import format_date_time\r\nfrom urllib.parse import urlencode\r\nimport base64\r\nimport ssl\r\nimport hashlib\r\nimport hmac\r\nimport json\r\nfrom time import mktime\r\nfrom urllib.parse import urlparse\r\nimport websocket\r\nimport queue\r\nimport threading\r\nimport random\r\n\r\n# 消息队列 map\r\nqueue_map = dict()\r\n\r\n# 响应队列 map\r\nreply_map = dict()\r\n\r\n\r\nclass XunFeiBot(Bot):\r\n def __init__(self):\r\n super().__init__()\r\n self.app_id = conf().get(\"xunfei_app_id\")\r\n self.api_key = conf().get(\"xunfei_api_key\")\r\n self.api_secret = conf().get(\"xunfei_api_secret\")\r\n # 默认使用v2.0版本: \"generalv2\"\r\n # Spark Lite请求地址(spark_url): wss://spark-api.xf-yun.com/v1.1/chat, 对应的domain参数为: \"lite\"\r\n # Spark V2.0请求地址(spark_url): wss://spark-api.xf-yun.com/v2.1/chat, 对应的domain参数为: \"generalv2\"\r\n # Spark Pro 请求地址(spark_url): wss://spark-api.xf-yun.com/v3.1/chat, 对应的domain参数为: \"generalv3\"\r\n # Spark Pro-128K请求地址(spark_url): wss://spark-api.xf-yun.com/chat/pro-128k, 对应的domain参数为: \"pro-128k\"\r\n # Spark Max 请求地址(spark_url): wss://spark-api.xf-yun.com/v3.5/chat, 对应的domain参数为: \"generalv3.5\"\r\n # Spark4.0 Ultra 请求地址(spark_url): wss://spark-api.xf-yun.com/v4.0/chat, 对应的domain参数为: \"4.0Ultra\"\r\n # 后续模型更新,对应的参数可以参考官网文档获取:https://www.xfyun.cn/doc/spark/Web.html\r\n self.domain = conf().get(\"xunfei_domain\", \"generalv3.5\")\r\n self.spark_url = conf().get(\"xunfei_spark_url\", \"wss://spark-api.xf-yun.com/v3.5/chat\")\r\n self.host = urlparse(self.spark_url).netloc\r\n self.path = urlparse(self.spark_url).path\r\n # 和wenxin使用相同的session机制\r\n self.sessions = SessionManager(ChatGPTSession, model=const.XUNFEI)\r\n\r\n def reply(self, query, context: Context = None) -> Reply:\r\n if context.type == ContextType.TEXT:\r\n logger.info(\"[XunFei] query={}\".format(query))\r\n session_id = context[\"session_id\"]\r\n request_id = self.gen_request_id(session_id)\r\n reply_map[request_id] = \"\"\r\n session = self.sessions.session_query(query, session_id)\r\n threading.Thread(target=self.create_web_socket,\r\n args=(session.messages, request_id)).start()\r\n depth = 0\r\n time.sleep(0.1)\r\n t1 = time.time()\r\n usage = {}\r\n while depth <= 300:\r\n try:\r\n data_queue = queue_map.get(request_id)\r\n if not data_queue:\r\n depth += 1\r\n time.sleep(0.1)\r\n continue\r\n data_item = data_queue.get(block=True, timeout=0.1)\r\n if data_item.is_end:\r\n # 请求结束\r\n del queue_map[request_id]\r\n if data_item.reply:\r\n reply_map[request_id] += data_item.reply\r\n usage = data_item.usage\r\n break\r\n\r\n reply_map[request_id] += data_item.reply\r\n depth += 1\r\n except Exception as e:\r\n depth += 1\r\n continue\r\n t2 = time.time()\r\n logger.info(\r\n f\"[XunFei-API] response={reply_map[request_id]}, time={t2 - t1}s, usage={usage}\"\r\n )\r\n self.sessions.session_reply(reply_map[request_id], session_id,\r\n usage.get(\"total_tokens\"))\r\n reply = Reply(ReplyType.TEXT, reply_map[request_id])\r\n del reply_map[request_id]\r\n return reply\r\n else:\r\n reply = Reply(ReplyType.ERROR,\r\n \"Bot不支持处理{}类型的消息\".format(context.type))\r\n return reply\r\n\r\n def create_web_socket(self, prompt, session_id, temperature=0.5):\r\n logger.info(f\"[XunFei] start connect, prompt={prompt}\")\r\n websocket.enableTrace(False)\r\n wsUrl = self.create_url()\r\n ws = websocket.WebSocketApp(wsUrl,\r\n on_message=on_message,\r\n on_error=on_error,\r\n on_close=on_close,\r\n on_open=on_open)\r\n data_queue = queue.Queue(1000)\r\n queue_map[session_id] = data_queue\r\n ws.appid = self.app_id\r\n ws.question = prompt\r\n ws.domain = self.domain\r\n ws.session_id = session_id\r\n ws.temperature = temperature\r\n ws.run_forever(sslopt={\"cert_reqs\": ssl.CERT_NONE})\r\n\r\n def gen_request_id(self, session_id: str):\r\n return session_id + \"_\" + str(int(time.time())) + \"\" + str(\r\n random.randint(0, 100))\r\n\r\n # 生成url\r\n def create_url(self):\r\n # 生成RFC1123格式的时间戳\r\n now = datetime.now()\r\n date = format_date_time(mktime(now.timetuple()))\r\n\r\n # 拼接字符串\r\n signature_origin = \"host: \" + self.host + \"\\n\"\r\n signature_origin += \"date: \" + date + \"\\n\"\r\n signature_origin += \"GET \" + self.path + \" HTTP/1.1\"\r\n\r\n # 进行hmac-sha256进行加密\r\n signature_sha = hmac.new(self.api_secret.encode('utf-8'),\r\n signature_origin.encode('utf-8'),\r\n digestmod=hashlib.sha256).digest()\r\n\r\n signature_sha_base64 = base64.b64encode(signature_sha).decode(\r\n encoding='utf-8')\r\n\r\n authorization_origin = f'api_key=\"{self.api_key}\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", ' \\\r\n f'signature=\"{signature_sha_base64}\"'\r\n\r\n authorization = base64.b64encode(\r\n authorization_origin.encode('utf-8')).decode(encoding='utf-8')\r\n\r\n # 将请求的鉴权参数组合为字典\r\n v = {\"authorization\": authorization, \"date\": date, \"host\": self.host}\r\n # 拼接鉴权参数,生成url\r\n url = self.spark_url + '?' + urlencode(v)\r\n # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致\r\n return url\r\n\r\n def gen_params(self, appid, domain, question):\r\n \"\"\"\r\n 通过appid和用户的提问来生成请参数\r\n \"\"\"\r\n data = {\r\n \"header\": {\r\n \"app_id\": appid,\r\n \"uid\": \"1234\"\r\n },\r\n \"parameter\": {\r\n \"chat\": {\r\n \"domain\": domain,\r\n \"random_threshold\": 0.5,\r\n \"max_tokens\": 2048,\r\n \"auditing\": \"default\"\r\n }\r\n },\r\n \"payload\": {\r\n \"message\": {\r\n \"text\": question\r\n }\r\n }\r\n }\r\n return data\r\n\r\n\r\nclass ReplyItem:\r\n def __init__(self, reply, usage=None, is_end=False):\r\n self.is_end = is_end\r\n self.reply = reply\r\n self.usage = usage\r\n\r\n\r\n# 收到websocket错误的处理\r\ndef on_error(ws, error):\r\n logger.error(f\"[XunFei] error: {str(error)}\")\r\n\r\n\r\n# 收到websocket关闭的处理\r\ndef on_close(ws, one, two):\r\n data_queue = queue_map.get(ws.session_id)\r\n data_queue.put(\"END\")\r\n\r\n\r\n# 收到websocket连接建立的处理\r\ndef on_open(ws):\r\n logger.info(f\"[XunFei] Start websocket, session_id={ws.session_id}\")\r\n thread.start_new_thread(run, (ws, ))\r\n\r\n\r\ndef run(ws, *args):\r\n data = json.dumps(\r\n gen_params(appid=ws.appid,\r\n domain=ws.domain,\r\n question=ws.question,\r\n temperature=ws.temperature))\r\n ws.send(data)\r\n\r\n\r\n# Websocket 操作\r\n# 收到websocket消息的处理\r\ndef on_message(ws, message):\r\n data = json.loads(message)\r\n code = data['header']['code']\r\n if code != 0:\r\n logger.error(f'请求错误: {code}, {data}')\r\n ws.close()\r\n else:\r\n choices = data[\"payload\"][\"choices\"]\r\n status = choices[\"status\"]\r\n content = choices[\"text\"][0][\"content\"]\r\n data_queue = queue_map.get(ws.session_id)\r\n if not data_queue:\r\n logger.error(\r\n f\"[XunFei] can't find data queue, session_id={ws.session_id}\")\r\n return\r\n reply_item = ReplyItem(content)\r\n if status == 2:\r\n usage = data[\"payload\"].get(\"usage\")\r\n reply_item = ReplyItem(content, usage)\r\n reply_item.is_end = True\r\n ws.close()\r\n data_queue.put(reply_item)\r\n\r\n\r\ndef gen_params(appid, domain, question, temperature=0.5):\r\n \"\"\"\r\n 通过appid和用户的提问来生成请参数\r\n \"\"\"\r\n data = {\r\n \"header\": {\r\n \"app_id\": appid,\r\n \"uid\": \"1234\"\r\n },\r\n \"parameter\": {\r\n \"chat\": {\r\n \"domain\": domain,\r\n \"temperature\": temperature,\r\n \"random_threshold\": 0.5,\r\n \"max_tokens\": 2048,\r\n \"auditing\": \"default\"\r\n }\r\n },\r\n \"payload\": {\r\n \"message\": {\r\n \"text\": question\r\n }\r\n }\r\n }\r\n return data\r\n"}}, "week10/chatgpt-on-wechat/README.md": {"83a4bc18b95788ad4856433cda9af6566a52b7a1": {"t": 1785809855.3995755, "n": 25295, "h": "b94116abab632246", "c": "

\"Chatgpt-on-Wechat\"

\r\n\r\n

\r\n \"Latest\r\n \"License:\r\n \"Stars\"
\r\n

\r\n\r\n**chatgpt-on-wechat**(简称CoW)项目是基于大模型的智能对话机器人,支持自由切换多种模型,可接入网页、微信公众号、企业微信应用、飞书、钉钉中使用,能处理文本、语音、图片、文件等多模态消息,支持通过插件访问操作系统和互联网等外部资源,以及基于自有知识库定制企业AI应用。\r\n\r\n# 简介\r\n\r\n> 该项目既是一个可以开箱即用的对话机器人,也是一个支持高度扩展的AI应用框架,可以通过为项目添加大模型接口、接入渠道、自定义插件来灵活实现各种定制需求。支持的功能如下:\r\n\r\n- ✅ **多端部署:** 有多种部署方式可选择且功能完备,目前已支持网页、微信公众号、企业微信应用、飞书、钉钉等部署方式\r\n- ✅ **基础对话:** 私聊及群聊的AI智能回复,支持多轮会话上下文记忆,基础模型支持OpenAI, Claude, Gemini, DeepSeek, 通义千问, Kimi, 文心一言, 讯飞星火, ChatGLM, MiniMax, GiteeAI, ModelScope, LinkAI\r\n- ✅ **语音能力:** 可识别语音消息,通过文字或语音回复,支持 openai(whisper/tts), azure, baidu, google 等多种语音模型\r\n- ✅ **图像能力:** 支持图片生成、图片识别、图生图,可选择 Dall-E-3, stable diffusion, replicate, midjourney, CogView-3, vision模型\r\n- ✅ **丰富插件:** 支持自定义插件扩展,已实现多角色切换、敏感词过滤、聊天记录总结、文档总结和对话、联网搜索、智能体等内置插件\r\n- ✅ **Agent能力:** 支持访问浏览器、终端、文件系统、搜索引擎等各类工具,并可通过多智能体协作完成复杂任务,基于 [AgentMesh](https://github.com/MinimalFuture/AgentMesh) 框架实现\r\n- ✅ **知识库:** 通过上传知识库自定义专属机器人,可作为数字分身、智能客服、企业智能体使用,基于 [LinkAI](https://link-ai.tech) 实现\r\n\r\n## 声明\r\n\r\n1. 本项目遵循 [MIT开源协议](/LICENSE),仅用于技术研究和学习,使用本项目时需遵守所在地法律法规、相关政策以及企业章程,禁止用于任何违法或侵犯他人权益的行为。任何个人、团队和企业,无论以何种方式使用该项目、对何对象提供服务,所产生的一切后果,本项目均不承担任何责任\r\n2. 境内使用该项目时,建议使用国内厂商的大模型服务,并进行必要的内容安全审核及过滤\r\n3. 本项目当前主要接入协同办公平台,推荐使用网页、公众号、企微自建应用、钉钉、飞书等接入通道,其他通道为历史产物暂不维护\r\n\r\n## 演示\r\n\r\nDEMO视频:https://cdn.link-ai.tech/doc/cow_demo.mp4\r\n\r\n## 社区\r\n\r\n添加小助手微信加入开源项目交流群:\r\n\r\n\r\n\r\n
\r\n\r\n# 企业服务\r\n\r\n\r\n\r\n> [LinkAI](https://link-ai.tech/) 是面向企业和开发者的一站式AI智能体平台,聚合多模态大模型、知识库、Agent 插件、工作流等能力,支持一键接入主流平台并进行管理,支持SaaS、私有化部署等多种模式。\r\n>\r\n> LinkAI 目前已在智能客服、私域运营、企业效率助手等场景积累了丰富的AI解决方案,在消费、健康、文教、科技制造等各行业沉淀了大模型落地应用的最佳实践,致力于帮助更多企业和开发者拥抱 AI 生产力。\r\n\r\n**产品咨询和企业服务** 可联系产品客服:\r\n\r\n\r\n\r\n
\r\n\r\n# 🏷 更新日志\r\n\r\n>**2025.05.23:** [1.7.6版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.6) 优化web网页channel、新增 [AgentMesh多智能体插件](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/agent/README.md)、百度语音合成优化、企微应用`access_token`获取优化、支持`claude-4-sonnet`和`claude-4-opus`模型\r\n\r\n>**2025.04.11:** [1.7.5版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.5) 新增支持 [wechatferry](https://github.com/zhayujie/chatgpt-on-wechat/pull/2562) 协议、新增 deepseek 模型、新增支持腾讯云语音能力、新增支持 ModelScope 和 Gitee-AI API接口\r\n\r\n>**2024.12.13:** [1.7.4版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.4) 新增 Gemini 2.0 模型、新增web channel、解决内存泄漏问题、解决 `#reloadp` 命令重载不生效问题\r\n\r\n>**2024.10.31:** [1.7.3版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.3) 程序稳定性提升、数据库功能、Claude模型优化、linkai插件优化、离线通知\r\n\r\n更多更新历史请查看: [更新日志](/docs/version/release-notes.md)\r\n\r\n
\r\n\r\n# 🚀 快速开始\r\n\r\n项目提供了一键安装、启动、管理程序的脚本,可以选择使用脚本快速运行,也可以根据详细指引一步步安装运行。\r\n\r\n- 详细文档:[快速开始](https://docs.link-ai.tech/cow/quick-start)\r\n\r\n- 一键安装脚本说明:[一键安装脚本](https://github.com/zhayujie/chatgpt-on-wechat/wiki/%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85%E5%90%AF%E5%8A%A8%E8%84%9A%E6%9C%AC)\r\n\r\n```bash\r\nbash <(curl -sS https://cdn.link-ai.tech/code/cow/install.sh)\r\n```\r\n\r\n- 项目管理脚本说明:[项目管理脚本](https://github.com/zhayujie/chatgpt-on-wechat/wiki/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E8%84%9A%E6%9C%AC)\r\n\r\n## 一、准备\r\n\r\n### 1. 模型账号\r\n\r\n项目默认使用ChatGPT模型,需前往 [OpenAI平台](https://platform.openai.com/api-keys) 创建API Key并填入项目配置文件中。同时支持其他国内外产商以及第三方自定义模型接口,详情参考:[模型说明](#模型说明)。\r\n\r\n同时支持使用 **LinkAI平台** 接口,可聚合使用 OpenAI、Claude、DeepSeek、Kimi、Qwen 等多种常用模型,并支持知识库、工作流、联网搜索、MJ绘图、文档总结等能力。修改配置即可一键启用,参考 [接入文档](https://link-ai.tech/platform/link-app/wechat)。\r\n\r\n### 2.环境安装\r\n\r\n支持 Linux、MacOS、Windows 系统,同时需安装 `Python`,Python版本需要在3.7以上,推荐使用3.9版本。\r\n\r\n> 注意:选择Docker部署则无需安装python环境和下载源码,可直接快进到下一节。\r\n\r\n**(1) 克隆项目代码:**\r\n\r\n```bash\r\ngit clone https://github.com/zhayujie/chatgpt-on-wechat\r\ncd chatgpt-on-wechat/\r\n```\r\n\r\n若遇到网络问题可使用国内仓库地址:https://gitee.com/zhayujie/chatgpt-on-wechat\r\n\r\n**(2) 安装核心依赖 (必选):**\r\n\r\n```bash\r\npip3 install -r requirements.txt\r\n```\r\n\r\n**(3) 拓展依赖 (可选,建议安装):**\r\n\r\n```bash\r\npip3 install -r requirements-optional.txt\r\n```\r\n如果某项依赖安装失败可注释掉对应的行后重试。\r\n\r\n## 二、配置\r\n\r\n配置文件的模板在根目录的`config-template.json`中,需复制该模板创建最终生效的 `config.json` 文件:\r\n\r\n```bash\r\n cp config-template.json config.json\r\n```\r\n\r\n然后在`config.json`中填入配置,以下是对默认配置的说明,可根据需要进行自定义修改(注意实际使用时请去掉注释,保证JSON格式的规范):\r\n\r\n```bash\r\n# config.json 文件内容示例\r\n{\r\n \"channel_type\": \"web\", # 接入渠道类型,默认为web,支持修改为:terminal, wechatmp, wechatmp_service, wechatcom_app, dingtalk, feishu\r\n \"model\": \"gpt-4o-mini\", # 模型名称, 支持 gpt-4o-mini, gpt-4.1, gpt-4o, deepseek-reasoner, wenxin, xunfei, glm-4, claude-3-7-sonnet-latest, moonshot等\r\n \"open_ai_api_key\": \"YOUR API KEY\", # 如果使用openAI模型则填入上面创建的 OpenAI API KEY\r\n \"open_ai_api_base\": \"https://api.openai.com/v1\", # OpenAI接口代理地址,修改此项可接入第三方模型接口\r\n \"proxy\": \"\", # 代理客户端的ip和端口,国内环境开启代理的需要填写该项,如 \"127.0.0.1:7890\"\r\n \"single_chat_prefix\": [\"bot\", \"@bot\"], # 私聊时文本需要包含该前缀才能触发机器人回复\r\n \"single_chat_reply_prefix\": \"[bot] \", # 私聊时自动回复的前缀,用于区分真人\r\n \"group_chat_prefix\": [\"@bot\"], # 群聊时包含该前缀则会触发机器人回复\r\n \"group_name_white_list\": [\"ChatGPT测试群\", \"ChatGPT测试群2\"], # 开启自动回复的群名称列表\r\n \"group_chat_in_one_session\": [\"ChatGPT测试群\"], # 支持会话上下文共享的群名称 \r\n \"image_create_prefix\": [\"画\", \"看\", \"找\"], # 开启图片回复的前缀\r\n \"conversation_max_tokens\": 1000, # 支持上下文记忆的最多字符数\r\n \"speech_recognition\": false, # 是否开启语音识别\r\n \"group_speech_recognition\": false, # 是否开启群组语音识别\r\n \"voice_reply_voice\": false, # 是否使用语音回复语音\r\n \"character_desc\": \"你是基于大语言模型的AI智能助手,旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。\", # 系统提示词\r\n # 订阅欢迎语,公众号和企业微信channel中使用,当被订阅时会自动回复以下内容\r\n \"subscribe_msg\": \"感谢您的关注!\\n这里是AI智能助手,可以自由对话。\\n支持语音对话。\\n支持图片输入。\\n支持图片输出,画字开头的消息将按要求创作图片。\\n支持tool、角色扮演和文字冒险等丰富的插件。\\n输入{trigger_prefix}#help 查看详细指令。\",\r\n \"use_linkai\": false, # 是否使用LinkAI接口,默认关闭,设置为true后可对接LinkAI平台的智能体\r\n \"linkai_api_key\": \"\", # LinkAI Api Key\r\n \"linkai_app_code\": \"\" # LinkAI 应用或工作流的code\r\n}\r\n```\r\n\r\n**详细配置说明:** \r\n\r\n
\r\n1. 单聊配置\r\n\r\n+ 个人聊天中,需要以 \"bot\"或\"@bot\" 为开头的内容触发机器人,对应配置项 `single_chat_prefix` (如果不需要以前缀触发可以填写 `\"single_chat_prefix\": [\"\"]`)\r\n+ 机器人回复的内容会以 \"[bot] \" 作为前缀, 以区分真人,对应的配置项为 `single_chat_reply_prefix` (如果不需要前缀可以填写 `\"single_chat_reply_prefix\": \"\"`)\r\n
\r\n\r\n\r\n
\r\n2. 群聊配置\r\n\r\n+ 群组聊天中,群名称需配置在 `group_name_white_list ` 中才能开启群聊自动回复。如果想对所有群聊生效,可以直接填写 `\"group_name_white_list\": [\"ALL_GROUP\"]`\r\n+ 默认只要被人 @ 就会触发机器人自动回复;另外群聊天中只要检测到以 \"@bot\" 开头的内容,同样会自动回复(方便自己触发),这对应配置项 `group_chat_prefix`\r\n+ 可选配置: `group_name_keyword_white_list`配置项支持模糊匹配群名称,`group_chat_keyword`配置项则支持模糊匹配群消息内容,用法与上述两个配置项相同。(Contributed by [evolay](https://github.com/evolay))\r\n+ `group_chat_in_one_session`:使群聊共享一个会话上下文,配置 `[\"ALL_GROUP\"]` 则作用于所有群聊\r\n
\r\n\r\n
\r\n3. 语音配置\r\n\r\n+ 添加 `\"speech_recognition\": true` 将开启语音识别,默认使用openai的whisper模型识别为文字,同时以文字回复,该参数仅支持私聊 (注意由于语音消息无法匹配前缀,一旦开启将对所有语音自动回复,支持语音触发画图);\r\n+ 添加 `\"group_speech_recognition\": true` 将开启群组语音识别,默认使用openai的whisper模型识别为文字,同时以文字回复,参数仅支持群聊 (会匹配group_chat_prefix和group_chat_keyword, 支持语音触发画图);\r\n+ 添加 `\"voice_reply_voice\": true` 将开启语音回复语音(同时作用于私聊和群聊)\r\n
\r\n\r\n
\r\n4. 其他配置\r\n\r\n+ `model`: 模型名称,目前支持 `gpt-4o-mini`, `gpt-4.1`, `gpt-4o`, `gpt-3.5-turbo`, `wenxin` , `claude` , `gemini`, `glm-4`, `xunfei`, `moonshot`等,全部模型名称参考[common/const.py](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/common/const.py)文件\r\n+ `temperature`,`frequency_penalty`,`presence_penalty`: Chat API接口参数,详情参考[OpenAI官方文档。](https://platform.openai.com/docs/api-reference/chat)\r\n+ `proxy`:由于目前 `openai` 接口国内无法访问,需配置代理客户端的地址,详情参考 [#351](https://github.com/zhayujie/chatgpt-on-wechat/issues/351)\r\n+ 对于图像生成,在满足个人或群组触发条件外,还需要额外的关键词前缀来触发,对应配置 `image_create_prefix `\r\n+ 关于OpenAI对话及图片接口的参数配置(内容自由度、回复字数限制、图片大小等),可以参考 [对话接口](https://beta.openai.com/docs/api-reference/completions) 和 [图像接口](https://beta.openai.com/docs/api-reference/completions) 文档,在[`config.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/config.py)中检查哪些参数在本项目中是可配置的。\r\n+ `conversation_max_tokens`:表示能够记忆的上下文最大字数(一问一答为一组对话,如果累积的对话字数超出限制,就会优先移除最早的一组对话)\r\n+ `rate_limit_chatgpt`,`rate_limit_dalle`:每分钟最高问答速率、画图速率,超速后排队按序处理。\r\n+ `clear_memory_commands`: 对话内指令,主动清空前文记忆,字符串数组可自定义指令别名。\r\n+ `hot_reload`: 程序退出后,暂存等于状态,默认关闭。\r\n+ `character_desc` 配置中保存着你对机器人说的一段话,他会记住这段话并作为他的设定,你可以为他定制任何人格 (关于会话上下文的更多内容参考该 [issue](https://github.com/zhayujie/chatgpt-on-wechat/issues/43))\r\n+ `subscribe_msg`:订阅消息,公众号和企业微信channel中请填写,当被订阅时会自动回复, 可使用特殊占位符。目前支持的占位符有{trigger_prefix},在程序中它会自动替换成bot的触发词。\r\n
\r\n\r\n
\r\n5. LinkAI配置\r\n\r\n+ `use_linkai`: 是否使用LinkAI接口,默认关闭,设置为true后可对接LinkAI平台的Agent,使用知识库、工作流、联网搜索、`Midjourney` 绘画等能力, 参考 [文档](https://link-ai.tech/platform/link-app/wechat)\r\n+ `linkai_api_key`: LinkAI Api Key,可在 [控制台](https://link-ai.tech/console/interface) 创建\r\n+ `linkai_app_code`: LinkAI 应用或工作流的code,选填\r\n
\r\n\r\n注:完整配置项说明可在 [`config.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/config.py) 文件中查看。\r\n\r\n## 三、运行\r\n\r\n### 1.本地运行\r\n\r\n如果是个人计算机 **本地运行**,直接在项目根目录下执行:\r\n\r\n```bash\r\npython3 app.py # windows环境下该命令通常为 python app.py\r\n```\r\n\r\n运行后默认会启动一个web服务,可以通过访问 `http://localhost:9899/chat` 在网页端对话。如果需要接入其他应用通道只需修改 `config.json` 配置文件中的 `channel_type` 参数,详情参考:[通道说明](#通道说明)。\r\n\r\n向机器人发送 `#help` 消息可以查看可用指令及插件的说明。\r\n\r\n### 2.服务器部署\r\n\r\n在服务器中可使用 `nohup` 命令在后台运行程序:\r\n\r\n```bash\r\nnohup python3 app.py & tail -f nohup.out\r\n```\r\n\r\n执行后程序运行于服务器后台,可通过 `ctrl+c` 关闭日志,不会影响后台程序的运行。使用 `ps -ef | grep app.py | grep -v grep` 命令可查看运行于后台的进程,如果想要重新启动程序可以先 `kill` 掉对应的进程。 日志关闭后如果想要再次打开只需输入 `tail -f nohup.out`。 \r\n\r\n此外,项目的 `scripts` 目录下有一键运行、关闭程序的脚本供使用。 运行后默认channel为web,通过可以通过修改配置文件进行切换。\r\n\r\n\r\n### 3.Docker部署\r\n\r\n使用docker部署无需下载源码和安装依赖,只需要获取 `docker-compose.yml` 配置文件并启动容器即可。\r\n\r\n> 前提是需要安装好 `docker` 及 `docker-compose`,安装成功后执行 `docker -v` 和 `docker-compose version` (或 `docker compose version`) 可查看到版本号。安装地址为 [docker官网](https://docs.docker.com/engine/install/) 。\r\n\r\n**(1) 下载 docker-compose.yml 文件**\r\n\r\n```bash\r\nwget https://cdn.link-ai.tech/code/cow/docker-compose.yml\r\n```\r\n\r\n下载完成后打开 `docker-compose.yml` 填写所需配置,例如 `CHANNEL_TYPE`、`OPEN_AI_API_KEY` 和等配置。\r\n\r\n**(2) 启动容器**\r\n\r\n在 `docker-compose.yml` 所在目录下执行以下命令启动容器:\r\n\r\n```bash\r\nsudo docker compose up -d # 若docker-compose为 1.X 版本,则执行 `sudo docker-compose up -d`\r\n```\r\n\r\n运行命令后,会自动取 [docker hub](https://hub.docker.com/r/zhayujie/chatgpt-on-wechat) 拉取最新release版本的镜像。当执行 `sudo docker ps` 能查看到 NAMES 为 chatgpt-on-wechat 的容器即表示运行成功。最后执行以下命令可查看容器的运行日志:\r\n\r\n```bash\r\nsudo docker logs -f chatgpt-on-wechat\r\n```\r\n\r\n**(3) 插件使用**\r\n\r\n如果需要在docker容器中修改插件配置,可通过挂载的方式完成,将 [插件配置文件](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/config.json.template)\r\n重命名为 `config.json`,放置于 `docker-compose.yml` 相同目录下,并在 `docker-compose.yml` 中的 `chatgpt-on-wechat` 部分下添加 `volumes` 映射:\r\n\r\n```\r\nvolumes:\r\n - ./config.json:/app/plugins/config.json\r\n```\r\n**注**:使用docker方式部署的详细教程可以参考:[docker部署CoW项目](https://www.wangpc.cc/ai/docker-deploy-cow/)\r\n\r\n\r\n## 模型说明\r\n\r\n以下对所有可支持的模型的配置和使用方法进行说明,模型接口实现在项目的 `bot/` 目录下。\r\n>部分模型厂商接入有官方sdk和OpenAI兼容两种方式,建议使用OpenAI兼容的方式。\r\n\r\n
\r\nOpenAI\r\n\r\n1. API Key创建:在 [OpenAI平台](https://platform.openai.com/api-keys) 创建API Key\r\n\r\n2. 填写配置\r\n\r\n```json\r\n{\r\n \"model\": \"gpt-4.1-mini\",\r\n \"open_ai_api_key\": \"YOUR_API_KEY\",\r\n \"open_ai_api_base\": \"https://api.openai.com/v1\",\r\n \"bot_type\": \"chatGPT\"\r\n}\r\n```\r\n\r\n - `model`: 与OpenAI接口的 [model参数](https://platform.openai.com/docs/models) 一致,支持包括 o系列、gpt-4系列、gpt-3.5系列等模型\r\n - `open_ai_api_base`: 如果需要接入第三方代理接口,可通过修改该参数进行接入\r\n - `bot_type`: 使用OpenAI相关模型时无需填写。当使用第三方代理接口接入Claude等非OpenAI官方模型时,该参数设为 `chatGPT`\r\n
\r\n\r\n
\r\nLinkAI\r\n\r\n1. API Key创建:在 [LinkAI平台](https://link-ai.tech/console/interface) 创建API Key \r\n\r\n2. 填写配置\r\n\r\n```json\r\n{\r\n \"use_linkai\": true,\r\n \"linkai_api_key\": \"YOUR API KEY\",\r\n \"linkai_app_code\": \"YOUR APP CODE\"\r\n}\r\n```\r\n\r\n+ `use_linkai`: 是否使用LinkAI接口,默认关闭,设置为true后可对接LinkAI平台的智能体,使用知识库、工作流、数据库、联网搜索、MCP工具等丰富的Agent能力, 参考 [文档](https://link-ai.tech/platform/link-app/wechat)\r\n+ `linkai_api_key`: LinkAI平台的API Key,可在 [控制台](https://link-ai.tech/console/interface) 中创建\r\n+ `linkai_app_code`: LinkAI智能体 (应用或工作流) 的code,选填。智能体创建可参考 [说明文档](https://docs.link-ai.tech/platform/quick-start)\r\n+ `model`: model字段填写空则直接使用智能体的模型,可在平台中灵活切换,[模型列表](https://link-ai.tech/console/models)中的全部模型均可使用\r\n
\r\n\r\n
\r\nDeepSeek\r\n\r\n1. API Key创建:在 [DeepSeek平台](https://platform.deepseek.com/api_keys) 创建API Key \r\n\r\n2. 填写配置\r\n\r\n```json\r\n{\r\n \"bot_type\": \"chatGPT\",\r\n \"model\": \"deepseek-chat\",\r\n \"open_ai_api_key\": \"sk-xxxxxxxxxxx\",\r\n \"open_ai_api_base\": \"https://api.deepseek.com/v1\"\r\n}\r\n```\r\n\r\n - `bot_type`: OpenAI兼容方式\r\n - `model`: 可填 `deepseek-chat、deepseek-reasoner`,分别对应的是 V3 和 R1 模型\r\n - `open_ai_api_key`: DeepSeek平台的 API Key\r\n - `open_ai_api_base`: DeepSeek平台 BASE URL\r\n
\r\n\r\n
\r\nAzure\r\n\r\n1. API Key创建:在 [DeepSeek平台](https://platform.deepseek.com/api_keys) 创建API Key \r\n\r\n2. 填写配置\r\n\r\n```json\r\n{\r\n \"model\": \"\",\r\n \"use_azure_chatgpt\": true,\r\n \"open_ai_api_key\": \"e7ffc5dd84f14521a53f14a40231ea78\",\r\n \"open_ai_api_base\": \"https://linkai-240917.openai.azure.com/\",\r\n \"azure_deployment_id\": \"gpt-4.1\",\r\n \"azure_api_version\": \"2025-01-01-preview\"\r\n}\r\n```\r\n\r\n - `model`: 留空即可\r\n - `use_azure_chatgpt`: 设为 true \r\n - `open_ai_api_key`: Azure平台的密钥\r\n - `open_ai_api_base`: Azure平台的 BASE URL\r\n - `azure_deployment_id`: Azure平台部署的模型名称\r\n - `azure_api_version`: api版本以及以上参数可以在部署的 [模型配置](https://oai.azure.com/resource/deployments) 界面查看\r\n
\r\n\r\n
\r\nClaude\r\n\r\n1. API Key创建:在 [Claude控制台](https://console.anthropic.com/settings/keys) 创建API Key\r\n\r\n2. 填写配置\r\n\r\n```json\r\n{\r\n \"model\": \"claude-sonnet-4-0\",\r\n \"claude_api_key\": \"YOUR_API_KEY\"\r\n}\r\n```\r\n - `model`: 参考 [官方模型ID](https://docs.anthropic.com/en/docs/about-claude/models/overview#model-aliases) ,例如`claude-opus-4-0`、`claude-3-7-sonnet-latest`等\r\n
\r\n\r\n
\r\n通义千问\r\n\r\n方式一:官方SDK接入,配置如下:\r\n\r\n```json\r\n{\r\n \"model\": \"qwen-turbo\",\r\n \"dashscope_api_key\": \"sk-qVxxxxG\"\r\n}\r\n```\r\n - `model`: 可填写`qwen-turbo、qwen-plus、qwen-max`\r\n - `dashscope_api_key`: 通义千问的 API-KEY,参考 [官方文档](https://bailian.console.aliyun.com/?tab=api#/api) ,在 [控制台](https://bailian.console.aliyun.com/?tab=model#/api-key) 创建\r\n \r\n方式二:OpenAI兼容方式接入,配置如下:\r\n```json\r\n{\r\n \"bot_type\": \"chatGPT\",\r\n \"model\": \"qwen-turbo\",\r\n \"open_ai_api_base\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\r\n \"open_ai_api_key\": \"sk-qVxxxxG\"\r\n}\r\n```\r\n- `bot_type`: OpenAI兼容方式\r\n- `model`: 支持官方所有模型,参考[模型列表](https://help.aliyun.com/zh/model-studio/models?spm=a2c4g.11186623.0.0.78d84823Kth5on#9f8890ce29g5u)\r\n- `open_ai_api_base`: 通义千问API的 BASE URL\r\n- `open_ai_api_key`: 通义千问的 API-KEY,参考 [官方文档](https://bailian.console.aliyun.com/?tab=api#/api) ,在 [控制台](https://bailian.console.aliyun.com/?tab=model#/api-key) 创建\r\n
\r\n\r\n
\r\nGemini\r\n\r\nAPI Key创建:在 [控制台](https://aistudio.google.com/app/apikey?hl=zh-cn) 创建API Key ,配置如下\r\n```json\r\n{\r\n \"model\": \"gemini-2.5-pro\",\r\n \"gemini_api_key\": \"\"\r\n}\r\n```\r\n - `model`: 参考[官方文档-模型列表](https://ai.google.dev/gemini-api/docs/models?hl=zh-cn)\r\n
\r\n\r\n
\r\nMoonshot\r\n\r\n方式一:官方接入,配置如下:\r\n\r\n```json\r\n{\r\n \"model\": \"moonshot-v1-8k\",\r\n \"moonshot_api_key\": \"moonshot-v1-8k\"\r\n}\r\n```\r\n - `model`: 可填写`moonshot-v1-8k、 moonshot-v1-32k、 moonshot-v1-128k`\r\n - `moonshot_api_key`: Moonshot的API-KEY,在 [控制台](https://platform.moonshot.cn/console/api-keys) 创建\r\n \r\n方式二:OpenAI兼容方式接入,配置如下:\r\n```json\r\n{\r\n \"bot_type\": \"chatGPT\",\r\n \"model\": \"moonshot-v1-8k\",\r\n \"open_ai_api_base\": \"https://api.moonshot.cn/v1\",\r\n \"open_ai_api_key\": \"\"\r\n}\r\n```\r\n- `bot_type`: OpenAI兼容方式\r\n- `model`: 可填写`moonshot-v1-8k、 moonshot-v1-32k、 moonshot-v1-128k`\r\n- `open_ai_api_base`: Moonshot的 BASE URL\r\n- `open_ai_api_key`: Moonshot的 API-KEY,在 [控制台](https://platform.moonshot.cn/console/api-keys) 创建\r\n
\r\n\r\n
\r\n百度文心\r\n方式一:官方SDK接入,配置如下:\r\n\r\n```json\r\n{\r\n \"model\": \"wenxin\", \r\n \"baidu_wenxin_api_key\": \"IajztZ0bDxgnP9bEykU7lBer\",\r\n \"baidu_wenxin_secret_key\": \"EDPZn6L24uAS9d8RWFfotK47dPvkjD6G\"\r\n}\r\n```\r\n - `model`: 可填 `wenxin`和`wenxin-4`,对应模型为 文心-3.5 和 文心-4.0\r\n - `baidu_wenxin_api_key`:参考 [千帆平台-access_token鉴权](https://cloud.baidu.com/doc/WENXINWORKSHOP/s/dlv4pct3s) 文档获取 API Key\r\n - `baidu_wenxin_secret_key`:参考 [千帆平台-access_token鉴权](https://cloud.baidu.com/doc/WENXINWORKSHOP/s/dlv4pct3s) 文档获取 Secret Key\r\n\r\n方式二:OpenAI兼容方式接入,配置如下:\r\n```json\r\n{\r\n \"bot_type\": \"chatGPT\",\r\n \"model\": \"qwen-turbo\",\r\n \"open_ai_api_base\": \"https://qianfan.baidubce.com/v2\",\r\n \"open_ai_api_key\": \"bce-v3/ALTxxxxxxd2b\"\r\n}\r\n```\r\n- `bot_type`: OpenAI兼容方式\r\n- `model`: 支持官方所有模型,参考[模型列表](https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Wm9cvy6rl)\r\n- `open_ai_api_base`: 百度文心API的 BASE URL\r\n- `open_ai_api_key`: 百度文心的 API-KEY,参考 [官方文档](https://cloud.baidu.com/doc/qianfan-api/s/ym9chdsy5) ,在 [控制台](https://console.bce.baidu.com/iam/#/iam/apikey/list) 创建API Key\r\n\r\n
\r\n\r\n
\r\n讯飞星火\r\n\r\n方式一:官方接入,配置如下:\r\n参考 [官方文档-快速指引](https://www.xfyun.cn/doc/platform/quickguide.html#%E7%AC%AC%E4%BA%8C%E6%AD%A5-%E5%88%9B%E5%BB%BA%E6%82%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%BA%94%E7%94%A8-%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8%E6%9C%8D%E5%8A%A1) 获取 `APPID、 APISecret、 APIKey` 三个参数\r\n\r\n```json\r\n{\r\n \"model\": \"xunfei\",\r\n \"xunfei_app_id\": \"\",\r\n \"xunfei_api_key\": \"\",\r\n \"xunfei_api_secret\": \"\",\r\n \"xunfei_domain\": \"4.0Ultra\",\r\n \"xunfei_spark_url\": \"wss://spark-api.xf-yun.com/v4.0/chat\"\r\n}\r\n```\r\n - `model`: 填 `xunfei`\r\n - `xunfei_domain`: 可填写 `4.0Ultra、 generalv3.5、 max-32k、 generalv3、 pro-128k、 lite`\r\n - `xunfei_spark_url`: 填写参考 [官方文档-请求地址](https://www.xfyun.cn/doc/spark/Web.html#_1-1-%E8%AF%B7%E6%B1%82%E5%9C%B0%E5%9D%80) 的说明\r\n \r\n方式二:OpenAI兼容方式接入,配置如下:\r\n```json\r\n{\r\n \"bot_type\": \"chatGPT\",\r\n \"model\": \"4.0Ultra\",\r\n \"open_ai_api_base\": \"https://spark-api-open.xf-yun.com/v1\",\r\n \"open_ai_api_key\": \"\"\r\n}\r\n```\r\n- `bot_type`: OpenAI兼容方式\r\n- `model`: 可填写 `4.0Ultra、 generalv3.5、 max-32k、 generalv3、 pro-128k、 lite`\r\n- `open_ai_api_base`: 讯飞星火平台的 BASE URL\r\n- `open_ai_api_key`: 讯飞星火平台的[APIPassword](https://console.xfyun.cn/services/bm3) ,因模型而已\r\n
\r\n\r\n
\r\n智谱AI\r\n\r\n方式一:官方接入,配置如下:\r\n\r\n```json\r\n{\r\n \"model\": \"glm-4-plus\",\r\n \"zhipu_ai_api_key\": \"\"\r\n}\r\n```\r\n - `model`: 可填 `glm-4-plus、glm-4-air-250414、glm-4-airx、glm-4-long 、glm-4-flashx 、glm-4-flash-250414`, 参考 [glm-4系列模型编码](https://bigmodel.cn/dev/api/normal-model/glm-4)\r\n - `zhipu_ai_api_key`: 智谱AI平台的 API KEY,在 [控制台](https://www.bigmodel.cn/usercenter/proj-mgmt/apikeys) 创建\r\n \r\n方式二:OpenAI兼容方式接入,配置如下:\r\n```json\r\n{\r\n \"bot_type\": \"chatGPT\",\r\n \"model\": \"glm-4-plus\",\r\n \"open_ai_api_base\": \"https://open.bigmodel.cn/api/paas/v4\",\r\n \"open_ai_api_key\": \"\"\r\n}\r\n```\r\n- `bot_type`: OpenAI兼容方式\r\n- `model`: 可填 `glm-4-plus、glm-4-air-250414、glm-4-airx、glm-4-long 、glm-4-flashx 、glm-4-flash-250414`, 参考 [glm-4系列模型编码](https://bigmodel.cn/dev/api/normal-model/glm-4) \r\n- `open_ai_api_base`: 智谱AI平台的 BASE URL\r\n- `open_ai_api_key`: 智谱AI平台的 API KEY,在 [控制台](https://www.bigmodel.cn/usercenter/proj-mgmt/apikeys) 创建\r\n
\r\n\r\n
\r\nMiniMax\r\n\r\n方式一:官方接入,配置如下:\r\n\r\n```json\r\n{\r\n \"model\": \"abab6.5-chat\",\r\n \"Minimax_api_key\": \"\",\r\n \"Minimax_group_id\": \"\"\r\n}\r\n```\r\n - `model`: 可填写`abab6.5-chat`\r\n - `Minimax_api_key`:MiniMax平台的API-KEY,在 [控制台](https://platform.minimaxi.com/user-center/basic-information/interface-key) 创建\r\n - `Minimax_group_id`: 在 [账户信息](https://platform.minimaxi.com/user-center/basic-information) 右上角获取\r\n \r\n方式二:OpenAI兼容方式接入,配置如下:\r\n```json\r\n{\r\n \"bot_type\": \"chatGPT\",\r\n \"model\": \"MiniMax-M1\",\r\n \"open_ai_api_base\": \"https://api.minimaxi.com/v1\",\r\n \"open_ai_api_key\": \"\"\r\n}\r\n```\r\n- `bot_type`: OpenAI兼容方式\r\n- `model`: 可填`MiniMax-M1、MiniMax-Text-01`,参考[API文档](https://platform.minimaxi.com/document/%E5%AF%B9%E8%AF%9D?key=66701d281d57f38758d581d0#QklxsNSbaf6kM4j6wjO5eEek)\r\n- `open_ai_api_base`: MiniMax平台API的 BASE URL\r\n- `open_ai_api_key`: MiniMax平台的API-KEY,在 [控制台](https://platform.minimaxi.com/user-center/basic-information/interface-key) 创建\r\n
\r\n\r\n
\r\nModelScope\r\n\r\n```json\r\n{\r\n \"bot_type\": \"modelscope\",\r\n \"model\": \"Qwen/QwQ-32B\",\r\n \"modelscope_api_key\": \"your_api_key\",\r\n \"modelscope_base_url\": \"https://api-inference.modelscope.cn/v1/chat/completions\",\r\n \"text_to_image\": \"MusePublic/489_ckpt_FLUX_1\"\r\n}\r\n```\r\n\r\n- `bot_type`: modelscope接口格式\r\n- `model`: 参考[模型列表](https://www.modelscope.cn/models?filter=inference_type&page=1)\r\n- `modelscope_api_key`: 参考 [官方文档-访问令牌](https://modelscope.cn/docs/accounts/token) ,在 [控制台](https://modelscope.cn/my/myaccesstoken) \r\n- `modelscope_base_url`: modelscope平台的 BASE URL\r\n- `text_to_image`: 图像生成模型,参考[模型列表](https://www.modelscope.cn/models?filter=inference_type&page=1)\r\n
\r\n\r\n\r\n## 通道说明\r\n\r\n以下对可接入通道的配置方式进行说明,应用通道代码在项目的 `channel/` 目录下。\r\n\r\n
\r\nWeb\r\n\r\n项目启动后默认运行web通道,配置如下:\r\n\r\n```json\r\n{\r\n \"channel_type\": \"web\",\r\n \"web_port\": 9899\r\n}\r\n```\r\n- `web_port`: 默认为 9899,可按需更改,需要服务器防火墙和安全组放行该端口\r\n- 如本地运行,启动后请访问 `http://localhost:port/chat` ;如服务器运行,请访问 `http://ip:port/chat` \r\n> 注:请将上述 url 中的 ip 或者 port 替换为实际的值\r\n
\r\n\r\n
\r\nTerminal\r\n\r\n修改 `config.json` 中的 `channel_type` 字段:\r\n\r\n```json\r\n{\r\n \"channel_type\": \"terminal\"\r\n}\r\n```\r\n\r\n运行后可在终端与机器人进行对话。\r\n\r\n
\r\n\r\n
\r\n微信公众号\r\n\r\n本项目支持订阅号和服务号两种公众号,通过服务号(`wechatmp_service`)体验更佳。将下列配置加入 `config.json`:\r\n\r\n```json\r\n{\r\n \"channel_type\": \"wechatmp\",\r\n \"wechatmp_token\": \"TOKEN\",\r\n \"wechatmp_port\": 80,\r\n \"wechatmp_app_id\": \"APPID\",\r\n \"wechatmp_app_secret\": \"APPSECRET\",\r\n \"wechatmp_aes_key\": \"\"\r\n}\r\n```\r\n- `channel_type`: 个人订阅号为`wechatmp`,企业服务号为`wechatmp_service`\r\n\r\n详细步骤和参数说明参考 [微信公众号接入](https://docs.link-ai.tech/cow/multi-platform/wechat-mp)\r\n\r\n
\r\n\r\n
\r\n企业微信应用\r\n\r\n企业微信自建应用接入需在后台创建应用并启用消息回调,配置示例:\r\n\r\n```json\r\n{\r\n \"channel_type\": \"wechatcom_app\",\r\n \"wechatcom_corp_id\": \"CORPID\",\r\n \"wechatcomapp_token\": \"TOKEN\",\r\n \"wechatcomapp_port\": 9898,\r\n \"wechatcomapp_secret\": \"SECRET\",\r\n \"wechatcomapp_agent_id\": \"AGENTID\",\r\n \"wechatcomapp_aes_key\": \"AESKEY\"\r\n}\r\n```\r\n详细步骤和参数说明参考 [企微自建应用接入](https://docs.link-ai.tech/cow/multi-platform/wechat-com)\r\n\r\n
\r\n\r\n
\r\n钉钉\r\n\r\n钉钉需要在开放平台创建智能机器人应用,将以下配置填入 `config.json`:\r\n\r\n```json\r\n{\r\n \"channel_type\": \"dingtalk\",\r\n \"dingtalk_client_id\": \"CLIENT_ID\",\r\n \"dingtalk_client_secret\": \"CLIENT_SECRET\"\r\n}\r\n```\r\n详细步骤和参数说明参考 [钉钉接入](https://docs.link-ai.tech/cow/multi-platform/dingtalk)\r\n
\r\n\r\n
\r\n飞书\r\n\r\n通过自建应用接入AI相关能力到飞书应用中,默认已是飞书的企业用户,且具有企业管理权限,将以下配置填入 `config.json`::\r\n\r\n```json\r\n{\r\n \"channel_type\": \"feishu\",\r\n \"feishu_app_id\": \"APP_ID\",\r\n \"feishu_app_secret\": \"APP_SECRET\",\r\n \"feishu_token\": \"VERIFICATION_TOKEN\",\r\n \"feishu_port\": 80\r\n}\r\n```\r\n详细步骤和参数说明参考 [飞书接入](https://docs.link-ai.tech/cow/multi-platform/feishu)\r\n
\r\n\r\n
\r\n\r\n# 🔗 相关项目\r\n\r\n- [bot-on-anything](https://github.com/zhayujie/bot-on-anything):轻量和高可扩展的大模型应用框架,支持接入Slack, Telegram, Discord, Gmail等海外平台,可作为本项目的补充使用。\r\n- [AgentMesh](https://github.com/MinimalFuture/AgentMesh):开源的多智能体(Multi-Agent)框架,可以通过多智能体团队的协同来解决复杂问题。本项目基于该框架实现了[Agent插件](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/agent/README.md),可访问终端、浏览器、文件系统、搜索引擎 等各类工具,并实现了多智能体协同。\r\n\r\n\r\n\r\n# 🔎 常见问题\r\n\r\nFAQs: \r\n\r\n或直接在线咨询 [项目小助手](https://link-ai.tech/app/Kv2fXJcH) (知识库持续完善中,回复供参考)\r\n\r\n# 🛠️ 开发\r\n\r\n欢迎接入更多应用通道,参考 [Terminal代码](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/channel/terminal/terminal_channel.py) 新增自定义通道,实现接收和发送消息逻辑即可完成接入。 同时欢迎贡献新的插件,参考 [插件开发文档](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins)。\r\n\r\n# ✉ 联系\r\n\r\n欢迎提交PR、Issues进行反馈,以及通过 🌟Star 支持并关注项目更新。项目运行遇到问题可以查看 [常见问题列表](https://github.com/zhayujie/chatgpt-on-wechat/wiki/FAQs) ,以及前往 [Issues](https://github.com/zhayujie/chatgpt-on-wechat/issues) 中搜索。个人开发者可加入开源交流群参与更多讨论,企业用户可联系[产品客服](https://cdn.link-ai.tech/portal/linkai-customer-service.png)咨询。\r\n\r\n# 🌟 贡献者\r\n\r\n![cow contributors](https://contrib.rocks/image?repo=zhayujie/chatgpt-on-wechat&max=1000)\r\n"}}, "week10/chatgpt-on-wechat/config.py": {"83a4bc18b95788ad4856433cda9af6566a52b7a1": {"t": 1785809855.9481206, "n": 14213, "h": "0743efa949bcf799", "c": "# encoding:utf-8\r\n\r\nimport json\r\nimport logging\r\nimport os\r\nimport pickle\r\nimport copy\r\n\r\nfrom common.log import logger\r\n\r\n# 将所有可用的配置项写在字典里, 请使用小写字母\r\n# 此处的配置值无实际意义,程序不会读取此处的配置,仅用于提示格式,请将配置加入到config.json中\r\navailable_setting = {\r\n # openai api配置\r\n \"open_ai_api_key\": \"\", # openai api key\r\n # openai apibase,当use_azure_chatgpt为true时,需要设置对应的api base\r\n \"open_ai_api_base\": \"\",\r\n \"proxy\": \"\", # openai使用的代理\r\n # chatgpt模型, 当use_azure_chatgpt为true时,其名称为Azure上model deployment名称\r\n \"model\": \"\", # 可选择: gpt-4o, gpt-4o-mini, gpt-4-turbo, claude-3-sonnet, wenxin, moonshot, qwen-turbo, xunfei, glm-4, minimax, gemini等模型,全部可选模型详见common/const.py文件\r\n \"bot_type\": \"\", # 可选配置,使用兼容openai格式的三方服务时候,需填\"chatGPT\"。bot具体名称详见common/const.py文件列出的bot_type,如不填根据model名称判断,\r\n \"use_azure_chatgpt\": False, # 是否使用azure的chatgpt\r\n \"azure_deployment_id\": \"\", # azure 模型部署名称\r\n \"azure_api_version\": \"\", # azure api版本\r\n # Bot触发配置\r\n \"single_chat_prefix\": [\"bot\", \"@bot\"], # 私聊时文本需要包含该前缀才能触发机器人回复\r\n \"single_chat_reply_prefix\": \"[bot] \", # 私聊时自动回复的前缀,用于区分真人\r\n \"single_chat_reply_suffix\": \"\", # 私聊时自动回复的后缀,\\n 可以换行\r\n \"group_chat_prefix\": [\"@bot\"], # 群聊时包含该前缀则会触发机器人回复\r\n \"no_need_at\": False, # 群聊回复时是否不需要艾特\r\n \"group_chat_reply_prefix\": \"\", # 群聊时自动回复的前缀\r\n \"group_chat_reply_suffix\": \"\", # 群聊时自动回复的后缀,\\n 可以换行\r\n \"group_chat_keyword\": [], # 群聊时包含该关键词则会触发机器人回复\r\n \"group_at_off\": False, # 是否关闭群聊时@bot的触发\r\n \"group_name_white_list\": [\"ChatGPT测试群\", \"ChatGPT测试群2\"], # 开启自动回复的群名称列表\r\n \"group_name_keyword_white_list\": [], # 开启自动回复的群名称关键词列表\r\n \"group_chat_in_one_session\": [\"ChatGPT测试群\"], # 支持会话上下文共享的群名称\r\n \"nick_name_black_list\": [], # 用户昵称黑名单\r\n \"group_welcome_msg\": \"\", # 配置新人进群固定欢迎语,不配置则使用随机风格欢迎\r\n \"trigger_by_self\": False, # 是否允许机器人触发\r\n \"text_to_image\": \"dall-e-2\", # 图片生成模型,可选 dall-e-2, dall-e-3\r\n # Azure OpenAI dall-e-3 配置\r\n \"dalle3_image_style\": \"vivid\", # 图片生成dalle3的风格,可选有 vivid, natural\r\n \"dalle3_image_quality\": \"hd\", # 图片生成dalle3的质量,可选有 standard, hd\r\n # Azure OpenAI DALL-E API 配置, 当use_azure_chatgpt为true时,用于将文字回复的资源和Dall-E的资源分开.\r\n \"azure_openai_dalle_api_base\": \"\", # [可选] azure openai 用于回复图片的资源 endpoint,默认使用 open_ai_api_base\r\n \"azure_openai_dalle_api_key\": \"\", # [可选] azure openai 用于回复图片的资源 key,默认使用 open_ai_api_key\r\n \"azure_openai_dalle_deployment_id\":\"\", # [可选] azure openai 用于回复图片的资源 deployment id,默认使用 text_to_image\r\n \"image_proxy\": True, # 是否需要图片代理,国内访问LinkAI时需要\r\n \"image_create_prefix\": [\"画\", \"看\", \"找\"], # 开启图片回复的前缀\r\n \"concurrency_in_session\": 1, # 同一会话最多有多少条消息在处理中,大于1可能乱序\r\n \"image_create_size\": \"256x256\", # 图片大小,可选有 256x256, 512x512, 1024x1024 (dall-e-3默认为1024x1024)\r\n \"group_chat_exit_group\": False,\r\n # chatgpt会话参数\r\n \"expires_in_seconds\": 3600, # 无操作会话的过期时间\r\n # 人格描述\r\n \"character_desc\": \"你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。\",\r\n \"conversation_max_tokens\": 1000, # 支持上下文记忆的最多字符数\r\n # chatgpt限流配置\r\n \"rate_limit_chatgpt\": 20, # chatgpt的调用频率限制\r\n \"rate_limit_dalle\": 50, # openai dalle的调用频率限制\r\n # chatgpt api参数 参考https://platform.openai.com/docs/api-reference/chat/create\r\n \"temperature\": 0.9,\r\n \"top_p\": 1,\r\n \"frequency_penalty\": 0,\r\n \"presence_penalty\": 0,\r\n \"request_timeout\": 180, # chatgpt请求超时时间,openai接口默认设置为600,对于难问题一般需要较长时间\r\n \"timeout\": 120, # chatgpt重试超时时间,在这个时间内,将会自动重试\r\n # Baidu 文心一言参数\r\n \"baidu_wenxin_model\": \"eb-instant\", # 默认使用ERNIE-Bot-turbo模型\r\n \"baidu_wenxin_api_key\": \"\", # Baidu api key\r\n \"baidu_wenxin_secret_key\": \"\", # Baidu secret key\r\n \"baidu_wenxin_prompt_enabled\": False, # Enable prompt if you are using ernie character model\r\n # 讯飞星火API\r\n \"xunfei_app_id\": \"\", # 讯飞应用ID\r\n \"xunfei_api_key\": \"\", # 讯飞 API key\r\n \"xunfei_api_secret\": \"\", # 讯飞 API secret\r\n \"xunfei_domain\": \"\", # 讯飞模型对应的domain参数,Spark4.0 Ultra为 4.0Ultra,其他模型详见: https://www.xfyun.cn/doc/spark/Web.html\r\n \"xunfei_spark_url\": \"\", # 讯飞模型对应的请求地址,Spark4.0 Ultra为 wss://spark-api.xf-yun.com/v4.0/chat,其他模型参考详见: https://www.xfyun.cn/doc/spark/Web.html\r\n # claude 配置\r\n \"claude_api_cookie\": \"\",\r\n \"claude_uuid\": \"\",\r\n # claude api key\r\n \"claude_api_key\": \"\",\r\n # 通义千问API, 获取方式查看文档 https://help.aliyun.com/document_detail/2587494.html\r\n \"qwen_access_key_id\": \"\",\r\n \"qwen_access_key_secret\": \"\",\r\n \"qwen_agent_key\": \"\",\r\n \"qwen_app_id\": \"\",\r\n \"qwen_node_id\": \"\", # 流程编排模型用到的id,如果没有用到qwen_node_id,请务必保持为空字符串\r\n # 阿里灵积(通义新版sdk)模型api key\r\n \"dashscope_api_key\": \"\",\r\n # Google Gemini Api Key\r\n \"gemini_api_key\": \"\",\r\n # wework的通用配置\r\n \"wework_smart\": True, # 配置wework是否使用已登录的企业微信,False为多开\r\n # 语音设置\r\n \"speech_recognition\": True, # 是否开启语音识别\r\n \"group_speech_recognition\": False, # 是否开启群组语音识别\r\n \"voice_reply_voice\": False, # 是否使用语音回复语音,需要设置对应语音合成引擎的api key\r\n \"always_reply_voice\": False, # 是否一直使用语音回复\r\n \"voice_to_text\": \"openai\", # 语音识别引擎,支持openai,baidu,google,azure,xunfei,ali\r\n \"text_to_voice\": \"openai\", # 语音合成引擎,支持openai,baidu,google,azure,xunfei,ali,pytts(offline),elevenlabs,edge(online)\r\n \"text_to_voice_model\": \"tts-1\",\r\n \"tts_voice_id\": \"alloy\",\r\n # baidu 语音api配置, 使用百度语音识别和语音合成时需要\r\n \"baidu_app_id\": \"\",\r\n \"baidu_api_key\": \"\",\r\n \"baidu_secret_key\": \"\",\r\n # 1536普通话(支持简单的英文识别) 1737英语 1637粤语 1837四川话 1936普通话远场\r\n \"baidu_dev_pid\": 1536,\r\n # azure 语音api配置, 使用azure语音识别和语音合成时需要\r\n \"azure_voice_api_key\": \"\",\r\n \"azure_voice_region\": \"japaneast\",\r\n # elevenlabs 语音api配置\r\n \"xi_api_key\": \"\", # 获取ap的方法可以参考https://docs.elevenlabs.io/api-reference/quick-start/authentication\r\n \"xi_voice_id\": \"\", # ElevenLabs提供了9种英式、美式等英语发音id,分别是“Adam/Antoni/Arnold/Bella/Domi/Elli/Josh/Rachel/Sam”\r\n # 服务时间限制,目前支持itchat\r\n \"chat_time_module\": False, # 是否开启服务时间限制\r\n \"chat_start_time\": \"00:00\", # 服务开始时间\r\n \"chat_stop_time\": \"24:00\", # 服务结束时间\r\n # 翻译api\r\n \"translate\": \"baidu\", # 翻译api,支持baidu\r\n # baidu翻译api的配置\r\n \"baidu_translate_app_id\": \"\", # 百度翻译api的appid\r\n \"baidu_translate_app_key\": \"\", # 百度翻译api的秘钥\r\n # itchat的配置\r\n \"hot_reload\": False, # 是否开启热重载\r\n # wechaty的配置\r\n \"wechaty_puppet_service_token\": \"\", # wechaty的token\r\n # wechatmp的配置\r\n \"wechatmp_token\": \"\", # 微信公众平台的Token\r\n \"wechatmp_port\": 8080, # 微信公众平台的端口,需要端口转发到80或443\r\n \"wechatmp_app_id\": \"\", # 微信公众平台的appID\r\n \"wechatmp_app_secret\": \"\", # 微信公众平台的appsecret\r\n \"wechatmp_aes_key\": \"\", # 微信公众平台的EncodingAESKey,加密模式需要\r\n # wechatcom的通用配置\r\n \"wechatcom_corp_id\": \"\", # 企业微信公司的corpID\r\n # wechatcomapp的配置\r\n \"wechatcomapp_token\": \"\", # 企业微信app的token\r\n \"wechatcomapp_port\": 9898, # 企业微信app的服务端口,不需要端口转发\r\n \"wechatcomapp_secret\": \"\", # 企业微信app的secret\r\n \"wechatcomapp_agent_id\": \"\", # 企业微信app的agent_id\r\n \"wechatcomapp_aes_key\": \"\", # 企业微信app的aes_key\r\n # 飞书配置\r\n \"feishu_port\": 80, # 飞书bot监听端口\r\n \"feishu_app_id\": \"cli_a9aa0118f3385bc2\", # 飞书机器人应用APP Id\r\n \"feishu_app_secret\": \"MfTeObtxGiw5uchMseJRQcPK2GER7MbB\", # 飞书机器人APP secret\r\n \"feishu_token\": \"oPZvPAUHkgI0nxGr1kKg6gXkY7YvLupk\", # 飞书 verification token\r\n \"feishu_bot_name\": \"demobot\", # 飞书机器人的名字\r\n # 钉钉配置\r\n \"dingtalk_client_id\": \"\", # 钉钉机器人Client ID \r\n \"dingtalk_client_secret\": \"\", # 钉钉机器人Client Secret\r\n \"dingtalk_card_enabled\": False,\r\n \r\n # chatgpt指令自定义触发词\r\n \"clear_memory_commands\": [\"#清除记忆\"], # 重置会话指令,必须以#开头\r\n # channel配置\r\n \"channel_type\": \"\", # 通道类型,支持:{wx,wxy,terminal,wechatmp,wechatmp_service,wechatcom_app,dingtalk}\r\n \"subscribe_msg\": \"\", # 订阅消息, 支持: wechatmp, wechatmp_service, wechatcom_app\r\n \"debug\": False, # 是否开启debug模式,开启后会打印更多日志\r\n \"appdata_dir\": \"\", # 数据目录\r\n # 插件配置\r\n \"plugin_trigger_prefix\": \"$\", # 规范插件提供聊天相关指令的前缀,建议不要和管理员指令前缀\"#\"冲突\r\n # 是否使用全局插件配置\r\n \"use_global_plugin_config\": False,\r\n \"max_media_send_count\": 3, # 单次最大发送媒体资源的个数\r\n \"media_send_interval\": 1, # 发送图片的事件间隔,单位秒\r\n # 智谱AI 平台配置\r\n \"zhipu_ai_api_key\": \"\",\r\n \"zhipu_ai_api_base\": \"https://open.bigmodel.cn/api/paas/v4\",\r\n \"moonshot_api_key\": \"\",\r\n \"moonshot_base_url\": \"https://api.moonshot.cn/v1/chat/completions\",\r\n #魔搭社区 平台配置\r\n \"modelscope_api_key\": \"\",\r\n \"modelscope_base_url\": \"https://api-inference.modelscope.cn/v1/chat/completions\",\r\n # LinkAI平台配置\r\n \"use_linkai\": False,\r\n \"linkai_api_key\": \"\",\r\n \"linkai_app_code\": \"\",\r\n \"linkai_api_base\": \"https://api.link-ai.tech\", # linkAI服务地址\r\n \"Minimax_api_key\": \"\",\r\n \"Minimax_group_id\": \"\",\r\n \"Minimax_base_url\": \"\",\r\n \"web_port\": 9899,\r\n}\r\n\r\n\r\nclass Config(dict):\r\n def __init__(self, d=None):\r\n super().__init__()\r\n if d is None:\r\n d = {}\r\n for k, v in d.items():\r\n self[k] = v\r\n # user_datas: 用户数据,key为用户名,value为用户数据,也是dict\r\n self.user_datas = {}\r\n\r\n def __getitem__(self, key):\r\n if key not in available_setting:\r\n raise Exception(\"key {} not in available_setting\".format(key))\r\n return super().__getitem__(key)\r\n\r\n def __setitem__(self, key, value):\r\n if key not in available_setting:\r\n raise Exception(\"key {} not in available_setting\".format(key))\r\n return super().__setitem__(key, value)\r\n\r\n def get(self, key, default=None):\r\n try:\r\n return self[key]\r\n except KeyError as e:\r\n return default\r\n except Exception as e:\r\n raise e\r\n\r\n # Make sure to return a dictionary to ensure atomic\r\n def get_user_data(self, user) -> dict:\r\n if self.user_datas.get(user) is None:\r\n self.user_datas[user] = {}\r\n return self.user_datas[user]\r\n\r\n def load_user_datas(self):\r\n try:\r\n with open(os.path.join(get_appdata_dir(), \"user_datas.pkl\"), \"rb\") as f:\r\n self.user_datas = pickle.load(f)\r\n logger.info(\"[Config] User datas loaded.\")\r\n except FileNotFoundError as e:\r\n logger.info(\"[Config] User datas file not found, ignore.\")\r\n except Exception as e:\r\n logger.info(\"[Config] User datas error: {}\".format(e))\r\n self.user_datas = {}\r\n\r\n def save_user_datas(self):\r\n try:\r\n with open(os.path.join(get_appdata_dir(), \"user_datas.pkl\"), \"wb\") as f:\r\n pickle.dump(self.user_datas, f)\r\n logger.info(\"[Config] User datas saved.\")\r\n except Exception as e:\r\n logger.info(\"[Config] User datas error: {}\".format(e))\r\n\r\n\r\nconfig = Config()\r\n\r\n\r\ndef drag_sensitive(config):\r\n try:\r\n if isinstance(config, str):\r\n conf_dict: dict = json.loads(config)\r\n conf_dict_copy = copy.deepcopy(conf_dict)\r\n for key in conf_dict_copy:\r\n if \"key\" in key or \"secret\" in key:\r\n if isinstance(conf_dict_copy[key], str):\r\n conf_dict_copy[key] = conf_dict_copy[key][0:3] + \"*\" * 5 + conf_dict_copy[key][-3:]\r\n return json.dumps(conf_dict_copy, indent=4)\r\n\r\n elif isinstance(config, dict):\r\n config_copy = copy.deepcopy(config)\r\n for key in config:\r\n if \"key\" in key or \"secret\" in key:\r\n if isinstance(config_copy[key], str):\r\n config_copy[key] = config_copy[key][0:3] + \"*\" * 5 + config_copy[key][-3:]\r\n return config_copy\r\n except Exception as e:\r\n logger.exception(e)\r\n return config\r\n return config\r\n\r\n\r\ndef load_config():\r\n global config\r\n config_path = \"./config.json\"\r\n if not os.path.exists(config_path):\r\n logger.info(\"配置文件不存在,将使用config-template.json模板\")\r\n config_path = \"./config-template.json\"\r\n\r\n config_str = read_file(config_path)\r\n logger.debug(\"[INIT] config str: {}\".format(drag_sensitive(config_str)))\r\n\r\n # 将json字符串反序列化为dict类型\r\n config = Config(json.loads(config_str))\r\n\r\n # override config with environment variables.\r\n # Some online deployment platforms (e.g. Railway) deploy project from github directly. So you shouldn't put your secrets like api key in a config file, instead use environment variables to override the default config.\r\n for name, value in os.environ.items():\r\n name = name.lower()\r\n if name in available_setting:\r\n logger.info(\"[INIT] override config by environ args: {}={}\".format(name, value))\r\n try:\r\n config[name] = eval(value)\r\n except:\r\n if value == \"false\":\r\n config[name] = False\r\n elif value == \"true\":\r\n config[name] = True\r\n else:\r\n config[name] = value\r\n\r\n if config.get(\"debug\", False):\r\n logger.setLevel(logging.DEBUG)\r\n logger.debug(\"[INIT] set log level to DEBUG\")\r\n\r\n logger.info(\"[INIT] load config: {}\".format(drag_sensitive(config)))\r\n\r\n config.load_user_datas()\r\n\r\n\r\ndef get_root():\r\n return os.path.dirname(os.path.abspath(__file__))\r\n\r\n\r\ndef read_file(path):\r\n with open(path, mode=\"r\", encoding=\"utf-8\") as f:\r\n return f.read()\r\n\r\n\r\ndef conf():\r\n return config\r\n\r\n\r\ndef get_appdata_dir():\r\n data_path = os.path.join(get_root(), conf().get(\"appdata_dir\", \"\"))\r\n if not os.path.exists(data_path):\r\n logger.info(\"[INIT] data path not exists, create it: {}\".format(data_path))\r\n os.makedirs(data_path)\r\n return data_path\r\n\r\n\r\ndef subscribe_msg():\r\n trigger_prefix = conf().get(\"single_chat_prefix\", [\"\"])[0]\r\n msg = conf().get(\"subscribe_msg\", \"\")\r\n return msg.format(trigger_prefix=trigger_prefix)\r\n\r\n\r\n# global plugin config\r\nplugin_config = {}\r\n\r\n\r\ndef write_plugin_config(pconf: dict):\r\n \"\"\"\r\n 写入插件全局配置\r\n :param pconf: 全量插件配置\r\n \"\"\"\r\n global plugin_config\r\n for k in pconf:\r\n plugin_config[k.lower()] = pconf[k]\r\n\r\ndef remove_plugin_config(name: str):\r\n \"\"\"\r\n 移除待重新加载的插件全局配置\r\n :param name: 待重载的插件名\r\n \"\"\"\r\n global plugin_config\r\n plugin_config.pop(name.lower(), None)\r\n\r\n\r\ndef pconf(plugin_name: str) -> dict:\r\n \"\"\"\r\n 根据插件名称获取配置\r\n :param plugin_name: 插件名称\r\n :return: 该插件的配置项\r\n \"\"\"\r\n return plugin_config.get(plugin_name.lower())\r\n\r\n\r\n# 全局配置,用于存放全局生效的状态\r\nglobal_config = {\"admin_users\": []}\r\n"}}}}