Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/blackoutta_ai-engineer-training.json
T

1 line
91 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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": "<p align=\"center\"><img src= \"https://github.com/user-attachments/assets/31fb4eab-3be4-477d-aa76-82cf62bfd12c\" alt=\"Chatgpt-on-Wechat\" width=\"600\" /></p>\r\n\r\n<p align=\"center\">\r\n <a href=\"https://github.com/zhayujie/chatgpt-on-wechat/releases/latest\"><img src=\"https://img.shields.io/github/v/release/zhayujie/chatgpt-on-wechat\" alt=\"Latest release\"></a>\r\n <a href=\"https://github.com/zhayujie/chatgpt-on-wechat/blob/master/LICENSE\"><img src=\"https://img.shields.io/github/license/zhayujie/chatgpt-on-wechat\" alt=\"License: MIT\"></a>\r\n <a href=\"https://github.com/zhayujie/chatgpt-on-wechat\"><img src=\"https://img.shields.io/github/stars/zhayujie/chatgpt-on-wechat?style=flat-square\" alt=\"Stars\"></a> <br/>\r\n</p>\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<img width=\"140\" src=\"https://img-1317903499.cos.ap-guangzhou.myqcloud.com/docs/open-community.png\">\r\n\r\n<br/>\r\n\r\n# 企业服务\r\n\r\n<a href=\"https://link-ai.tech\" target=\"_blank\"><img width=\"720\" src=\"https://cdn.link-ai.tech/image/link-ai-intro.jpg\"></a>\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<img width=\"150\" src=\"https://cdn.link-ai.tech/portal/linkai-customer-service.png\">\r\n\r\n<br/>\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<br/>\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<details>\r\n<summary>1. 单聊配置</summary>\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</details>\r\n\r\n\r\n<details>\r\n<summary>2. 群聊配置</summary>\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</details>\r\n\r\n<details>\r\n<summary>3. 语音配置</summary>\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</details>\r\n\r\n<details>\r\n<summary>4. 其他配置</summary>\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</details>\r\n\r\n<details>\r\n<summary>5. LinkAI配置</summary>\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</details>\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<details>\r\n<summary>OpenAI</summary>\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</details>\r\n\r\n<details>\r\n<summary>LinkAI</summary>\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</details>\r\n\r\n<details>\r\n<summary>DeepSeek</summary>\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</details>\r\n\r\n<details>\r\n<summary>Azure</summary>\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</details>\r\n\r\n<details>\r\n<summary>Claude</summary>\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</details>\r\n\r\n<details>\r\n<summary>通义千问</summary>\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</details>\r\n\r\n<details>\r\n<summary>Gemini</summary>\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</details>\r\n\r\n<details>\r\n<summary>Moonshot</summary>\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</details>\r\n\r\n<details>\r\n<summary>百度文心</summary>\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</details>\r\n\r\n<details>\r\n<summary>讯飞星火</summary>\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</details>\r\n\r\n<details>\r\n<summary>智谱AI</summary>\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</details>\r\n\r\n<details>\r\n<summary>MiniMax</summary>\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</details>\r\n\r\n<details>\r\n<summary>ModelScope</summary>\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</details>\r\n\r\n\r\n## 通道说明\r\n\r\n以下对可接入通道的配置方式进行说明,应用通道代码在项目的 `channel/` 目录下。\r\n\r\n<details>\r\n<summary>Web</summary>\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</details>\r\n\r\n<details>\r\n<summary>Terminal</summary>\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</details>\r\n\r\n<details>\r\n<summary>微信公众号</summary>\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</details>\r\n\r\n<details>\r\n<summary>企业微信应用</summary>\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</details>\r\n\r\n<details>\r\n<summary>钉钉</summary>\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</details>\r\n\r\n<details>\r\n<summary>飞书</summary>\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</details>\r\n\r\n<br/>\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: <https://github.com/zhayujie/chatgpt-on-wechat/wiki/FAQs>\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"}}}}