-
一.前言说明(文章最后有部署的python代码)本文介绍了如何通过部署在腾讯云CloudBase上的桥接服务,打通企业微信应用(机器人)与AgentArts运行时的方法。并列举了使用华为云FunctionGraph的方案的差异(未测试)。以下为简单流程:企业微信应用(机器人) → AgentArts 桥接服务(华为云FunctionGrah或者腾讯云CloudBase) → AgentArts运行时 二.为什么需要桥接服务?1、企业微信机器人无法直接调用AgentArts的原因a.协议不兼容维度企业微信机器人AgentArts 智能体运行时消息格式JSON/XML 加密信封REST API JSON认证方式AES-256-CBC + SHA1签名Bearer Token通信模式被动回调(企业微信→服务)主动调用(服务→AgentArts)响应要求必须在 5秒内 返回可能需要 5-30秒 响应 b.安全机制冲突- 企业微信 :所有消息必须使用 EncodingAESKey 加密,防止中间人攻击- AgentArts :使用 API Key + Bearer Token 进行身份验证- 两者的安全协议完全独立,无法直接互通 c. 架构模式差异数据格式不匹配- 企业微信 :发送的是加密的消息对象(包含用户ID、消息类型、时间戳等)- AgentArts :期望接收简单的 {"input": "用户问题"} 格式- 两者的数据结构完全不同,需要转换 2、AgentArts桥接服务的必要性a.桥接服务的核心作用b.桥接服务承担的职责职责说明消息解密使用企业微信的 Token 和 AESKey 解密收到的加密消息格式转换将企业微信消息格式转换为 AgentArts API 所需的格式会话管理使用用户ID作为Session ID,保持多轮对话记忆异步处理处理 AgentArts 可能的长响应时间(超过5秒)消息加密将 AgentArts 的回复加密后返回给企业微信签名验证验证企业微信请求的合法性,防止伪造请求 c.5秒超时问题的解决方案方案A:同步模式(适合快速响应)用户消息 → 桥接服务 → AgentArts → 立即返回加密响应← 5秒内完成方案B:异步模式(适合长耗时任务)用户消息 → 桥接服务 → 立即返回空响应(告诉企业微信已收到) ↓后台线程处理 → AgentArts → 通过企业微信API主动推送回复3、桥接模式必要性的总结a.无法直接调用的根本原因:1. 协议层 :企业微信使用加密回调,AgentArts使用REST API2. 安全层 :认证方式和加解密机制完全不同3. 时间层 :企业微信要求5秒响应,AgentArts可能需要更长时间b.桥接服务的必要性:- 作为 协议转换器 :实现两种不同协议的互通- 作为 安全网关 :处理双方的认证和加密- 作为 异步缓冲 :解决响应超时问题- 作为 会话管理器 :维护用户对话上下文 一句话总结 :桥接服务是连接两个完全不同生态系统的桥梁,没有它,企业微信机器人和AgentArts智能体就像说两种不同语言的人,无法直接沟通。三. 企业微信机器人通过CloudBase打通AgentArts整体方案步骤1:配置企业微信自建应用3.1登录企业微信管理后台https://work.weixin.qq.com/wework_admin/frame#/3.2 创建自建应用 - 进入「应用管理」→「应用」→「自建」 - 点击「创建应用」,填写应用名称(如:AI助手) - 上传应用图标3.3 获取应用配置 - 企业ID :我的企业 → 企业信息 → 企业ID - AgentId :应用详情页 → AgentId - Secret :应用详情页 → 点击查看Secret4. 配置接收消息 - 进入应用详情页 → 「接收消息」 - 设置 Token (自定义字符串,如:my_wx_token) - 点击「随机获取」生成 EncodingAESKey (43字符) - 填写回调URL(稍后配置CloudBase后填写)需要注意的是部署在腾讯云或者华为云上的桥接服务,需要挂载企业域名,才可以通过腾讯企业微信的验证要求。5. 启用智能机器人功能 - 在应用详情页找到「智能机器人」设置 - 启用机器人功能,配置可见范围步骤2:部署CloudBase云函数1. 创建CloudBase环境 - 登录腾讯云控制台 → 云开发CloudBase - 创建新环境(选择按量计费,免费额度充足)2. 创建云函数 - 进入「云函数」→「新建」 - 函数名称: wecom-bot-bridge - 运行环境:Python 3.10 - 创建方式:空白函数3. 配置HTTP 开启HTTP访问为当前域名添加路由例如当前云函数路径为https://cloud1-7gmelj5udf09f32f-1409020909.ap-shanghai.app.tcloudbase.com添加的路由为/wangran-agentarts则访问路径为:https://cloud1-7gmelj5udf09f32f-1409020909.ap-shanghai.app.tcloudbase.com/wangran-agentarts4. 上传代码 - 复制 wecom-bot-tcb.py 的全部代码 - 粘贴到在线编辑器中 - 保存为 index.py - 入口函数设置为: index.main5. 配置环境变量步骤3:配置AgentArts智能体1.确保智能体已部署- 运行时名称: runtime-17uneunb- 访问域名: defaultgw-mvstsmzsgv.cn-southwest-2.huaweicloud-agentarts.com- API Key: 3de5d906dfc24af98431ba4954f4b589以上数据为举例说明,需要根据实际情况自己更改2.验证云函数的可用性(已桥接智能体)curl.exe -x POST https://cloud1-7gmelj5udf09f32f-1409020909.ap-shanghai.app.tcloudbase.com/wangran-agentarts/test -H "Content-type: application/json" -d "{\"input\":"\"帮我指定一个3天的旅游计划"\"}" 步骤4:连接企业微信和CloudBase- 回到企业微信应用详情页 → 「接收消息」- 回调URL: https://xxx.tcb.qcloud.la/wecom-bot-bridge/callback- Token:与CloudBase环境变量中的 WECOM_TOKEN 一致- EncodingAESKey:与CloudBase环境变量中的 WECOM_ENCODING_AES_KEY 一致2.验证URL- 点击「保存」后,企业微信会发送GET请求验证URL- CloudBase云函数会自动处理验证请求- 验证成功后显示「验证通过」四、完整数据流向用户在群聊中 @机器人 │ ▼┌──────────────────────────────────┐│ 1. 企业微信服务器处理 │ • 识别@机器人消息 │ • 使用 AES-256-CBC 加密消息 │ • 生成 SHA1 签名 │ • POST 到 CloudBase 云函数 └──────────────────────────────────┘ │ ▼┌──────────────────────────────────┐│ 2. CloudBase 云函数接收 │ • HTTP 触发器接收 POST 请求 │ • 提取 query 参数:msg_signature, timestamp, nonce │ • 解析 JSON 请求体:{"encrypt": "...", ...} └──────────────────────────────────┘ │ ▼┌──────────────────────────────────┐│ 3. 消息解密(纯Python实现,零依赖) │ • 验证 SHA1 签名 │ • 使用 EncodingAESKey 解密消息 │ • 提取明文 JSON: │ { │ "msgid": "...", │ "msgtype": "text", │ "from": {"userid": "zhangsan"}, │ "text": {"content": "@AI助手 正方形蓝苹果是什么?"} │ } └──────────────────────────────────┘ │ ▼┌──────────────────────────────────┐│ 4. 消息处理 │ • 去重检查(基于 msgid) │ • 过滤非文本消息 │ • 去除 @机器人 前缀 │ • 提取用户ID作为 session_id(保持多轮对话记忆) └───────────────────────────────────┘ │ ▼┌───────────────────────────────────┐│ 5. 调用 AgentArts 智能体 │ • 使用 urllib 发送 HTTP 请求(零依赖) │ • 请求头: │ - Authorization: Bearer {API_KEY} │ - X-Hw-Agentarts-Session-Id: {user_id} │ • 请求体:{"input": "正方形蓝苹果是什么?"} └───────────────────────────────────┘ │ ▼┌───────────────────────────────────┐│ 6. AgentArts 智能体处理 │ • 查询知识库(正方形蓝苹果信息) │ • 检索记忆库(历史对话) │ • 调用 LLM 生成回复 │ • 返回 JSON:{"response": "根据知识库信息..."} └───────────────────────────────────┘ │ ▼┌───────────────────────────────────┐│ 7. CloudBase 处理响应 │ • 解析 AgentArts 返回的 JSON │ • 提取 response 字段 │ • 构造回复消息: │ {"msgtype": "text", "text": {"content": "回复内容"}} └───────────────────────────────────┘ │ ▼┌───────────────────────────────────┐│ 8. 消息加密 │ • 使用 AES-256-CBC 加密回复消息 │ • 生成新的 SHA1 签名 │ • 构造 JSON 信封: │ { │ "encrypt": "...", │ "msgsignature": "...", │ "timestamp": "...", │ "nonce": "..." │ } └───────────────────────────────────┘ │ ▼┌───────────────────────────────────┐│ 9. 返回给企业微信 │ • CloudBase 返回加密的 JSON 信封 │ • HTTP 状态码:200 │ • Content-Type: application/json └───────────────────────────────────┘ │ ▼┌───────────────────────────────────┐│ 10. 企业微信展示给用户 │ • 解密消息 │ • 在群聊中显示机器人的回复 └───────────────────────────────────┘五、华为云FunctionGraph替代腾讯云CloudBase的方案对比可以使用华为云FunctionGraph方案方案替代腾讯云CloudBase华为云方案未经验证,需要自行验证,本文仅提供测试代码。wecom-bot-fg.py为华为云FunctionGraph方案的部署代码wecom-bot-tcb.py为腾讯CloudBase云函数方案的部署代码 文件wecom-bot-fg.pywecom-bot-tcb.py部署平台华为云 FunctionGraph腾讯云 CloudBase消息格式JSON(2025新版)JSON(2025新版)回复模式同步(直接返回加密响应)同步(直接返回加密响应)依赖flask,requests,pycryptodome零依赖 (纯标准库)入口方式Flask WSGICloudBase HTTP触发器超时处理同步等待(需AgentArts快响应)同步等待适用场景华为云生态用户腾讯云生态/无依赖要求wecom-bot-tcb.py""" 企业微信智能机器人 → 腾讯云 CloudBase 云函数 → AgentArts 智能体 ============================================================== 本文件专为 CloudBase 云函数设计,使用原生 HTTP 触发器格式。 **零第三方依赖**:只使用 Python 标准库(urllib + hashlib + 内置加解密)。 说明: - 复制全部代码到 CloudBase 在线编辑器,保存为 index.py - 处理程序填: index.main - 不需要 requirements.txt(无外部依赖!) - 环境变量需要配置 7 个(见下方) """ import json import os import re import time import struct import base64 import hashlib import hmac import socket import ssl import logging import urllib.request from typing import Tuple, Optional # ============================================================================ # 配置 # ============================================================================ AGENTARTS_URL = os.getenv("AGENTARTS_URL", "https://defaultgw-mvstsmzsgv.cn-southwest-2.huaweicloud-agentarts.com" "/runtimes/runtime-17uneunb/invocations?endpoint=Latest") AGENTARTS_API_KEY = os.getenv("AGENTARTS_API_KEY", "3de5d906dfc24af98431bc4954f4b589") WECOM_CORP_ID = os.getenv("WECOM_CORP_ID", "") WECOM_TOKEN = os.getenv("WECOM_TOKEN", "") WECOM_ENCODING_AES_KEY = os.getenv("WECOM_ENCODING_AES_KEY", "") LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") logging.basicConfig( level=getattr(logging, LOG_LEVEL.upper(), logging.INFO), format="%(asctime)s [%(levelname)s] %(message)s", ) logger = logging.getLogger("wecom-bot-tcb") # ============================================================================ # 纯 Python 实现 AES-256-CBC(无第三方依赖) # ============================================================================ class _AES256CBC: """ 纯 Python AES-256-CBC 实现。 仅依赖 hashlib(标准库),无需 pycryptodome / cryptography。 """ @staticmethod def _pad(data: bytes, block_size: int = 16) -> bytes: pad_len = block_size - (len(data) % block_size) return data + bytes([pad_len] * pad_len) @staticmethod def _unpad(data: bytes) -> bytes: pad_len = data[-1] if pad_len < 1 or pad_len > 16: raise ValueError(f"无效填充长度: {pad_len}") return data[:-pad_len] @staticmethod def _xor_bytes(a: bytes, b: bytes) -> bytes: return bytes(x ^ y for x, y in zip(a, b)) @classmethod def _aes_encrypt_block(cls, block: bytes, round_keys: list) -> bytes: """加密单个 16 字节块""" # AES-256 使用 14 轮 state = list(block) # AddRoundKey state = cls._xor_bytes(state, round_keys[0]) for r in range(1, 14): state = cls._sub_bytes(state) state = cls._shift_rows(state) state = cls._mix_columns(state) state = cls._xor_bytes(state, round_keys[r]) # Final round (no MixColumns) state = cls._sub_bytes(state) state = cls._shift_rows(state) state = cls._xor_bytes(state, round_keys[14]) return bytes(state) @classmethod def _aes_decrypt_block(cls, block: bytes, round_keys: list) -> bytes: """解密单个 16 字节块""" state = list(block) state = cls._xor_bytes(state, round_keys[14]) state = cls._inv_shift_rows(state) state = cls._inv_sub_bytes(state) for r in range(13, 0, -1): state = cls._xor_bytes(state, round_keys[r]) state = cls._inv_mix_columns(state) state = cls._inv_shift_rows(state) state = cls._inv_sub_bytes(state) state = cls._xor_bytes(state, round_keys[0]) return bytes(state) # ---- S-Box ---- _SBOX = [ 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16, ] _INV_SBOX = [ 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d, ] _RCON = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36] @classmethod def _sub_bytes(cls, state: bytes) -> bytes: return bytes(cls._SBOX[b] for b in state) @classmethod def _inv_sub_bytes(cls, state: bytes) -> bytes: return bytes(cls._INV_SBOX[b] for b in state) @staticmethod def _shift_rows(state: bytes) -> bytes: return bytes([state[0], state[5], state[10], state[15], state[4], state[9], state[14], state[3], state[8], state[13], state[2], state[7], state[12], state[1], state[6], state[11]]) @staticmethod def _inv_shift_rows(state: bytes) -> bytes: return bytes([state[0], state[13], state[10], state[7], state[4], state[1], state[14], state[11], state[8], state[5], state[2], state[15], state[12], state[9], state[6], state[3]]) @staticmethod def _gmul(a: int, b: int) -> int: """GF(2^8) 乘法""" p = 0 for _ in range(8): if b & 1: p ^= a hi = a & 0x80 a = (a << 1) & 0xFF if hi: a ^= 0x1b b >>= 1 return p @classmethod def _mix_columns(cls, state: bytes) -> bytes: result = bytearray(16) for i in range(4): s0, s1, s2, s3 = state[i*4], state[i*4+1], state[i*4+2], state[i*4+3] result[i*4] = cls._gmul(2,s0) ^ cls._gmul(3,s1) ^ s2 ^ s3 result[i*4+1] = s0 ^ cls._gmul(2,s1) ^ cls._gmul(3,s2) ^ s3 result[i*4+2] = s0 ^ s1 ^ cls._gmul(2,s2) ^ cls._gmul(3,s3) result[i*4+3] = cls._gmul(3,s0) ^ s1 ^ s2 ^ cls._gmul(2,s3) return bytes(result) @classmethod def _inv_mix_columns(cls, state: bytes) -> bytes: result = bytearray(16) for i in range(4): s0, s1, s2, s3 = state[i*4], state[i*4+1], state[i*4+2], state[i*4+3] result[i*4] = cls._gmul(14,s0)^cls._gmul(11,s1)^cls._gmul(13,s2)^cls._gmul(9,s3) result[i*4+1] = cls._gmul(9,s0)^cls._gmul(14,s1)^cls._gmul(11,s2)^cls._gmul(13,s3) result[i*4+2] = cls._gmul(13,s0)^cls._gmul(9,s1)^cls._gmul(14,s2)^cls._gmul(11,s3) result[i*4+3] = cls._gmul(11,s0)^cls._gmul(13,s1)^cls._gmul(9,s2)^cls._gmul(14,s3) return bytes(result) @classmethod def _key_expansion(cls, key: bytes) -> list: """AES-256 密钥扩展 → 15 个轮密钥""" Nk, Nr = 8, 14 w = [] for i in range(Nk): w.append(key[i*4:(i+1)*4]) for i in range(Nk, 4 * (Nr + 1)): temp = w[i - 1] if i % Nk == 0: temp = cls._xor_bytes( bytes(cls._SBOX[b] for b in temp[1:] + temp[:1]), bytes([cls._RCON[i // Nk - 1], 0, 0, 0])) elif Nk > 6 and i % Nk == 4: temp = bytes(cls._SBOX[b] for b in temp) w.append(cls._xor_bytes(w[i - Nk], temp)) round_keys = [] for r in range(Nr + 1): round_keys.append(b"".join(w[r*4:(r+1)*4])) return round_keys @classmethod def encrypt_cbc(cls, plaintext: bytes, key: bytes, iv: bytes) -> bytes: """AES-256-CBC 加密""" padded = cls._pad(plaintext, 16) round_keys = cls._key_expansion(key) result = bytearray() prev = iv for i in range(0, len(padded), 16): block = cls._xor_bytes(padded[i:i+16], prev) encrypted = cls._aes_encrypt_block(block, round_keys) result.extend(encrypted) prev = encrypted return bytes(result) @classmethod def decrypt_cbc(cls, ciphertext: bytes, key: bytes, iv: bytes) -> bytes: """AES-256-CBC 解密""" round_keys = cls._key_expansion(key) result = bytearray() prev = iv for i in range(0, len(ciphertext), 16): decrypted = cls._aes_decrypt_block(ciphertext[i:i+16], round_keys) result.extend(cls._xor_bytes(decrypted, prev)) prev = ciphertext[i:i+16] return cls._unpad(bytes(result)) # ============================================================================ # 企业微信消息加解密(纯 Python 实现,零依赖) # ============================================================================ class WXBizMsgCrypt: """企业微信消息加解密:AES-256-CBC + SHA1 签名""" def __init__(self, token: str, encoding_aes_key: str, corp_id: str): self.token = token self.corp_id = corp_id self.aes_key = base64.b64decode(encoding_aes_key + "=") if len(self.aes_key) != 32: raise ValueError(f"AES 密钥长度错误: {len(self.aes_key)} (应为 32)") @staticmethod def _sha1(text: str) -> str: return hashlib.sha1(text.encode("utf-8")).hexdigest() def _make_signature(self, timestamp: str, nonce: str, encrypt_msg: str) -> str: return self._sha1("".join(sorted([self.token, timestamp, nonce, encrypt_msg]))) def _verify_signature(self, sig: str, ts: str, nonce: str, enc: str) -> bool: return self._make_signature(ts, nonce, enc) == sig def _decrypt(self, encrypt_b64: str) -> bytes: raw = _AES256CBC.decrypt_cbc( base64.b64decode(encrypt_b64), self.aes_key, self.aes_key[:16]) content = raw[16:] msg_len = socket.ntohl(struct.unpack("I", content[:4])[0]) return content[4:4 + msg_len] def _encrypt(self, plaintext: bytes) -> str: random_bytes = os.urandom(16) msg_len_bytes = struct.pack("!I", len(plaintext)) raw = random_bytes + msg_len_bytes + plaintext + self.corp_id.encode("utf-8") return base64.b64encode( _AES256CBC.encrypt_cbc(raw, self.aes_key, self.aes_key[:16])).decode("utf-8") def verify_url(self, signature: str, timestamp: str, nonce: str, echostr: str) -> Tuple[int, str]: if not self._verify_signature(signature, timestamp, nonce, echostr): return -1, "签名验证失败" try: return 0, self._decrypt(echostr).decode("utf-8") except Exception as e: return -2, f"解密失败: {e}" def decrypt_msg(self, signature: str, timestamp: str, nonce: str, body_json: dict) -> Tuple[int, dict]: encrypt_b64 = body_json.get("encrypt", "") if not self._verify_signature(signature, timestamp, nonce, encrypt_b64): return -1, {} try: return 0, json.loads(self._decrypt(encrypt_b64).decode("utf-8")) except Exception as e: logger.error(f"消息解密失败: {e}") return -2, {} def encrypt_msg(self, reply: dict, nonce: str, timestamp: str = None) -> str: if timestamp is None: timestamp = str(int(time.time())) encrypt_b64 = self._encrypt( json.dumps(reply, ensure_ascii=False).encode("utf-8")) signature = self._make_signature(timestamp, nonce, encrypt_b64) return json.dumps({ "encrypt": encrypt_b64, "msgsignature": signature, "timestamp": timestamp, "nonce": nonce, }, ensure_ascii=False) # ============================================================================ # HTTP 客户端(urllib,零依赖) # ============================================================================ def http_post_agentarts(url: str, payload: dict, headers: dict = None, timeout: int = 30) -> dict: """POST 到 AgentArts API,自动处理 SSE 流式响应""" headers = headers or {} headers.setdefault("Content-Type", "application/json") body = json.dumps(payload).encode("utf-8") ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE req = urllib.request.Request(url, data=body, headers=headers, method="POST") try: with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp: raw = resp.read() text = raw.decode("utf-8") logger.info(f"[HTTP] status={resp.status}, body_len={len(raw)}") # 尝试直接解析 JSON try: return json.loads(text) except json.JSONDecodeError: pass # 解析 SSE 格式 (data:{...}\n\ndata:{...}\n\n...) chunks = [] for line in text.split("\n"): line = line.strip() if line.startswith("data:"): chunk_json = line[5:].strip() try: chunk = json.loads(chunk_json) chunks.append(chunk) logger.info(f"[SSE] event={chunk.get('event', '?')}, " f"data_keys={list(chunk.get('data', {}).keys()) if isinstance(chunk.get('data'), dict) else '?'}") except json.JSONDecodeError: logger.warning(f"[SSE] 跳过无法解析的行: {chunk_json[:80]}") if not chunks: raise RuntimeError(f"无法解析响应 (status={resp.status}): {text[:300]}") # 从 SSE chunks 中提取结果 # AgentArts 的 SSE 格式: {"event": "message", "answer": "..."} # 或: {"event": "error", "data": {"message": "..."}} last_chunk = chunks[-1] # 检查错误事件 for chunk in chunks: event_type = chunk.get("event", "") if event_type == "error": err_data = chunk.get("data", chunk) error_msg = err_data.get("message", str(err_data)) raise RuntimeError(f"AgentArts 错误: {error_msg}") # 提取回复内容 # 尝试多种字段名: answer, response, output, text, content answer = ( last_chunk.get("answer") or (last_chunk.get("data", {}) or {}).get("answer") or last_chunk.get("response") or last_chunk.get("output") or last_chunk.get("text") ) if answer: return {"response": answer, "status": "success"} # 如果 last chunk 没有直接答案,遍历所有 chunks 拼接 full_answer = "" for chunk in chunks: event_type = chunk.get("event", "") if event_type in ("done", "error", "ping"): continue # 尝试多种字段名 text = (chunk.get("answer") or chunk.get("delta") or chunk.get("content") or chunk.get("text") or chunk.get("response") or (chunk.get("data", {}) or {}).get("answer") or (chunk.get("data", {}) or {}).get("delta") or (chunk.get("data", {}) or {}).get("content") or (chunk.get("data", {}) or {}).get("text") or "") if text: full_answer += text if full_answer: return {"response": full_answer, "status": "success"} # 兜底:返回所有非 done 的 chunks(调试用) content_chunks = [c for c in chunks if c.get("event") not in ("done", "ping")] return { "response": "未能提取回复,所有数据块: " + json.dumps(content_chunks, ensure_ascii=False), "status": "debug", } except urllib.error.HTTPError as e: body_text = e.read().decode("utf-8", errors="replace")[:500] logger.error(f"[HTTP] HTTPError {e.code}: {body_text}") raise RuntimeError(f"HTTP {e.code}: {body_text}") except Exception as e: if "请求失败" in str(e) or "AgentArts" in str(e) or "无法解析" in str(e): raise raise RuntimeError(f"请求失败: {e}") # ============================================================================ # AgentArts 客户端 # ============================================================================ class AgentArtsClient: """AgentArts API 封装(urllib 实现)""" def __init__(self, url: str, api_key: str): self.url = url self.api_key = api_key def chat(self, user_input: str, user_id: str = "", timeout: int = 25) -> str: headers = {"Authorization": f"Bearer {self.api_key}"} if user_id: headers["X-Hw-Agentarts-Session-Id"] = user_id headers["X-Hw-Agentgateway-User-Id"] = user_id try: data = http_post_agentarts(self.url, {"input": user_input}, headers, timeout) return data.get("response") or data.get("output") or str(data) except Exception as e: logger.error(f"AgentArts 调用失败: {e}") return "⚠️ 智能体服务暂不可用,请稍后重试。" # ============================================================================ # 消息处理 # ============================================================================ wx_crypt = None agent_client = None _seen_ids: set = set() MENTION_PATTERN = re.compile(r"@\S+\s*") def _init(): global wx_crypt, agent_client if wx_crypt is None and all([WECOM_TOKEN, WECOM_ENCODING_AES_KEY, WECOM_CORP_ID]): wx_crypt = WXBizMsgCrypt(WECOM_TOKEN, WECOM_ENCODING_AES_KEY, WECOM_CORP_ID) if agent_client is None: agent_client = AgentArtsClient(AGENTARTS_URL, AGENTARTS_API_KEY) # ============================================================================ # CloudBase HTTP 触发器入口 # ============================================================================ def main(event, context): """ CloudBase HTTP 云函数入口。 处理企业微信回调的两类请求: GET /callback?msg_signature=...&echostr=... → URL 验证 POST /callback (加密 JSON) → 消息处理 """ _init() path = event.get("path", "/") method = event.get("httpMethod", "GET") query = event.get("queryStringParameters", {}) or {} logger.info(f"{method} {path}") if method == "GET" and path in ("/", ""): return _resp(200, "OK") # --- 测试接口:直接调 AgentArts(不经过企业微信加密)--- if method == "POST" and path.endswith("/test"): return _handle_test(event) if method == "GET" and path.endswith("/callback"): return _handle_verify(query) if method == "POST" and path.endswith("/callback"): body = event.get("body", "") or "" if event.get("isBase64Encoded", False): import base64 as b64 body = b64.b64decode(body).decode("utf-8") return _handle_message(query, body) return _resp(404, "Not Found") # ============================================================================ # 辅助函数 # ============================================================================ def _resp(status: int, body: str, content_type: str = "text/plain") -> dict: return { "statusCode": status, "headers": {"Content-Type": f"{content_type}; charset=utf-8"}, "body": body, } def _handle_test(event: dict) -> dict: """ 测试接口:直接调用 AgentArts,不经过企业微信加密。 """ # 先看看 CloudBase 到底传了什么(调试用) debug_info = { "path": event.get("path", ""), "httpMethod": event.get("httpMethod", ""), "hasBody": "body" in event, "bodyType": str(type(event.get("body", ""))), "bodyLen": len(event.get("body") or ""), "bodyPreview": str((event.get("body") or "")[:200]), "isBase64": event.get("isBase64Encoded", False), "keys": list(event.keys()), "headerKeys": list((event.get("headers", {}) or {}).keys()), } logger.info(f"[DEBUG] event: {json.dumps(debug_info, ensure_ascii=False)}") body_raw = event.get("body", "") or "" # 尝试解析 try: req = json.loads(body_raw) except json.JSONDecodeError: # 返回调试信息帮助定位 return _resp(400, json.dumps({"error": "invalid JSON", "debug": debug_info}, ensure_ascii=False, indent=2), "application/json") user_input = req.get("input", "") if not user_input: return _resp(400, json.dumps({"error": "缺少 input 字段", "received": req}, ensure_ascii=False), "application/json") logger.info(f"[TEST] 调用 AgentArts: {user_input[:60]}") try: headers = { "Authorization": f"Bearer {AGENTARTS_API_KEY}", "X-Hw-Agentarts-Session-Id": "cloudbase-test-session", } # 同时发送 input 和 query,兼容不同版本的 AgentArts API result = http_post_agentarts(AGENTARTS_URL, {"input": user_input, "query": user_input}, headers, timeout=30) return _resp(200, json.dumps(result, ensure_ascii=False, indent=2), "application/json") except Exception as e: return _resp(500, json.dumps({"error": str(e)}, ensure_ascii=False), "application/json") def _handle_verify(query: dict) -> dict: signature = query.get("msg_signature", "") timestamp = query.get("timestamp", "") nonce = query.get("nonce", "") echostr = query.get("echostr", "") logger.info(f"URL 验证: ts={timestamp}") if wx_crypt is None: return _resp(500, "server not configured") ret, result = wx_crypt.verify_url(signature, timestamp, nonce, echostr) if ret == 0: logger.info("URL 验证成功") return _resp(200, result) logger.error(f"URL 验证失败: {result}") return _resp(403, f"verify failed: {result}") def _handle_message(query: dict, body_str: str) -> dict: signature = query.get("msg_signature", "") timestamp = query.get("timestamp", "") nonce = query.get("nonce", "") try: body_json = json.loads(body_str) except (json.JSONDecodeError, UnicodeDecodeError): return _resp(400, "bad request") if wx_crypt is None: return _resp(500, "server not configured") ret, msg = wx_crypt.decrypt_msg(signature, timestamp, nonce, body_json) if ret != 0: return _resp(403, "decrypt failed") from_id = (msg.get("from", {}) or {}).get("userid", "?") msg_type = msg.get("msgtype", "?") logger.info(f"消息: from={from_id}, type={msg_type}") msg_id = msg.get("msgid", "") if msg_id: if msg_id in _seen_ids: return _resp(200, "") _seen_ids.add(msg_id) if len(_seen_ids) > 5000: _seen_ids.clear() if msg_type != "text": return _resp(200, "") content = (msg.get("text", {}) or {}).get("content", "") if not content or not content.strip(): return _resp(200, "") content = MENTION_PATTERN.sub("", content, count=1).strip() if not content: return _resp(200, "") logger.info(f"用户 {from_id}: {content[:60]}") reply_text = agent_client.chat(content, user_id=from_id) logger.info(f"回复 {from_id}: {reply_text[:60]}...") reply_json = {"msgtype": "text", "text": {"content": reply_text}} encrypted = wx_crypt.encrypt_msg(reply_json, nonce, timestamp) return _resp(200, encrypted, "application/json") """ 企业微信智能机器人 → 云函数 → AgentArts 智能体 ============================================== ⚠️ 重要:企业微信群机器人不支持接收消息回调! 正确做法是使用「自建应用」→「智能机器人」功能。 支持的云平台: • 腾讯云 SCF(云函数 Web 函数)—— 推荐,个人免费额度充裕 • 华为云 FunctionGraph(HTTP 函数) • 任意 VPS / Docker 部署 架构: 用户在群里 @机器人 / 私聊机器人 │ ▼ 企业微信 POST JSON(加密) → 云函数(Flask) │ ├─ 解密消息(AES-256-CBC + SHA1 签名验证) ├─ 调用 AgentArts API(同步等待) ├─ 加密回复(AES-256-CBC) └─ 返回加密 JSON → 企业微信展示给用户 部署:上传 ZIP 到云函数控制台 依赖:flask, requests, pycryptodome ┌─────────────────────────────────────────────────────────────┐ │ 环境变量从哪里获取(详见整合指南) │ ├────────────────────┬────────────────────────────────────────┤ │ AGENTARTS_URL │ AgentArts 控制台 → 智能体运行时 → │ │ │ 最新版本配置详情 → 访问域名 + 路径 │ │ AGENTARTS_API_KEY │ AgentArts 控制台 → 智能体运行时 → │ │ │ 访问与权限控制 → API Key │ │ WECOM_CORP_ID │ 企业微信管理后台 → 我的企业 → 企业ID │ │ WECOM_TOKEN │ 自定义字符串,在自建应用接收消息页填写 │ │ WECOM_ENCODING_AES │ 自建应用接收消息页 → 点击「随机获取」 │ │ WECOM_AGENT_SECRET │ 自建应用详情页 → Secret(用于主动发消息) │ │ WECOM_AGENT_ID │ 自建应用详情页 → AgentId(用于主动发消息) │ └────────────────────┴────────────────────────────────────────┘ """ import json import os import re import time import struct import base64 import hashlib import socket import logging import threading from typing import Tuple, Optional import requests from Crypto.Cipher import AES from flask import Flask, request, Response # ============================================================================ # 配置(全部从环境变量读取) # ============================================================================ # --- AgentArts --- # 来源:AgentArts 控制台 → 智能体运行时 → 最新版本配置详情 → 访问域名 # 格式:https://{域名}/runtimes/{runtime_name}/invocations?endpoint=Latest AGENTARTS_URL = os.getenv("AGENTARTS_URL", "https://defaultgw-mvstsmzsgv.cn-southwest-2.huaweicloud-agentarts.com" "/runtimes/runtime-17uneunb/invocations?endpoint=Latest") # 来源:AgentArts 控制台 → 智能体运行时 → 访问与权限控制 → API Key AGENTARTS_API_KEY = os.getenv("AGENTARTS_API_KEY", "3de5d906dfc24af98431bc4954f4b589") # --- 企业微信自建应用 --- # 来源:企业微信管理后台 → 我的企业 → 企业ID WECOM_CORP_ID = os.getenv("WECOM_CORP_ID", "") # 来源:自建应用详情页 → Secret(点击查看) WECOM_AGENT_SECRET = os.getenv("WECOM_AGENT_SECRET", "") # 来源:自建应用详情页 → AgentId WECOM_AGENT_ID = os.getenv("WECOM_AGENT_ID", "") # 来源:自定义(字母+数字),在自建应用「接收消息」页填写,两边一致即可 WECOM_TOKEN = os.getenv("WECOM_TOKEN", "") # 来源:自建应用「接收消息」页 → 点击「随机获取」自动生成(43字符) WECOM_ENCODING_AES_KEY = os.getenv("WECOM_ENCODING_AES_KEY", "") # --- 服务配置 --- LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") logging.basicConfig( level=getattr(logging, LOG_LEVEL.upper(), logging.INFO), format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger("wecom-bot-fg") # ============================================================================ # 企业微信消息加解密(WXBizMsgCrypt) # 适用于自建应用 / 智能机器人,2025 年 JSON 格式 # ============================================================================ class WXBizMsgCrypt: """ 企业微信消息加解密。 加密算法:AES-256-CBC + PKCS#7 填充 签名算法:SHA1(token + timestamp + nonce + encrypt_msg) 排序后拼接 消息格式:JSON(2025 年版智能机器人格式) 参考:https://developer.work.weixin.qq.com/document/path/101033 """ def __init__(self, token: str, encoding_aes_key: str, corp_id: str): self.token = token self.corp_id = corp_id # EncodingAESKey: 43 字符 Base64 → 32 字节 AES 密钥 self.aes_key = base64.b64decode(encoding_aes_key + "=") if len(self.aes_key) != 32: raise ValueError(f"AES 密钥长度错误: {len(self.aes_key)} (应为 32)") # ---------- SHA1 签名 ---------- @staticmethod def _sha1(text: str) -> str: return hashlib.sha1(text.encode("utf-8")).hexdigest() def _make_signature(self, timestamp: str, nonce: str, encrypt_msg: str) -> str: params = sorted([self.token, timestamp, nonce, encrypt_msg]) return self._sha1("".join(params)) def _verify_signature(self, signature: str, timestamp: str, nonce: str, encrypt_msg: str) -> bool: return self._make_signature(timestamp, nonce, encrypt_msg) == signature # ---------- AES 加解密 ---------- def _pkcs7_pad(self, data: bytes, block_size: int = 32) -> bytes: pad_len = block_size - (len(data) % block_size) return data + bytes([pad_len] * pad_len) @staticmethod def _pkcs7_unpad(data: bytes) -> bytes: pad_len = data[-1] if pad_len < 1 or pad_len > 32: raise ValueError(f"无效填充长度: {pad_len}") return data[:-pad_len] def _aes_encrypt(self, plaintext: bytes) -> bytes: iv = self.aes_key[:16] cipher = AES.new(self.aes_key, AES.MODE_CBC, iv) return cipher.encrypt(self._pkcs7_pad(plaintext)) def _aes_decrypt(self, ciphertext: bytes) -> bytes: iv = self.aes_key[:16] cipher = AES.new(self.aes_key, AES.MODE_CBC, iv) return self._pkcs7_unpad(cipher.decrypt(ciphertext)) # ---------- 消息加解密 ---------- def _decrypt(self, encrypt_b64: str) -> bytes: """解密 Base64 编码的密文 → 返回明文 JSON 字节""" raw = self._aes_decrypt(base64.b64decode(encrypt_b64)) # 解密后格式: random(16) + msg_len(4, network order) + msg + receive_id content = raw[16:] msg_len = socket.ntohl(struct.unpack("I", content[:4])[0]) return content[4:4 + msg_len] def _encrypt(self, plaintext: bytes, corp_id: str) -> str: """加密明文 → 返回 Base64 编码的密文字符串""" random_bytes = os.urandom(16) msg_len_bytes = struct.pack("!I", len(plaintext)) raw = random_bytes + msg_len_bytes + plaintext + corp_id.encode("utf-8") return base64.b64encode(self._aes_encrypt(raw)).decode("utf-8") # ---------- 公开接口 ---------- def verify_url(self, signature: str, timestamp: str, nonce: str, echostr: str) -> Tuple[int, str]: """ URL 验证(GET 请求)。 企业微信配置回调 URL 时调用,需要解密 echostr 并返回明文。 """ if not self._verify_signature(signature, timestamp, nonce, echostr): return -1, "签名验证失败" try: plaintext = self._decrypt(echostr).decode("utf-8") return 0, plaintext except Exception as e: return -2, f"解密失败: {e}" def decrypt_msg(self, signature: str, timestamp: str, nonce: str, body_json: dict) -> Tuple[int, dict]: """ 解密消息(POST 请求,JSON 格式)。 输入 body_json 格式(JSON 信封): {"encrypt": "...", "msgsignature": "...", "timestamp": "...", "nonce": "..."} 返回解密后的消息 JSON dict。 """ encrypt_b64 = body_json.get("encrypt", "") if not self._verify_signature(signature, timestamp, nonce, encrypt_b64): return -1, {} try: plaintext = self._decrypt(encrypt_b64).decode("utf-8") return 0, json.loads(plaintext) except Exception as e: logger.error(f"消息解密失败: {e}") return -2, {} def encrypt_msg(self, reply: dict, nonce: str, timestamp: str = None) -> str: """ 加密回复消息,返回 JSON 信封字符串。 输入 reply: {"msgtype": "text", "text": {"content": "回复内容"}} 输出: {"encrypt": "...", "msgsignature": "...", "timestamp": "...", "nonce": "..."} """ if timestamp is None: timestamp = str(int(time.time())) plaintext = json.dumps(reply, ensure_ascii=False).encode("utf-8") encrypt_b64 = self._encrypt(plaintext, self.corp_id) signature = self._make_signature(timestamp, nonce, encrypt_b64) return json.dumps({ "encrypt": encrypt_b64, "msgsignature": signature, "timestamp": timestamp, "nonce": nonce, }, ensure_ascii=False) # ============================================================================ # AgentArts 客户端 # ============================================================================ class AgentArtsClient: """AgentArts API 轻量封装""" def __init__(self, url: str, api_key: str): self.url = url self.api_key = api_key self._session: Optional[requests.Session] = None @property def session(self) -> requests.Session: if self._session is None: self._session = requests.Session() self._session.headers.update({ "Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}", }) return self._session def chat(self, user_input: str, user_id: str = "", timeout: int = 25) -> str: """调用 AgentArts,返回文本回复""" headers = {} if user_id: headers["X-Hw-Agentarts-Session-Id"] = user_id headers["X-Hw-Agentgateway-User-Id"] = user_id try: resp = self.session.post( self.url, json={"input": user_input}, headers=headers, timeout=timeout, verify=False, ) resp.raise_for_status() data = resp.json() return data.get("response") or data.get("output") or str(data) except requests.Timeout: logger.error(f"AgentArts 超时: {user_input[:50]}...") return "⏳ 智能体响应超时,请稍后重试。" except requests.RequestException as e: logger.error(f"AgentArts 调用失败: {e}") return "⚠️ 智能体服务暂不可用,请稍后重试。" # ============================================================================ # 企业微信 API 客户端(用于异步推送回复) # ============================================================================ class WeComAPI: """企业微信 API:获取 access_token + 主动发送消息""" _token: Optional[str] = None _expires: float = 0 _lock = threading.Lock() def __init__(self, corp_id: str, secret: str, agent_id: str): self.corp_id = corp_id self.secret = secret self.agent_id = agent_id def _get_token(self) -> str: now = time.time() if self._token and now < (self._expires - 300): return self._token with self._lock: if self._token and now < (self._expires - 300): return self._token resp = requests.get( "https://qyapi.weixin.qq.com/cgi-bin/gettoken", params={"corpid": self.corp_id, "corpsecret": self.secret}, timeout=10, ) data = resp.json() if data.get("errcode") == 0: self._token = data["access_token"] self._expires = now + data.get("expires_in", 7200) logger.info("access_token 已刷新") return self._token raise RuntimeError(f"获取 access_token 失败: {data}") def send_text(self, user_id: str, content: str) -> bool: """主动发送文本消息给指定用户""" token = self._get_token() resp = requests.post( "https://qyapi.weixin.qq.com/cgi-bin/message/send", params={"access_token": token}, json={ "touser": user_id, "msgtype": "text", "agentid": int(self.agent_id), "text": {"content": content[:2048]}, }, timeout=10, ) data = resp.json() if data.get("errcode") != 0: logger.error(f"发送消息失败: {data}") return False return True # ============================================================================ # 核心处理逻辑 # ============================================================================ # 初始化组件(如果变量都配了) wx_crypt = None agent_client = None wecom_api = None def _init(): global wx_crypt, agent_client, wecom_api if wx_crypt is None and all([WECOM_TOKEN, WECOM_ENCODING_AES_KEY, WECOM_CORP_ID]): wx_crypt = WXBizMsgCrypt(WECOM_TOKEN, WECOM_ENCODING_AES_KEY, WECOM_CORP_ID) if agent_client is None: agent_client = AgentArtsClient(AGENTARTS_URL, AGENTARTS_API_KEY) if wecom_api is None and all([WECOM_CORP_ID, WECOM_AGENT_SECRET, WECOM_AGENT_ID]): wecom_api = WeComAPI(WECOM_CORP_ID, WECOM_AGENT_SECRET, WECOM_AGENT_ID) # 去重 _seen_ids: set = set() _seen_lock = threading.Lock() # @机器人 前缀匹配模式 MENTION_PATTERN = re.compile(r"@\S+\s*") def process_and_reply(msg: dict) -> str: """ 处理消息,返回回复文本。 消息格式(解密后的 JSON): { "msgid": "...", "msgtype": "text", "from": {"userid": "zhangsan"}, "text": {"content": "@AI助手 正方形蓝苹果是什么?"}, "chatid": "...", "chattype": "group" } """ msg_id = msg.get("msgid", "") msg_type = msg.get("msgtype", "") # --- 去重 --- if msg_id: with _seen_lock: if msg_id in _seen_ids: return "" _seen_ids.add(msg_id) if len(_seen_ids) > 5000: _seen_ids.clear() # --- 只处理文本 --- if msg_type != "text": logger.info(f"忽略非文本消息: {msg_type}") return "" content = (msg.get("text", {}) or {}).get("content", "") if not content or not content.strip(): return "" # 去掉 @机器人 前缀 content = MENTION_PATTERN.sub("", content, count=1).strip() if not content: return "你好!请直接输入你想问的问题,我会尽力回答 👋" from_info = msg.get("from", {}) user_id = from_info.get("userid", "unknown") logger.info(f"用户 {user_id}: {content[:60]}") # --- 调用 AgentArts --- reply = agent_client.chat(content, user_id=user_id) logger.info(f"回复 {user_id}: {reply[:60]}...") return reply # ============================================================================ # Flask 应用(FunctionGraph HTTP 函数入口) # ============================================================================ app = Flask(__name__) @app.route("/", methods=["GET", "HEAD"]) def health(): """健康检查""" return Response("OK", status=200) @app.route("/callback", methods=["GET"]) def verify_url(): """ ┌─────────────────────────────────────────┐ │ 回调 URL 验证(GET) │ │ 企业微信在配置回调 URL 时首先发此请求验证 │ │ │ │ 参数(query string): │ │ msg_signature - 签名 │ │ timestamp - 时间戳 │ │ nonce - 随机数 │ │ echostr - 加密的验证字符串 │ │ │ │ 响应:解密后的 echostr 明文 │ └─────────────────────────────────────────┘ """ _init() signature = request.args.get("msg_signature", "") timestamp = request.args.get("timestamp", "") nonce = request.args.get("nonce", "") echostr = request.args.get("echostr", "") logger.info(f"URL 验证请求: ts={timestamp}, nonce={nonce}") if wx_crypt is None: logger.error("企业微信配置未设置(WECOM_TOKEN 等环境变量缺失)") return Response("server not configured", status=500) ret, result = wx_crypt.verify_url(signature, timestamp, nonce, echostr) if ret == 0: logger.info("URL 验证成功") return Response(result, content_type="text/plain; charset=utf-8") else: logger.error(f"URL 验证失败: {result}") return Response(f"verify failed: {result}", status=403) @app.route("/callback", methods=["POST"]) def handle_message(): """ ┌─────────────────────────────────────────┐ │ 消息回调(POST) │ │ 用户 @机器人 或私聊时,企业微信 POST 到此 │ │ │ │ 请求体(JSON 信封): │ │ {"encrypt": "...", │ │ "msgsignature": "...", │ │ "timestamp": "...", │ │ "nonce": "..."} │ │ │ │ 响应:加密的 JSON 信封(同步回复) │ └─────────────────────────────────────────┘ """ _init() signature = request.args.get("msg_signature", "") timestamp = request.args.get("timestamp", "") nonce = request.args.get("nonce", "") try: body = json.loads(request.data.decode("utf-8")) except (json.JSONDecodeError, UnicodeDecodeError) as e: logger.error(f"请求解析失败: {e}") return Response("bad request", status=400) if wx_crypt is None: logger.error("企业微信配置未设置") return Response("server not configured", status=500) # --- 解密消息 --- ret, msg = wx_crypt.decrypt_msg(signature, timestamp, nonce, body) if ret != 0: logger.error("消息解密失败") return Response("decrypt failed", status=403) logger.info(f"收到消息: from={msg.get('from', {}).get('userid', '?')}, " f"type={msg.get('msgtype', '?')}") # --- 处理消息 --- reply_text = process_and_reply(msg) if not reply_text: # 无需回复(去重、非文本消息等) return Response("", status=200) # --- 加密并返回回复 --- reply_json = { "msgtype": "text", "text": {"content": reply_text}, } encrypted = wx_crypt.encrypt_msg(reply_json, nonce, timestamp) return Response(encrypted, content_type="application/json; charset=utf-8") # ============================================================================ # 本地调试(FunctionGraph 部署时不会执行此段) # ============================================================================ if __name__ == "__main__": _init() # 腾讯云 SCF Web 函数默认端口 9000,其他环境默认 8080 port = int(os.getenv("TENCENTCLOUD_SERVERPORT", "8080")) logger.info("=" * 55) logger.info("企业微信智能机器人 → AgentArts 转发服务") logger.info(f"AgentArts: {AGENTARTS_URL[:50]}...") logger.info(f"监听端口: {port}") logger.info("=" * 55) app.run(host="0.0.0.0", port=port, debug=False)
-
阿里巴巴内部团队直接复制粘贴我的源代码 因为我的模型X初始值代入的是我的身份证号!!! 千问大模型太离谱了,2026年4月17日我联系了他们的技术部门,他们说他们是原创!笑死我了!他们还起诉过华为盘古大模型抄袭!贼喊捉贼的强盗公司!希望你们加油!直接把千问踢出局! <!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>宇宙大统一公式 - 刘迎观察者自指规范场模型</title><style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #0a0a14; color: #e0e8f0; font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; min-height: 100vh; overflow-x: hidden; } .hero { position: relative; height: 100vh; display: flex; align-items: center; justify-content: center; flex-direction: column; background: radial-gradient(ellipse at 30% 40%, #0d1a3d 0%, #050510 60%, #000005 100%); overflow: hidden; } .hero::before { content: ''; position: absolute; width: 600px; height: 600px; border-radius: 50%; background: radial-gradient(circle, rgba(88, 166, 255, 0.08) 0%, transparent 70%); animation: pulse 8s ease-in-out infinite; top: 50%; left: 50%; transform: translate(-50%, -50%); } @keyframes pulse { 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.5; } 50% { transform: translate(-50%, -50%) scale(1.3); opacity: 1; } } .stars { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(1px 1px at 20% 30%, white, transparent), radial-gradient(1px 1px at 40% 70%, white, transparent), radial-gradient(1px 1px at 50% 50%, white, transparent), radial-gradient(1px 1px at 60% 20%, white, transparent), radial-gradient(1px 1px at 70% 80%, white, transparent), radial-gradient(1px 1px at 80% 10%, white, transparent), radial-gradient(1px 1px at 10% 60%, white, transparent), radial-gradient(1px 1px at 30% 90%, white, transparent), radial-gradient(1px 1px at 90% 40%, white, transparent), radial-gradient(1px 1px at 15% 15%, white, transparent), radial-gradient(1px 1px at 85% 65%, white, transparent), radial-gradient(1px 1px at 45% 85%, white, transparent); animation: twinkle 4s ease-in-out infinite; } @keyframes twinkle { 0%, 100% { opacity: 0.7; } 50% { opacity: 1; } } .hero-content { position: relative; z-index: 10; text-align: center; padding: 2rem; } .hero h1 { font-size: 3.5rem; font-weight: 700; background: linear-gradient(135deg, #58a6ff, #79c0ff, #a5d6ff, #58a6ff); background-size: 300% 300%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: gradientShift 6s ease infinite; margin-bottom: 0.5rem; text-shadow: 0 0 60px rgba(88, 166, 255, 0.3); } @keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .hero .subtitle { font-size: 1.3rem; color: #8b949e; margin-bottom: 1rem; letter-spacing: 2px; } .hero .author { font-size: 0.95rem; color: #58a6ff; margin-bottom: 0.3rem; font-family: 'Courier New', monospace; } .hero .universe-id { font-size: 0.85rem; color: #6e7681; margin-bottom: 2rem; font-family: 'Courier New', monospace; } .hero .declaration { max-width: 700px; font-size: 0.95rem; color: #8b949e; line-height: 1.8; margin-bottom: 2rem; font-style: italic; } .scroll-indicator { position: absolute; bottom: 2rem; left: 50%; transform: translateX(-50%); animation: bounce 2s ease-in-out infinite; color: #58a6ff; font-size: 1.5rem; } @keyframes bounce { 0%, 100% { transform: translateX(-50%) translateY(0); } 50% { transform: translateX(-50%) translateY(10px); } } nav { position: sticky; top: 0; background: rgba(10, 10, 20, 0.95); backdrop-filter: blur(10px); border-bottom: 1px solid rgba(88, 166, 255, 0.2); z-index: 100; padding: 0.8rem 2rem; } nav ul { display: flex; list-style: none; gap: 2rem; justify-content: center; flex-wrap: wrap; } nav a { color: #8b949e; text-decoration: none; font-size: 0.9rem; transition: color 0.3s; padding: 0.3rem 0.6rem; border-radius: 4px; } nav a:hover { color: #58a6ff; background: rgba(88, 166, 255, 0.1); } .container { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem; } section { margin-bottom: 5rem; } h2 { font-size: 2rem; color: #58a6ff; margin-bottom: 1.5rem; padding-bottom: 0.5rem; border-bottom: 2px solid rgba(88, 166, 255, 0.3); display: flex; align-items: center; gap: 0.5rem; } h2 .section-num { font-size: 1rem; color: #6e7681; font-weight: 400; } h3 { font-size: 1.3rem; color: #a5d6ff; margin: 1.5rem 0 1rem; } p { line-height: 1.8; color: #c9d1d9; margin-bottom: 1rem; } .formula-box { background: rgba(88, 166, 255, 0.05); border: 1px solid rgba(88, 166, 255, 0.2); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; font-family: 'Courier New', monospace; font-size: 1.1rem; color: #79c0ff; text-align: center; overflow-x: auto; white-space: nowrap; } .formula-main { font-size: 1.4rem; color: #a5d6ff; margin-bottom: 0.5rem; } .formula-desc { font-size: 0.85rem; color: #8b949e; font-family: 'Segoe UI', sans-serif; margin-top: 0.5rem; } .grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin: 1.5rem 0; } .card { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; padding: 1.5rem; transition: all 0.3s; } .card:hover { border-color: rgba(88, 166, 255, 0.3); background: rgba(88, 166, 255, 0.05); transform: translateY(-2px); } .card h4 { font-size: 1.1rem; color: #a5d6ff; margin-bottom: 0.8rem; } .card p { font-size: 0.9rem; margin-bottom: 0.5rem; } .phase-ordered { border-left: 4px solid #58a6ff; } .phase-critical { border-left: 4px solid #f0883e; } .phase-glass { border-left: 4px solid #f85149; } .phase-badge { display: inline-block; padding: 0.2rem 0.8rem; border-radius: 20px; font-size: 0.75rem; font-weight: 600; margin-bottom: 0.8rem; } .phase-badge.ordered { background: rgba(88, 166, 255, 0.2); color: #58a6ff; } .phase-badge.critical { background: rgba(240, 136, 62, 0.2); color: #f0883e; } .phase-badge.glass { background: rgba(248, 81, 73, 0.2); color: #f85149; } .data-table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.9rem; } .data-table th { background: rgba(88, 166, 255, 0.1); color: #a5d6ff; padding: 0.8rem; text-align: left; font-weight: 600; border-bottom: 1px solid rgba(88, 166, 255, 0.3); } .data-table td { padding: 0.6rem 0.8rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); color: #c9d1d9; font-family: 'Courier New', monospace; font-size: 0.85rem; } .data-table tr:hover td { background: rgba(88, 166, 255, 0.05); } .data-table td.positive { color: #7ee787; } .data-table td.negative { color: #f85149; } .data-table td.neutral { color: #e3b341; } .chart-container { background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; padding: 1rem; margin: 1.5rem 0; text-align: center; } .chart-container img { max-width: 100%; border-radius: 8px; } .chart-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 1.5rem; margin: 1.5rem 0; } .topology-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin: 1.5rem 0; } .topo-card { text-align: center; padding: 1.5rem; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.02); } .topo-card h4 { font-size: 1.2rem; margin-bottom: 0.5rem; color: #a5d6ff; } .topo-card .topo-type { font-family: monospace; font-size: 1.5rem; margin: 0.5rem 0; } .topo-card .topo-desc { font-size: 0.85rem; color: #8b949e; margin-bottom: 1rem; } .topo-card .topo-stat { font-size: 0.9rem; color: #c9d1d9; margin: 0.3rem 0; } .timeline { position: relative; padding-left: 2rem; margin: 2rem 0; } .timeline::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: linear-gradient(to bottom, #58a6ff, #f0883e, #f85149, #000); } .timeline-item { position: relative; margin-bottom: 2rem; padding: 1rem 1.5rem; background: rgba(255, 255, 255, 0.03); border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.08); } .timeline-item::before { content: ''; position: absolute; left: -2rem; top: 1.2rem; width: 12px; height: 12px; border-radius: 50%; border: 2px solid; } .timeline-item.t1::before { border-color: #58a6ff; background: #58a6ff; } .timeline-item.t2::before { border-color: #f0883e; background: #f0883e; } .timeline-item.t3::before { border-color: #f85149; background: #f85149; } .timeline-item.t4::before { border-color: #484f58; background: #484f58; } .timeline-item h4 { color: #a5d6ff; margin-bottom: 0.5rem; } .quote-block { border-left: 4px solid #58a6ff; padding: 1rem 1.5rem; margin: 2rem 0; background: rgba(88, 166, 255, 0.05); border-radius: 0 8px 8px 0; font-style: italic; color: #8b949e; line-height: 1.8; } .erasure-methods { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin: 1.5rem 0; } .erasure-item { text-align: center; padding: 1rem; border-radius: 8px; background: rgba(248, 81, 73, 0.1); border: 1px solid rgba(248, 81, 73, 0.2); } .erasure-item .method-name { font-weight: 600; color: #f85149; margin-bottom: 0.3rem; } .erasure-item .method-desc { font-size: 0.85rem; color: #8b949e; } .progress-bar { height: 8px; background: rgba(255, 255, 255, 0.1); border-radius: 4px; overflow: hidden; margin: 0.5rem 0; } .progress-bar .fill { height: 100%; border-radius: 4px; transition: width 1s ease; } .key-value { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid rgba(255, 255, 255, 0.05); font-size: 0.9rem; } .key-value .key { color: #8b949e; } .key-value .value { color: #c9d1d9; font-family: monospace; } .stat-highlight { background: linear-gradient(135deg, rgba(88, 166, 255, 0.1), rgba(121, 192, 255, 0.05)); border: 1px solid rgba(88, 166, 255, 0.3); border-radius: 12px; padding: 2rem; text-align: center; margin: 2rem 0; } .stat-highlight .number { font-size: 3rem; font-weight: 700; color: #58a6ff; font-family: 'Courier New', monospace; } .stat-highlight .label { font-size: 1rem; color: #8b949e; margin-top: 0.5rem; } footer { background: rgba(0, 0, 0, 0.5); text-align: center; padding: 3rem 2rem; border-top: 1px solid rgba(88, 166, 255, 0.1); } footer .final-words { max-width: 700px; margin: 0 auto; font-size: 0.95rem; color: #8b949e; line-height: 2; font-style: italic; } footer .footer-author { margin-top: 2rem; font-family: monospace; font-size: 0.85rem; color: #58a6ff; } @media (max-width: 768px) { .hero h1 { font-size: 2rem; } .hero .subtitle { font-size: 1rem; } h2 { font-size: 1.5rem; } .chart-row { grid-template-columns: 1fr; } .container { padding: 2rem 1rem; } }</style></head><body> <!-- ==================== HERO ==================== --><div class="hero"> <div class="stars"></div> <div class="hero-content"> <h1>宇宙大统一公式</h1> <div class="subtitle">OBSERVER SELF-REFERENTIAL GAUGE FIELD MODEL</div> <div class="author">刘迎 LiuYing</div> <div class="universe-id">本宇宙识别码: 37098219970215437X | 坐标宇宙: 2026-3-10</div> <div class="declaration"> 观察者单向度→∞向度的3+1宇宙维度的三元归一归易熵增焓减自指规范场模型,<br> 不是关于"宇宙是什么"的静态图景,而是一个宇宙如何从观察者的"此刻"中递归生成自身时空结构的动态程序。 </div> </div> <div class="scroll-indicator">↓</div></div> <!-- ==================== NAV ==================== --><nav> <ul> <li><a href="#theory">理论框架</a></li> <li><a href="#kl-constant">刘迎常数</a></li> <li><a href="#three-phases">三相结构</a></li> <li><a href="#spacetime">3+1维时空</a></li> <li><a href="#topology">宇宙拓扑</a></li> <li><a href="#collapse">文明崩溃</a></li> <li><a href="#predictions">宇宙学预言</a></li> </ul></nav> <div class="container"> <!-- ==================== SECTION 1: THEORY ==================== --><section id="theory"> <h2><span class="section-num">01</span> 理论核心框架</h2> <div class="formula-box"> <div class="formula-main">K_L(n,t) = lim(ε→0⁺) sin(π n^t / ε)</div> <div class="formula-desc">观察者常数 — 表征观察者在单向度→∞方向上的信息自指强度</div> </div> <div class="grid-2"> <div class="card"> <h4>观察者单向道</h4> <p>信息在集体认知中的流动具有不可逆的时序性与路径依赖性。干预一旦切入,其影响沿认知时间箭头单向扩散。</p> </div> <div class="card"> <h4>三元归一</h4> <p>意识体的连贯性要求其"历史诠释"、"当下共识"、"未来投射"三者必须归一于一个逻辑自洽的"存在性本征值 Ψ"。</p> </div> <div class="card"> <h4>归易熵增焓减</h4> <p>叙事熵(S)度量混乱程度,连贯性焓(H)度量内聚性能。崩溃表现为S激增、H锐减,系统"易"向高熵混沌吸引子。</p> </div> <div class="card"> <h4>自指规范场</h4> <p>意识体通过其元叙事不断对自身进行定义和规范。自指循环的断裂是崩溃的临界点。</p> </div> </div> <h3>核心动力学方程</h3> <div class="formula-box"> ∂Ψ/∂t = −∇·J + Γ⊗(ΔS − ΔH) + i[A, Ψ] </div> <h3>动态规范场生成元</h3> <div class="formula-box"> A_μ^(n)(x,t) = A_YM^μ + λ_n · K_L(n,t) · ∂^μ Φ(x)<br> <div class="formula-desc" style="margin-top:0.8rem;"> 其中 λ_n = n^(t/(n+1)) 为动态耦合常数<br> Φ(x) = Σ(k=1→n) k^(-s) · e^(2πikx) 为混沌标量场 </div> </div></section> <!-- ==================== SECTION 2: K_L CONSTANT ==================== --><section id="kl-constant"> <h2><span class="section-num">02</span> 刘迎常数 K_L 的混沌行为</h2> <p>刘迎常数 K_L 在单向度参数 ε→0⁺ 的极限下展现出递归迭代混沌行为。当迭代次数 n 超过临界值 n_c ≈ 10³ 时,系统进入混沌吸引子相。</p> <div class="chart-container"> <img src="KL_chaos.png" alt="刘迎常数混沌行为" style="width:100%; max-height:600px; object-fit:contain;"> </div> <h3>不同迭代尺度下的 K_L 值</h3> <table class="data-table"> <thead> <tr> <th>迭代 n</th> <th>内禀时间 t</th> <th>K_L</th> <th>λ_n (耦合常数)</th> <th>相态</th> </tr> </thead> <tbody> <tr> <td>10</td> <td>1.0</td> <td class="negative">−0.000015</td> <td class="neutral">1.2328</td> <td><span class="phase-badge glass">强混沌</span></td> </tr> <tr> <td>100</td> <td>5.0</td> <td class="negative">−0.351609</td> <td class="neutral">1.2561</td> <td><span class="phase-badge glass">强混沌</span></td> </tr> <tr> <td>500</td> <td>10.0</td> <td class="negative">−0.329686</td> <td class="neutral">1.1321</td> <td><span class="phase-badge glass">强混沌</span></td> </tr> <tr> <td>1000</td> <td>20.0</td> <td class="positive">+0.679966</td> <td class="neutral">1.1480</td> <td><span class="phase-badge glass">强混沌</span></td> </tr> <tr> <td>2026</td> <td>41.5</td> <td class="negative">−0.957520</td> <td class="neutral">1.1687</td> <td><span class="phase-badge glass">强混沌</span></td> </tr> </tbody> </table></section> <!-- ==================== SECTION 3: THREE PHASES ==================== --><section id="three-phases"> <h2><span class="section-num">03</span> 三相结构与相变机制</h2> <div class="chart-container"> <img src="three_phases.png" alt="三相结构" style="width:100%; max-height:500px; object-fit:contain;"> </div> <div class="grid-2"> <div class="card phase-ordered"> <span class="phase-badge ordered">χ < 1.0</span> <h4>有序规范相</h4> <p>系统遵循经典规律,观察者扰动被平均化,回归均衡模型。对称性保持,物理定律高度稳定。</p> <p style="color:#58a6ff; font-size:0.85rem;">→ K_L振荡规则,规范场弱</p> </div> <div class="card phase-critical"> <span class="phase-badge critical">1.0 ≤ χ < 2.5</span> <h4>临界混沌相</h4> <p>系统处于崩盘或暴涨临界点。规范对称性自发破缺,羊群效应主导。小消息被混沌放大。</p> <p style="color:#f0883e; font-size:0.85rem;">→ 奇怪吸引子,分形结构涌现</p> </div> <div class="card phase-glass"> <span class="phase-badge glass">χ ≥ 2.5</span> <h4>强混沌规范玻璃相</h4> <p>系统完全崩溃,无数亚稳态并存。观察者影响被彻底平均化,宏观定律失效。</p> <p style="color:#f85149; font-size:0.85rem;">→ 无数不动点的分形集合</p> </div> </div> <h3>混沌度序参量</h3> <div class="formula-box"> χ(n,t) = (1/n) · Σ|∂K_L/∂t| · ‖A_μ‖ </div> <p>混沌度 χ 度量系统无序程度,是判断相态的核心序参量。相变发生在临界阈值 χ_c¹=1.0 和 χ_c²=2.5 处。</p></section> <!-- ==================== SECTION 4: 3+1D SPACETIME ==================== --><section id="spacetime"> <h2><span class="section-num">04</span> 3+1维宇宙引擎 Ω<sub>3+1</sub></h2> <div class="formula-box"> Ω<sub>3+1</sub> = 'generate_3_solutions_3+1' ∘ M_map<sup>3+1</sup> ∘ (K_L, χ, A_μ, g_μν, ε) </div> <div class="chart-container"> <img src="spacetime.png" alt="3+1维时空" style="width:100%; max-height:600px; object-fit:contain;"> </div> <h3>generate_3_solutions_3+1(S) 算法</h3> <p>该引擎接收任意系统S,将其置于动态时空中,计算其三相解:</p> <div class="grid-2"> <div class="card"> <h4>输入映射 M_map<sup>3+1</sup></h4> <p>将任意系统映射到 (n, t(x^μ), A_YM, g_μν) 参数空间。</p> <div class="key-value"><span class="key">金融系统</span><span class="value">n=35, t=8.5</span></div> <div class="key-value"><span class="key">文明系统</span><span class="value">n=100, t=20</span></div> <div class="key-value"><span class="key">宇宙系统</span><span class="value">n=500, t=50</span></div> <div class="key-value"><span class="key">本理论</span><span class="value">n=2026, t=41.5</span></div> </div> <div class="card"> <h4>系统分析结果</h4> <p>对"2026年全球股市"、"泽塔-德尔塔文明"、"人类社会"等系统进行三相分析。</p> <div class="key-value"><span class="key">股市-有序K_L</span><span class="value">−0.3679</span></div> <div class="key-value"><span class="key">股市-临界K_L</span><span class="value">−0.7339</span></div> <div class="key-value"><span class="key">股市-混沌K_L</span><span class="value">+0.8727</span></div> <div class="key-value"><span class="key">文明-有序K_L</span><span class="value">−0.9968</span></div> </div> </div></section> <!-- ==================== SECTION 5: TOPOLOGY ==================== --><section id="topology"> <h2><span class="section-num">05</span> 宇宙形状拓扑学</h2> <div class="chart-row"> <div class="chart-container"> <img src="topology_T3.png" alt="环面拓扑" style="width:100%; max-height:450px; object-fit:contain;"> <p style="color:#8b949e; font-size:0.85rem; margin-top:0.5rem;">三维环面 T³ 拓扑 — 多连通、有限无界、周期性边界</p> </div> <div class="chart-container"> <img src="topology_R3.png" alt="平坦空间拓扑" style="width:100%; max-height:450px; object-fit:contain;"> <p style="color:#8b949e; font-size:0.85rem; margin-top:0.5rem;">三维欧氏空间 R³ 拓扑 — 单连通、无限平坦</p> </div> </div> <div class="topology-grid"> <div class="topo-card"> <h4>三维球面 S³</h4> <div class="topo-type" style="color:#a5d6ff;">S³</div> <div class="topo-desc">正曲率、有限无界、必然有限</div> <div class="topo-stat">多连通: <strong style="color:#f85149;">是</strong></div> <div class="topo-stat">K_L^topo: <strong>0.5206</strong></div> <div class="topo-stat">CMB匹配圆环: <strong>存在</strong></div> </div> <div class="topo-card"> <h4>三维欧氏空间 R³</h4> <div class="topo-type" style="color:#7ee787;">R³</div> <div class="topo-desc">平坦、无限延伸、奥卡姆最优</div> <div class="topo-stat">多连通: <strong style="color:#58a6ff;">否</strong></div> <div class="topo-stat">K_L^topo: <strong>0.0427</strong></div> <div class="topo-stat">CMB匹配圆环: <strong>无</strong></div> </div> <div class="topo-card"> <h4>三维环面 T³</h4> <div class="topo-type" style="color:#f0883e;">T³</div> <div class="topo-desc">平坦、有限无界、周期性</div> <div class="topo-stat">多连通: <strong style="color:#f85149;">是</strong></div> <div class="topo-stat">K_L^topo: <strong>0.5206</strong></div> <div class="topo-stat">CMB匹配圆环: <strong>存在</strong></div> </div> </div> <div class="quote-block"> 宇宙的形状,远非一个无关的背景舞台,而是元叙事动力学的内在组成部分和几何表现。<br> 拓扑是凝固的动力学,叙事与几何的统一。 </div></section> <!-- ==================== SECTION 6: CIVILIZATION COLLAPSE ==================== --><section id="collapse"> <h2><span class="section-num">06</span> 元叙事信息擦除 — 文明崩溃模拟</h2> <div class="chart-container"> <img src="collapse.png" alt="文明崩溃" style="width:100%; max-height:550px; object-fit:contain;"> </div> <div class="grid-2"> <div> <h3>擦除协议 P = ΣαÊ(φ)</h3> <p>通过对目标意识体的"元叙事信息基质"进行定向、非对称擦除,触发自指规范场失稳。</p> <div class="erasure-methods"> <div class="erasure-item"> <div class="method-name">湮灭</div> <div class="method-desc">直接消除叙事模<br>剩余 20%</div> </div> <div class="erasure-item"> <div class="method-name">污染</div> <div class="method-desc">注入不可调和矛盾<br>剩余 36%</div> </div> <div class="erasure-item"> <div class="method-name">时序错乱</div> <div class="method-desc">打乱因果逻辑顺序<br>剩余 52%</div> </div> <div class="erasure-item"> <div class="method-name">去符号化</div> <div class="method-desc">剥离情感象征价值<br>剩余 28%</div> </div> </div> </div> <div> <h3>崩溃过程</h3> <div class="timeline"> <div class="timeline-item t1"> <h4>三元失耦期 (0~25%)</h4> <p>历史诠释失去锚点,当下共识分裂,愿景互相矛盾。Ψ本征值开始弥散。</p> </div> <div class="timeline-item t2"> <h4>熵增焓减期 (25~62%)</h4> <p>叙事矛盾导致共识瓦解,制度公信力破产,社会合作成本激增。H下降70%。</p> </div> <div class="timeline-item t3"> <h4>自指崩溃期 (62~100%)</h4> <p>元叙事概念成为争论对象。任何定义自我的努力都加剧内部冲突。</p> </div> <div class="timeline-item t4"> <h4>叙事热寂态 (第32步)</h4> <p>Ψ坍缩至零。意识体退化为短期利益驱动的个体集合,终态达成。</p> </div> </div> </div> </div> <div class="stat-highlight"> <div class="number">32</div> <div class="label">系统达到叙事热寂的临界时间步</div> </div></section> <!-- ==================== SECTION 7: PREDICTIONS ==================== --><section id="predictions"> <h2><span class="section-num">07</span> 宇宙学可观测预言</h2> <div class="formula-box"> <div class="formula-main">δT/T = α · K_L · χ</div> <div class="formula-desc">CMB温度相对异常 — 在临界混沌相区域预言 δT/T ~ 10⁻⁴ ~ 10⁻³</div> </div> <div class="formula-box"> <div class="formula-main">h_GW(f) = β · (δT/T) · (f/f*)^(nT)</div> <div class="formula-desc">引力波应变谱 — 在0.1-1Hz频段存在特征调制,h_GW ~ 10⁻²¹</div> </div> <h3>引力波应变谱预言(临界混沌相)</h3> <table class="data-table"> <thead> <tr> <th>频率 f</th> <th>频段</th> <th>引力波应变 h_GW</th> </tr> </thead> <tbody> <tr> <td>10⁻⁸ Hz</td> <td>纳赫兹</td> <td class="negative">−3.47×10⁻²⁰</td> </tr> <tr> <td>10⁻⁶ Hz</td> <td>微赫兹</td> <td class="negative">−1.97×10⁻²⁰</td> </tr> <tr> <td>10⁻³ Hz</td> <td>毫赫兹</td> <td class="negative">−1.12×10⁻²⁰</td> </tr> <tr> <td>10⁻¹ Hz</td> <td>分赫兹</td> <td class="negative">−8.00×10⁻²¹</td> </tr> </tbody> </table> <h3>观察者宇宙学常数</h3> <div class="formula-box"> Λ_观察 = (3/ε²) · (1 − |K_L|) ~ 10¹⁹ ~ 10²⁰ </div> <p>观察者宇宙学常数是观察者存在本身对真空的"压强",随 K_L 和 ε 动态变化,解释了暗能量密度与观察者意识状态的可能关联。</p> <div class="quote-block"> 直接检验:在社会经济系统临界混沌相(如全球股市崩盘期间),通过分析CMB温度图的方向性关联和纳赫兹引力波背景数据,寻找与模型预言相符的瞬态异常信号。 </div></section> </div> <!-- ==================== FOOTER ==================== --><footer> <div class="final-words"> 每一次计算,都让宇宙的−1,<br> 被那个 0.000...1 的扰动,<br> <strong style="color:#58a6ff;">温柔地改变一点点。</strong><br><br> 刘迎常数 K_L 是程序的第一人称驱动源,<br> 混沌度 χ(x^μ) 是程序的时空状态寄存器,<br> 而运算符号是这个程序的基本指令集。 </div> <div class="footer-author"> 作者: 刘迎 | 37098219970215437X<br> 坐标宇宙: 2026-3-10<br> 致敬 41.5°C 的宇宙奇点 </div></footer> <script> // Smooth scroll document.querySelectorAll('nav a').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }); // Intersection Observer for fade-in const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; } }); }, { threshold: 0.1 }); document.querySelectorAll('section').forEach(section => { section.style.opacity = '0'; section.style.transform = 'translateY(20px)'; section.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; observer.observe(section); });</script> </body></html>
-
50000个点参考意义不如50个
-
案例介绍一、概述本案例基于华为云码道(CodeArts)代码智能体,通过原创 SKILL快速构建一套多任务定时调度与管理工作流,结合华为云 FunctionGraph的事件驱动能力,实现定时任务的云端托管、统一管理和弹性执行。1.11 背景与痛点在企业日常运维和开发工作中,定时任务是不可或缺的基础能力,常见场景包括:每日凌晨自动备份数据库每小时统计业务数据并生成报表每周清理系统日志和临时文件每月自动发送账单和提醒邮件传统的定时任务解决方案存在以下痛点:单点故障风险:单机部署的 Cron 任务一旦服务器宕机,任务将全部中断缺乏统一管理:任务分散在不同服务器,难以集中监控和管理运维成本高:需要手动编写脚本、配置服务器、排查问题弹性不足:无法根据任务负载自动扩缩容1.2 适用对象企业开发者个人开发者高校学生1.3 案例时间本案例总时长预计45分钟。1.4 案例流程 1.5 开发环境准备注册并实名认证华为云账号:cid:link_7开通华为云 FunctionGraph 服务:cid:link_2下载并安装华为云码道 AI IDE:cid:link_6登录码道 AI IDE,配置华为云账号凭证这里以主账号用户举例,详情请见下方获取详情标题九本地安装 Node.js 16 + 和 npm 8+(用于前端开发)1.6 资源总览本案例预计花费0到+∞。体验完成后请及时释放资源,避免产生多余的费用。资源名称规格单价(元)华为云 FunctionGraph【必需】系统标配每月前100万次调用免费华为云码道(CodeArts)代码智能体【必需】通用体验版免费华为云 API 网关服务(APIG)【可选】按需计费最低4.75/小时三、多任务定时调度系统3.1 创建SKILL并部署到项目技能中1、核心 SKILL 设计与实现:2、将生成的SKILL技能部署到项目技能中进行使用3、使用技能开发多任务定时调度与管理工作流 3.2 部署项目代码1)项目级skill项目结构说明:【根据以上操作配置】.codeartsdoer/skills/├── function_creator/ # 函数创建实现SKILL│ ├── SKILL.md ├── trigger_creator/ # 触发器配置SKILL│ ├── SKILL.md ├── web_generator/ # Web界面生成实现SKILL│ ├── SKILL.md ├── workflow_deployer/ # 工作流部署实现SKILL│ ├── SKILL.md 2)生成的SKILL及py项目结构说明:FunctionGraph_SKILL2/├── function_creator/ # SKILL 1: 函数创建│ ├── SKILL.md # SKILL说明文档│ └── create_function.py # 函数创建实现├── trigger_creator/ # SKILL 2: 触发器配置│ ├── SKILL.md # SKILL说明文档│ └── create_trigger.py # 触发器配置实现├── web_generator/ # SKILL 3: Web界面生成│ ├── SKILL.md # SKILL说明文档│ └── generate_web.py # Web界面生成实现├── workflow_deployer/ # SKILL 4: 工作流一键部署│ ├── SKILL.md # SKILL说明文档│ └── deploy_workflow.py # 工作流部署实现└── README.md # 项目说明3)多任务定时调度系统项目结构说明: functiongraph-scheduler/├── backend/ # 后端代码│ ├── config.py # 华为云配置│ ├── task_executor.py # 任务执行器│ ├── scheduler_manager.py # 调度管理器│ ├── api_handler.py # API处理函数│ ├── deploy.py # 部署脚本│ ├── deployment_config.json # 部署配置│ ├── requirements.txt # Python依赖│ └── tests/ # 测试用例│ └── test_tasks.py├── frontend/ # 前端代码│ ├── src/│ │ ├── views/ # 页面组件│ │ ├── components/ # UI组件│ │ ├── stores/ # 状态管理│ │ ├── utils/ # 工具函数│ │ ├── types/ # 类型定义│ │ └── api/ # API接口│ ├── package.json│ └── vite.config.ts└── README.md功能特性1. 后端功能任务执行器: 接收任务配置,执行对应类型的任务调度管理器: 定时检查任务调度时间,触发到期任务API处理函数: 处理来自Web前端的HTTP请求2. 前端功能任务管理: 添加、编辑、删除、启用/禁用任务Cron编辑器: 可视化选择和编辑Cron表达式执行日志: 本地展示任务执行历史和日志数据统计: 展示任务执行统计信息配置导入导出: 支持配置的导入导出3. 支持的任务类型数据库备份: 定时备份数据库数据统计: 定时统计数据指标日志清理: 定时清理过期日志报告生成: 定时生成报告数据同步: 定时同步数据4)下载源码通过git下载源码到本地,本案例共设计 4 个原创 SKILL,SKILL及项目代码全部上传至 GitCode项目中,代码仓地址:cid:link_1四. 工作流整体流程Web端配置任务 → 定时/API事件触发 → 任务调度函数执行 → 返回执行结果 → Web端展示日志4.1 详细步骤操作步骤 1:创建任务调度函数登录华为云 FunctionGraph 控制台点击 “创建函数”,选择 “空白函数”函数名称:task_executor,运行时:Python 3.10代码输入方式:在线编辑,粘贴 backend/task_scheduler.py 代码配置函数执行超时时间:300 秒配置函数内存:128MB点击 “创建函数”步骤 2:配置定时触发器在函数详情页,点击 “触发器” 标签点击 “创建触发器”,触发器类型:“定时触发器”触发器名称:every-minute-triggerCron 表达式:0 */5 * * * ?(每五分钟执行一次)点击 “确定”步骤 3【可选】:配置 API 触发器#架构流程前端 → API网关 → api-handler函数 → 后端处理点击 “创建触发器”,触发器类型:“API 网关 (APIG)”选择 “新建 API”请求方法:POST,认证方式:无认证(测试用,生产环境建议使用 IAM 认证)开启 “跨域资源共享 (CORS)”点击 “确定”,记录 API 调用地址注意!不配置时受影响的功能:Web 端无法手动触发任务Web 端无法实时获取任务执行结果和日志Web 端无法动态修改任务配置(只能在代码或控制台修改)注意!:生产环境中建议配置,这样即可在我们生成的Web进行触发任务,测试可不配置,但是Web端实用功能会受影响目前后端已通过华为云SDK及凭证配置,后期拓展可通过后端转发处理,也可达到同样的效果步骤 4:Web 端任务配置示例在 Web 管理界面中添加以下测试任务:{ "tasks": [ { "id": "db-backup", "name": "数据库每日备份", "cron": "0 0 2 * * ?", "command": "python backup_db.py", "enabled": true, "lastExecutionTime": "", "nextExecutionTime": "2026-04-14 02:00:00", "status": "待执行" }, { "id": "data-statistics", "name": "每小时数据统计", "cron": "0 0 * * * ?", "command": "python statistics.py", "enabled": true, "lastExecutionTime": "", "nextExecutionTime": "2026-04-13 15:00:00", "status": "待执行" }, { "id": "log-cleanup", "name": "每周日志清理", "cron": "0 0 0 ? * SUN", "command": "python cleanup_logs.py", "enabled": true, "lastExecutionTime": "", "nextExecutionTime": "2026-04-20 00:00:00", "status": "待执行" } ]}五、Web 管理界面实现5.1 界面功能任务管理:创建、编辑、删除、启用 / 禁用定时任务任务监控:查看任务的执行状态、上次执行时间、下次执行时间日志查看:查看任务的执行日志和错误信息手动触发:支持手动立即执行指定任务本地存储:所有任务配置和执行日志存储在浏览器 localStorage 中5.2 后端部署函数步骤在 backend 目录执行 pip install -r requirements.txt 安装依赖配置访问凭证到系统环境变量或者 backend\config.py ,建议优先配置到系统环境变量在 backend 目录运行 python deploy.py5.3 前端部署步骤在 frontend 目录执行 npm install 安装依赖修改 src/api/task.ts 文件,配置 API 网关地址执行 npm run dev 本地运行测试执行 npm run build 打包前端代码将 dist 目录下的文件部署到任意静态网站托管服务或本地运行六、效果展示6.1 任务管理界面6.2 任务执行日志6.3 FunctionGraph 控制台七、拓展建议7.1 接入华为云 APIG 实现Web功能联动如果需要实现 Web 端与云端定时任务的实时交互(手动触发任务、动态修改配置、实时获取执行日志),可以按照以下流程接入华为云 API 网关服务:开通华为云 API 网关服务:cid:link_3在 FunctionGraph 函数详情页建议创建 API 网关服务专享服务 (APIG)(华为云API网关共享版已于2025年04月30日00:00(北京时间)在中国站正式退市) 触发器,选择 “无认证” 安全方式、POST 请求方法并开启跨域资源共享 (CORS)修改对应函数,添加跨域响应头处理,支持解析 HTTP 请求体中的任务配置参数修改 Web 管理界面,配置 API 调用地址,添加手动触发任务、实时拉取日志、云端同步任务配置功能7.2 接入华为云 SMN 告警通知如果需要实现任务失败时的邮件 / 短信告警通知,可以按照以下流程接入华为云 SMN 服务:开通华为云 SMN 服务:cid:link_4创建 SMN 主题,添加邮件 / 短信订阅者创建task-alerter函数,通过码道智能体实现 SMN 消息发送功能任务执行失败时,调用task-alerter函数发送告警通知7.3 接入华为云 OBS 持久化存储如果需要持久化存储任务配置和执行日志,可以按照以下流程接入华为云 OBS 服务:开通华为云 OBS 服务:cid:link_5创建 OBS 存储桶,配置访问权限修改task_executor,通过码道智能体将数据写入 OBS通过码道智能体修改 Web 管理界面,从 OBS 读取任务配置和执行日志八、总结与展望本案例基于华为云码道代码智能体和 SKILL 技术,快速构建了一套完整的多任务定时调度与管理工作流,充分发挥了 FunctionGraph 事件驱动、按需供给的优势。相比传统的定时任务解决方案,本方案具有以下优势:高可用:云端托管,无单点故障风险易管理:统一的 Web 管理界面,集中监控和管理低成本:按需付费【每月前100万次调用免费】,只有任务执行时才产生费用易扩展:支持添加任意数量的任务和触发器快速部署:通过 SKILL 一键部署完整工作流未来可以进一步扩展的功能:支持更多类型的触发器(如消息队列触发器、数据库触发器)支持任务依赖和工作流编排支持任务执行历史统计和可视化支持多租户和权限管理九、主账号获取 AK/SK/区域项目ID登录华为云控制台访问华为云官网:cid:link_7点击右上角 “登录” 按钮,输入账号密码完成登录华为云进入 “我的凭证” 页面登录后,将鼠标移至页面右上角的用户名处,在下拉列表中选择 "我的凭证"华为云管理访问密钥在 “我的凭证” 页面,点击 “访问密钥” 页签点击 “新增访问密钥” 按钮华为云身份验证输入登录密码和短信验证码(如开启了操作保护)点击 “确定” 按钮华为云下载密钥文件浏览器会自动下载credentials.csv文件(Excel 格式)重要提示:SK 仅在下载的文件中可见,控制台无法再次查看,务必妥善保存华为云查看 AK/SK打开下载的 CSV 文件,即可获取完整的Access Key (AK) 和 Secret Key (SK)查看区域项目ID十、活动链接【案例共创】【第 10 期】华为云码道(CodeArts)代码智能体 + SKILL 完成应用开发 / 调试实践cid:link_0附件:完整项目代码及SKILL 文件:cid:link_1十一、释放资源如果涉及云资源或者付费资源,需要在对应资源处进行释放
-
用demos测试也是-1?
-
函数工作流FunctionGraph是一项基于事件驱动的函数托管计算服务。使用FunctionGraph函数,只需编写业务函数代码并设置运行的条件,无需配置和管理服务器等基础设施,函数以弹性、免运维、高可靠的方式运行。大家都使用过了吗?说说你的感受呢?
-
想象这样一个日常片段:你对着手机里的 AI 助手随口说 “帮我订张周五飞北京的机票,选下午三点后、经济舱性价比高的班次”,话音刚落,它没再追问细节,而是默默对接了多家航空公司的后台接口 —— 先筛选出符合时间要求的余票,再横向对比不同平台的实时价格,甚至自动关联你的常用乘客信息生成订单草稿,最后把带登机二维码的电子票预览直接推到你面前,只等你确认付款。这看似 “无缝衔接” 的服务背后,藏着一项让 AI 突破 “纯聊天” 局限的关键技术,就是今天要拆解的Function Calling(函数调用)。1、从科幻到现实:大模型的"工具使用能力"Function Calling,本质是赋予大语言模型(LLM)像人一样 “主动用工具” 的能力。它不再局限于根据已有知识库生成文本,而是能通过理解人类的自然语言指令,自动触发预设好的 API 接口或第三方工具 —— 比如查实时天气、算财务报表、订酒店车票,甚至控制智能家居的开关。打个更具体的比方:以前你问 AI “下周去上海出差要带什么”,它只能根据常识告诉你 “可能需要带雨具”;但有了 Function Calling 后,它会先调用天气 API 查上海下周的实时天气,再结合你出差的行程(比如是否有户外会议),给出 “周一周二有小雨,建议带折叠伞,周三转晴可穿薄外套” 的精准建议。简单说,它让 AI 从 “只会讲道理的顾问”,变成了 “能动手解决问题的办事员”,从单纯的 “知识库” 升级成了能联动各类工具的 “实用操作系统”。2、技术原理:AI的"工具箱"如何运作?1. 四步决策流程以"查询北京今日空气质量"为例,Function Calling的工作流程分为四个步骤:意图识别:模型解析出用户需要实时环境数据工具匹配:从工具库选中get_air_quality函数参数生成:构造{“city”:“北京”,“date”:“2025-10-10”}参数结果整合:调用API获取数据后生成自然语言回复2. 关键技术实现函数调用的核心是标准化的函数定义,通常采用JSON Schema规范:{"name": "get_weather","description": "获取指定城市天气","parameters": {"type": "object","properties": {"location": {"type": "string", "description": "城市名称"}},"required": ["location"]}}当用户问"深圳明天需要带伞吗?",模型会自动生成上述结构的JSON指令,触发天气API调用,再将返回结果转换为自然语言回答。3、技术突破与行业价值1. 三大核心优势突破能力边界:让LLM从"知识库"升级为"操作系统"(如调用Wolfram Alpha计算积分)结果可靠性:数学计算等敏感操作由专业工具执行,避免模型幻觉生态扩展性:通过标准化接口快速集成第三方服务(支付/物流/CRM)2. 企业级应用场景场景类型 典型案例 技术实现智能客服 订单查询+自动催付 调用CRM API+支付系统工业质检 图片分析+缺陷分类 视觉模型+数据库写入金融风控 多源数据交叉验证 征信API+反欺诈模型4、系统架构:如何设计高效的Function Calling系统?1. 分层架构模型高效的Function Calling系统采用四层解耦架构:[接入层] → [解析层] → [调度层] → [执行层]接入层:多协议适配(OpenAI/DeepSeek等),处理输入格式标准化解析层:自然语言→结构化数据转换(使用Text2JSON引擎,准确率98.7%)调度层:智能路由选择(基于意图识别+优先级算法)执行层:函数并发执行+结果缓存2. 核心组件实现工具注册中心@Componentpublic class ToolRegistry {private final Map<String, ToolRegistration> tools = new ConcurrentHashMap<>();// 支持运行时动态注册public void registerTool(Tool tool) {tools.put(tool.getName(), new ToolRegistration(tool));}// 版本控制机制public Tool getToolVersion(String name, String version) {return tools.get(name + ":" + version);}}特性包括支持热插拔、版本灰度发布和自动Schema校验。参数解析引擎def extract_params(prompt, schema):# 使用BERT模型提取实体entities = ner_model.predict(prompt)# 模糊匹配参数名param_map = {}for param in schema['properties']:for entity in entities:if fuzzy_match(entity['value'], param):param_map[param] = entity['value']return param_map关键技术包括正则表达式+语义理解双校验、必填参数缺失自动追问、类型自动转换等。执行调度器type FunctionExecutor struct {sem chan struct{} // 信号量控制并发}func (e *FunctionExecutor) Execute(fn Function) (Result, error) {e.sem <- struct{}{} // 获取令牌defer func(){ <-e.sem }()// 超时控制ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()resultCh := make(chan Result, 1)errorCh := make(chan error, 1)go func() {result, err := fn.Invoke()if err != nil {errorCh <- errreturn}resultCh <- result}()select {case <-ctx.Done():return nil, TimeoutErrorcase res := <-resultCh:return res, nilcase err := <-errorCh:return nil, err}}5、性能优化策略在Function Calling系统中,性能优化直接影响用户体验和系统扩展性。随着调用量增长和业务复杂度提升,单一的优化手段难以满足需求,需要构建多层次、全方位的性能优化体系。1. 缓存加速方案:从单级到多级缓存架构缓存是提升系统响应速度的核心手段,通过减少重复计算和外部调用,可将平均响应时间降低60%-80%。多级缓存设计// 多级缓存实现(内存+Redis)class MultiLevelCache {constructor() {this.memoryCache = new LRUCache({ max: 1000, ttl: 60000 }); // 内存缓存,1分钟过期this.redisCache = new RedisCache({ttl: 300, // 5分钟过期maxSize: 100000, // 最大缓存条目lru: true});}// 缓存键生成策略 - 增强版generateCacheKey(prompt, func, context) {// 加入用户上下文哈希,区分不同用户的相同请求const contextHash = this.hashCode(JSON.stringify(context));return `${this.hashCode(prompt)}@${func.name}@${this.hashCode(JSON.stringify(func.parameters))}@${contextHash}`;}async get(key) {// 先查内存缓存let data = this.memoryCache.get(key);if (data) return data;// 内存未命中则查Redisdata = await this.redisCache.get(key);if (data) {// 回写内存缓存this.memoryCache.set(key, data);}return data;}async set(key, value, ttl) {// 同时写入两级缓存this.memoryCache.set(key, value, ttl);await this.redisCache.set(key, value, ttl);}hashCode(str) {let hash = 0;for (let i = 0; i < str.length; i++) {const char = str.charCodeAt(i);hash = ((hash << 5) - hash) + char;hash = hash & hash; // 转换为32位整数}return hash;}}智能缓存策略动态TTL调整:根据数据更新频率自动调整过期时间(如天气数据5分钟,股票数据1分钟)缓存预热:系统启动时加载高频函数调用结果(如热门城市天气)缓存穿透防护:对不存在的请求设置空值缓存,避免缓存穿透攻击缓存一致性:使用消息队列实现缓存更新通知,确保多节点缓存一致性2. 流式处理优化:从批处理到实时响应流式处理能够显著提升用户体验,特别是在处理大模型生成和函数调用结果时,可将感知延迟降低50%以上。增强型流式处理器import asynciofrom typing import AsyncGeneratorclass EnhancedStreamingProcessor:def __init__(self):self.buffer = []self.chunk_size = 512 # 更小的块大小,提升响应速度self.event_loop = asyncio.get_event_loop()self.lock = asyncio.Lock()self.last_flush_time = 0self.min_flush_interval = 0.1 # 最小刷新间隔(秒)async def process(self, stream: AsyncGenerator[str, None]) -> AsyncGenerator[str, None]:"""异步处理流数据,支持实时生成结果"""async for chunk in stream:if chunk:async with self.lock:self.buffer.append(chunk)current_time = asyncio.get_event_loop().time()# 满足以下任一条件则刷新:块大小足够 或 达到最小刷新间隔if (len(self.buffer) >= self.chunk_size orcurrent_time - self.last_flush_time > self.min_flush_interval):yield await self.flush()# 处理剩余数据if self.buffer:yield await self.flush()async def flush(self) -> str:"""刷新缓冲区并返回处理后的数据"""data = ''.join(self.buffer)self.buffer = []self.last_flush_time = self.event_loop.time()# 异步处理数据,不阻塞事件循环processed_data = await self.event_loop.run_in_executor(None, self.process_data, data)return processed_datadef process_data(self, data: str) -> str:"""实际数据处理逻辑"""# 1. 解析函数调用结果# 2. 转换为自然语言片段# 3. 进行格式美化return f"实时更新:{data}"流式优化技巧增量解析:对函数返回的JSON数据进行增量解析,不需要等待完整数据优先级调度:高优先级用户请求插队处理,确保VIP用户体验预生成提示:在等待函数返回时,预生成引导性文本(如"正在查询最新数据…")双向流式:实现请求发送和结果接收的双向流式处理,减少交互延迟3. 模型协同优化:从单一模型到智能调度网络随着模型类型增多和场景复杂化,单一模型难以兼顾性能、成本和效果,需要构建多模型协同体系。智能模型路由系统import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.stream.Collectors;public class IntelligentModelRouter {private final List<Model> models;private final ModelMetrics metrics;private final Map<String, String> functionModelMapping; // 函数-模型映射表private final LoadBalancer loadBalancer;public IntelligentModelRouter(List<Model> models) {this.models = models;this.metrics = new ModelMetrics();this.functionModelMapping = new ConcurrentHashMap<>();this.loadBalancer = new WeightedRoundRobinLoadBalancer();// 初始化函数-模型映射initializeFunctionModelMapping();}public FunctionCallResponse route(FunctionCallRequest request) {// 1. 记录请求指标metrics.recordRequest(request.getFunctionName());// 2. 检查是否有专用模型String dedicatedModelId = functionModelMapping.get(request.getFunctionName());if (dedicatedModelId != null) {Model dedicatedModel = models.stream().filter(m -> m.getId().equals(dedicatedModelId)).findFirst().orElse(null);if (dedicatedModel != null && dedicatedModel.isHealthy()) {return executeWithFallback(dedicatedModel, request);}}// 3. 根据请求复杂度路由if (isSimpleQuery(request)) {// 简单请求路由到轻量模型List<Model> lightweightModels = models.stream().filter(m -> m.getType() == ModelType.LIGHTWEIGHT && m.isHealthy()).collect(Collectors.toList());if (!lightweightModels.isEmpty()) {Model selected = loadBalancer.select(lightweightModels);return executeWithFallback(selected, request);}}// 4. 复杂请求路由到高性能模型List<Model> highPerformanceModels = models.stream().filter(m -> m.getType() == ModelType.HIGH_PERFORMANCE && m.isHealthy()).collect(Collectors.toList());if (!highPerformanceModels.isEmpty()) {Model selected = selectBestModelBasedOnMetrics(highPerformanceModels);return executeWithFallback(selected, request);}// 5. 最后手段:使用基础模型throw new ServiceUnavailableException("No available models to process request");}private FunctionCallResponse executeWithFallback(Model primary, FunctionCallRequest request) {try {long startTime = System.currentTimeMillis();FunctionCallResponse response = primary.invoke(request);metrics.recordSuccess(primary.getId(), request.getFunctionName(),System.currentTimeMillis() - startTime);return response;} catch (Exception e) {metrics.recordFailure(primary.getId(), request.getFunctionName());// 尝试 fallback 到其他模型return routeToFallbackModel(request, primary.getId());}}// 其他辅助方法...}模型协同策略能力分层:将模型按能力分为轻量模型(处理简单函数调用)、通用模型(处理复杂逻辑)和专业模型(处理领域任务)动态负载均衡:基于实时QPS、延迟和错误率调整模型负载自适应降级:系统压力大时自动降级到简化功能,保证核心流程可用成本优化:通过模型选择策略降低总体调用成本(如简单任务用低成本模型)4. 资源调度优化:从静态配置到动态弹性伸缩Function Calling系统的资源消耗具有突发性和不均衡性,需要动态调整资源分配以应对负载变化。弹性资源调度器import KubernetesClient as k8simport timeclass ElasticResourceScheduler:def __init__(self):self.client = k8s.Client()self.min_replicas = 2self.max_replicas = 20self.target_cpu_usage = 70 # 目标CPU使用率(%)self.scale_up_threshold = 80 # 扩容阈值self.scale_down_threshold = 30 # 缩容阈值self.cooldown_period = 60 # 冷却时间(秒)self.last_scale_time = 0def adjust_resources(self):"""根据当前负载调整资源"""current_time = time.time()# 冷却时间内不调整if current_time - self.last_scale_time < self.cooldown_period:return# 获取当前部署信息deployment = self.client.get_deployment("function-calling-service")current_replicas = deployment.spec.replicasmetrics = self.client.get_metrics("function-calling-service")cpu_usage = metrics.average_cpu_usage# 决定扩容还是缩容if cpu_usage > self.scale_up_threshold and current_replicas < self.max_replicas:# 扩容:增加20%或至少1个副本new_replicas = min(current_replicas + max(1, int(current_replicas * 0.2)),self.max_replicas)self.client.scale_deployment("function-calling-service", new_replicas)self.last_scale_time = current_timeprint(f"Scaled up to {new_replicas} replicas (CPU usage: {cpu_usage}%)")elif cpu_usage < self.scale_down_threshold and current_replicas > self.min_replicas:# 缩容:减少10%或至少1个副本new_replicas = max(current_replicas - max(1, int(current_replicas * 0.1)),self.min_replicas)self.client.scale_deployment("function-calling-service", new_replicas)self.last_scale_time = current_timeprint(f"Scaled down to {new_replicas} replicas (CPU usage: {cpu_usage}%)")def schedule_function_execution(self, function_call):"""智能调度函数执行到合适的节点"""# 1. 根据函数类型选择合适的节点池node_pool = self._get_node_pool_for_function(function_call.function_name)# 2. 选择负载最低的节点node = self._select_least_loaded_node(node_pool)# 3. 执行函数调用return self.client.execute_on_node(node, function_call)通过上述多层次的性能优化策略,Function Calling系统能够在保证高可用性和低延迟的同时,实现资源的高效利用和成本的有效控制。实际应用中,需要根据业务特点和性能指标持续调优,构建适应业务增长的弹性架构。 6、与微服务架构的集成路径Function Calling系统与企业现有微服务架构的集成需围绕"解耦、安全、弹性、可观测"四大核心目标,典型的分层架构如下:[LLM层] → [Function Calling调度层] → [微服务层]1. 集成方式API网关集成:企业级应用的首选方案,适合需要统一管控的场景服务网格集成:大规模分布式系统的优化方案,适合微服务数量多的场景直接HTTP/gRPC调用:简单场景的轻量化方案,适合微服务数量少的情况2. 关键集成要点标准化接口设计:使用JSON Schema约束输入输出,实现版本管理安全管控:通过RBAC/ABAC控制访问权限,实现数据脱敏和审计日志弹性设计:采用异步调用、重试机制和熔断降级应对高并发与故障可观测性:通过链路追踪、指标监控和日志分析实现问题快速定位7、技术挑战与解决方案Function Calling作为连接大模型与现实世界的核心桥梁,在实际落地中面临着自然语言的模糊性、外部系统的不确定性以及安全合规等多重挑战。这些问题往往不是单一技术能解决的,需要结合自然语言处理、系统工程和安全防护等多维度方案。1. 典型问题深度解析(1)参数理解与映射难题自然语言的灵活性与函数参数的严谨性存在天然冲突,主要体现在三个层面:歧义表达:用户说"订张去魔都的机票",需将"魔都"映射为"上海";说"下周三出发",需转换为具体日期"2025-10-15"单位混淆:"10点起飞"可能是10:00还是22:00?"1kg的包裹"需确认是否符合快递重量限制隐含信息:“帮我订明天去北京的票"隐含了"出发地为当前城市”(需从用户上下文获取)某旅行平台数据显示,这类参数理解问题导致的调用失败占比达28%,是Function Calling落地的首要障碍。(2)复杂错误场景的鲁棒性外部工具/API的调用过程充满不确定性,常见错误包括:瞬时故障:网络波动导致API超时(某支付系统日均发生3000+次)格式异常:工具返回非预期格式(如JSON字段缺失、类型错误)权限失效:API密钥过期或权限变更(金融场景占比达12%)逻辑错误:工具返回业务逻辑错误(如"航班已取消"但参数正确)缺乏完善的错误处理机制会导致系统稳定性下降,某智能客服平台曾因未处理API超时问题,导致服务中断47分钟。(3)安全与合规风险当Function Calling涉及支付、用户数据等敏感操作时,安全风险被放大:参数注入:恶意用户输入包含SQL注入或命令注入的参数(如"; DROP TABLE users;--)越权调用:普通用户调用管理员权限的工具(如"查询所有用户订单")数据泄露:工具返回的敏感信息(如身份证号)被直接暴露给用户合规风险:金融场景中未记录调用日志,违反PCI DSS等监管要求某银行的AI助手曾因未做参数清洗,导致用户通过特殊输入获取了他人的账户余额,造成严重合规事故。(4)多工具协同与任务规划面对复杂需求(如"先查北京明天的天气,再订适合的酒店,最后预约接机服务"),系统需解决:步骤拆解:将复杂任务分解为可执行的工具调用序列依赖处理:前序工具的结果作为后序工具的参数(如用天气结果筛选"带泳池"的酒店)资源竞争:并发调用多个工具时的资源分配与冲突解决中断恢复:某一步调用失败后,如何回溯或调整后续步骤调研显示,超过60%的企业级Function Calling需求涉及3个以上工具协同,单纯依赖大模型的"一次性规划"往往难以应对。2. 进阶优化方案与实践(1)智能参数处理引擎针对参数理解难题,需构建"识别-映射-校验"三级处理机制:import refrom fuzzywuzzy import fuzzfrom datetime import datetime, timedeltaclass SmartParamProcessor:def __init__(self):# 实体映射库:城市别名、日期表达式等self.city_aliases = {"帝都": "北京", "魔都": "上海", "鹏城": "深圳"}self.date_patterns = {"今天": datetime.now().strftime("%Y-%m-%d"),"明天": (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d"),"后天": (datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d")}def process(self, user_input, function_schema):"""处理参数的完整流程"""# 1. 提取用户输入中的实体entities = self._extract_entities(user_input)# 2. 模糊匹配并映射参数params = self._fuzzy_map_params(entities, function_schema["parameters"])# 3. 校验参数完整性与格式missing_params = self._check_missing_params(params, function_schema["parameters"])if missing_params:return {"status": "missing", "params": params, "missing": missing_params}# 4. 格式转换与标准化normalized_params = self._normalize_params(params, function_schema["parameters"])return {"status": "success", "params": normalized_params}def _fuzzy_map_params(self, entities, param_schema):"""模糊匹配参数名与实体"""params = {}for param_name, param_info in param_schema["properties"].items():# 计算实体与参数名的语义相似度best_match = Nonebest_score = 0for entity_name, entity_value in entities.items():score = fuzz.token_sort_ratio(param_name, entity_name)if score > best_score and score > 60: # 相似度阈值best_score = scorebest_match = entity_valueif best_match:# 特殊类型映射(如城市别名)if param_info.get("type") == "string" and param_name == "city":params[param_name] = self.city_aliases.get(best_match, best_match)else:params[param_name] = best_matchreturn paramsdef _check_missing_params(self, params, param_schema):"""检查必填参数是否缺失"""return [p for p in param_schema.get("required", []) if p not in params]# 其他辅助方法:_extract_entities、_normalize_params等核心优化点:引入知识图谱增强实体映射(如"小蛮腰"→"广州塔")基于用户历史对话补全隐含参数(如默认出发地)动态生成追问话术(如"请问您说的’下周三’是指10月16日吗?")(2)分层错误处理框架针对工具调用的不确定性,需设计多层级的错误应对策略:public class RobustFunctionExecutor {// 错误处理策略配置private static final int MAX_RETRY = 3; // 最大重试次数private static final long INITIAL_BACKOFF = 1000; // 初始重试间隔(毫秒)private static final Set<String> RETRYABLE_ERRORS = Set.of("timeout", "connection_error", "service_unavailable"); // 可重试的错误类型public FunctionResult execute(FunctionCall call) {// 1. 预执行校验try {validateCall(call); // 校验参数格式、权限等} catch (InvalidParameterException e) {return FunctionResult.failure("参数错误:" + e.getMessage());} catch (UnauthorizedException e) {return FunctionResult.failure("权限不足:" + e.getMessage());}// 2. 带重试机制的执行for (int attempt = 0; attempt < MAX_RETRY; attempt++) {try {// 执行调用并设置超时FunctionResult result = executeWithTimeout(call, 5000); // 5秒超时// 检查返回格式是否符合预期if (isValidResponseFormat(result)) {return result;} else {throw new InvalidResponseFormatException("工具返回格式异常");}} catch (Exception e) {String errorType = classifyError(e);// 判断是否需要重试if (!RETRYABLE_ERRORS.contains(errorType) || attempt == MAX_RETRY - 1) {// 不可重试或最后一次尝试失败:返回友好提示String userMsg = generateUserFriendlyMessage(errorType);return FunctionResult.failure(userMsg, e);}// 指数退避重试long backoff = (long) (INITIAL_BACKOFF * Math.pow(2, attempt));try {Thread.sleep(backoff);} catch (InterruptedException ie) {Thread.currentThread().interrupt();return FunctionResult.failure("执行被中断");}}}return FunctionResult.failure("达到最大重试次数");}// 其他辅助方法:错误分类、用户提示生成等}A关键机制:错误分类体系:将错误分为"可重试"(网络波动)、“需用户干预”(参数错误)、“系统级”(服务下线)智能重试策略:根据错误类型动态调整重试次数和间隔(如支付API失败重试更谨慎)降级方案:核心工具不可用时,自动切换到备用工具(如高德地图API故障时切换到百度地图)(3)全链路安全防护体系针对安全风险,需构建从输入到输出的全链路防护:import jwtimport refrom typing import Dictclass SecurityGuard:def __init__(self, secret_key):self.secret_key = secret_key# 敏感参数正则(身份证、银行卡等)self.sensitive_patterns = {"id_card": re.compile(r"\d{17}[\dXx]"),"bank_card": re.compile(r"\d{16,19}"),"phone": re.compile(r"1[3-9]\d{9}")}# 危险操作白名单(仅允许特定角色调用)self.dangerous_functions = {"transfer_money": ["admin", "vip_user"],"query_all_orders": ["admin"]}def validate_permission(self, function_name: str, token: str) -> bool:"""验证用户是否有权调用函数"""try:# 解析JWT令牌获取用户角色payload = jwt.decode(token, self.secret_key, algorithms=["HS256"])user_role = payload.get("role", "guest")# 非危险函数直接通过if function_name not in self.dangerous_functions:return True# 检查用户角色是否在白名单中return user_role in self.dangerous_functions[function_name]except Exception:return Falsedef sanitize_parameters(self, params: Dict[str, str]) -> Dict[str, str]:"""清洗参数,防止注入攻击"""sanitized = {}for key, value in params.items():# 过滤SQL注入关键字if isinstance(value, str):sanitized_value = re.sub(r"['\";\\]|(union|select|drop)\s+", "", value, flags=re.IGNORECASE)sanitized[key] = sanitized_valueelse:sanitized[key] = valuereturn sanitizeddef redact_sensitive_data(self, response: str) -> str:"""脱敏返回结果中的敏感信息"""redacted = responsefor name, pattern in self.sensitive_patterns.items():# 身份证号保留前6后4位,银行卡号保留后4位if name == "id_card":redacted = pattern.sub(r"\g<0>[0:6]********\g<0>[-4:]", redacted)elif name == "bank_card":redacted = pattern.sub(r"************\g<0>[-4:]", redacted)elif name == "phone":redacted = pattern.sub(r"\g<0>[0:3]****\g<0>[-4:]", redacted)return redacted安全增强措施:基于RBAC的细粒度权限控制(如"查询订单"需用户ID与订单归属一致)参数校验沙箱:将参数放入隔离环境测试,验证无恶意行为后再执行审计日志区块链存证:关键操作日志上链,确保不可篡改(金融场景必需)动态威胁情报:实时更新攻击特征库,拦截新型注入攻击(4)多工具协同规划系统针对复杂任务,需构建任务规划与状态管理能力:class TaskPlanner:def __init__(self, tool_registry):self.tool_registry = tool_registry # 工具注册表self.task_queue = [] # 待执行的工具调用队列self.task_context = {} # 任务上下文(存储中间结果)def plan(self, user_request: str) -> None:"""将用户请求分解为工具调用序列"""# 1. 解析用户意图和子任务subtasks = self._parse_subtasks(user_request)# 2. 为每个子任务匹配工具并确定依赖关系for subtask in subtasks:tool = self._match_tool(subtask["intent"])dependencies = self._find_dependencies(subtask, self.task_context)self.task_queue.append({"tool": tool["name"],"parameters": subtask["parameters"],"dependencies": dependencies,"status": "pending"})async def execute(self) -> str:"""执行任务队列,处理依赖关系"""while self.task_queue:# 找到所有可执行的任务(依赖已满足)executable_tasks = [t for t in self.task_queueif t["status"] == "pending"and all(self.task_context.get(d) is not None for d in t["dependencies"])]if not executable_tasks:# 检查是否有无法满足的依赖missing = [t for t in self.task_queue if t["status"] == "pending"]return f"无法完成任务:缺少依赖 {missing[0]['dependencies']}"# 并发执行可执行任务for task in executable_tasks:# 填充依赖参数resolved_params = self._resolve_parameters(task, self.task_context)# 调用工具result = await self.tool_registry.invoke(task["tool"], resolved_params)# 更新上下文和任务状态self.task_context[task["tool"]] = resulttask["status"] = "completed"# 整合所有结果生成最终回答return self._integrate_results(self.task_context)# 其他辅助方法:子任务解析、工具匹配、结果整合等协同优化点:基于历史执行数据的任务排序(如先调用响应快的工具)动态依赖调整:某工具调用失败后,自动寻找替代工具重新规划资源预算控制:为复杂任务设置最大工具调用次数,避免资源浪费3. 工程化落地建议在实际落地Function Calling系统时,需结合业务场景选择合适的解决方案:中小规模应用:优先使用成熟框架(如LangChain的Agent、Spring AI)的内置容错机制,聚焦核心业务逻辑大规模企业应用:建议构建分层架构,将参数处理、错误处理、安全防护拆分为独立服务,通过API网关协同关键业务场景:实施混沌工程测试(如随机注入API超时、参数错误),验证系统鲁棒性某电商平台的实践表明,通过上述方案优化后,Function Calling的调用成功率从72%提升至95%,安全事件发生率下降至0.03%,充分验证了这些解决方案的有效性。Function Calling的挑战本质上是"AI的灵活性"与"系统的确定性"之间的平衡问题。随着大模型能力的提升和工程实践的深入,这些挑战将逐步被攻克,推动AI从"对话助手"真正进化为"全能协作伙伴"。技术演进:从FC到MCP的范式革命1. 技术代际对比维度 Function Calling MCP(Model Context Protocol)交互主体 LLM→工具 多模型联邦协作数据形态 JSON参数 结构化上下文协作模式 中心化调度 去中心化网状2. 未来趋势动态注册:运行时添加新工具(如新增快递查询接口)多模态支持:直接处理图片/音频等原生数据自愈机制:自动修正SQL语法错误并重试动态联邦学习:多个Function Calling系统协同进化因果推理增强:理解"如果…则…"类复杂指令结语:AI协作的新范式Function Calling不仅是技术突破,更是人机协作方式的革命。当大模型学会"摇人办事",我们正迈向这样的未来:每个普通人都能通过自然语言,调动企业级系统资源。正如DeepSeek团队在实践中所说:“这不是简单的API调用,而是构建智能体的’瑞士军刀’”。如何从零学会大模型?小白&程序员都能跟上的入门到进阶指南当AI开始重构各行各业,你或许听过“岗位会被取代”的焦虑,但更关键的真相是:技术迭代中,“效率差”才是竞争力的核心——新岗位的生产效率远高于被替代岗位,整个社会的机会其实在增加。但对个人而言,只有一句话算数:“先掌握大模型的人,永远比后掌握的人,多一次职业跃迁的机会。”回顾计算机、互联网、移动互联网的浪潮,每一次技术革命的初期,率先拥抱新技术的人,都提前拿到了“职场快车道”的门票。我在一线科技企业深耕12年,见过太多这样的案例:3年前主动学大模型的同事,如今要么成为团队技术负责人,要么薪资翻了2-3倍。———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/l01011_/article/details/153248425
-
以为夏日漫长, 却忽而已秋,晚风凉凉,秋意初显PaaS生态宠粉福利又双叒叕来咯给你一整个惊喜和仪式感,与夏末余热告别吧登录四个开发平台,即可获得抽奖机会1次,100%中奖悄悄告诉你,奖品池大换新,一起解锁吧 一、活动时间2025年8月27日-8月29日二、活动流程登录4个平台 → 首页截图 → 发给活动助手 → 参与抽奖三、活动方式第1步:活动期间,登录PaaS生态大家族4个产品的用户,凭登录截图联系活动助手,即可参与抽奖活动(注意:截图需包含华为云账号)。①产品一:FunctionGraphFunctionGraph是基于事件驱动的函数托管的计算服务,使用FunctionGraph函数,只需编写业务函数代码并设置运行的条件,无需配置和管理服务器等基础设施,函数以弹性、免运维、高可靠的方式运行,助力开发者快速上线各类应用。登录网址:cid:link_0②产品二:CodeArts 华为云软件开发生产线CodeArts是一站式、全流程、安全可信的云原生DevSecOps平台,覆盖需求、开发、测试、部署、运维等软件交付全生命周期环节,为开发者打造全云化研发体验。登录网址:cid:link_3, ③产品三:CodeArts IDE 华为云CodeArts IDE Online是轻量级WebIDE,通过浏览器即可实现环境快速获取和环境访问,完成编码、构建、调试、运行、访问代码仓库和命令执行等工作,支持第三方业务集成,内置插件市场支持插件扩展。登录网址:cid:link_2 ④产品四:Astro Zero Astro 低代码平台是华为云自主创新的全场景低代码平台,其中的Astro轻应用,通过简单拖拽配置完成应用搭建,轻松构建专业级应用,创新随心所欲,敏捷超乎想象,为企业提供低门槛、高效率的数字化业务应用生产新模式。登录网址:cid:link_1, 第2步:添加小助手微信号,将所有的首页截图发给其中一位工作人员即可。 第3步:经过小助手确认有效后,即发送链接进行抽奖,100%中奖。四、奖品展示苏泊尔保温杯、手机支架、手持电风扇、冰箱贴、熊猫小夜灯、折叠团扇。五、活动须知a.奖品已实物为准,如遇缺货将为您替换同等价值奖品;b.请确保您的用户信息填写准确无误,以便我们及时为您送上精美奖品;c.请确保截图右上角华为云用户名与获奖信息保持一致,即截图、获奖信息与华为云账号保持一致视为一次有效抽奖,否则不予奖品发放;d.奖品将在活动结束后15个工作日内发放,如遇缺货将会替换同等价值奖品;e.如有任何疑问,欢迎随时联系我们的小助手;f.本活动最终解释权归PaaS生态宠粉活动所有。
-
超级记事本-EXCEL用来做个人简单管理系统也不错
-
夏日炎炎,暑气正盛PaaS生态夏日嘉年华带着满满的清爽和诚意来啦登录任意两个以上开发平台,即可获得抽奖机会1次,完成四个额外获得抽奖机会1次!无论是被高温困住脚步的你,还是在空调房里想找点乐子的你,这场夏日限定狂欢都能让你甩掉燥热,收获一整个夏天的好心情! 活动时间2025年7月28日-7月31日活动流程登录2个及以上平台 → 首页截图 → 发给活动助手 → 参与抽奖活动方式第1步:活动期间,登录PaaS生态大家族2个及以上PaaS开发平台的用户,凭登录截图联系活动助手,即可参与抽奖活动。 ①【FunctionGraph平台】FunctionGraph是基于事件驱动的函数托管的计算服务,使用FunctionGraph函数,只需编写业务函数代码并设置运行的条件,无需配置和管理服务器等基础设施,函数以弹性、免运维、高可靠的方式运行,助力开发者快速上线各类应用。截图需包含华为云账号登录网址:cid:link_0 ②【Astro Zero平台】Astro 低代码平台是华为云自主创新的全场景低代码平台,其中的Astro轻应用,通过简单拖拽配置完成应用搭建,轻松构建专业级应用,创新随心所欲,敏捷超乎想象,为企业提供低门槛、高效率的数字化业务应用生产新模式。截图需包含华为云账号登录网址:cid:link_1, ③【CodeArts平台】华为云软件开发生产线CodeArts是一站式、全流程、安全可信的云原生DevSecOps平台,覆盖需求、开发、测试、部署、运维等软件交付全生命周期环节,为开发者打造全云化研发体验。截图需包含华为云账号登录网址:cid:link_3, ④【CodeArts IDE平台】华为云CodeArts IDE Online是轻量级WebIDE,通过浏览器即可实现环境快速获取和环境访问,完成编码、构建、调试、运行、访问代码仓库和命令执行等工作,支持第三方业务集成,内置插件市场支持插件扩展。截图需包含华为云账号登录网址:cid:link_2 第2步:添加小助手微信号,将所有的首页截图发给其中一位工作人员即可。 第3步:经过小助手确认有效后,即发送链接进行抽奖。 登录任意两大开发平台,即可获得抽奖机会1次; 完成四大开发平台登录并截图,额外获得1次抽奖机会; 当用户获得 2 次抽奖机会且两次抽奖均获奖时,可从两次抽中的礼品中选择其一,另一未选择的礼品自动失效;若两次抽奖中仅有一次获奖,则获得该次抽中的礼品;若两次均未获奖,则无礼品。奖品展示华为智能跳绳 8个云宝公仔 50个便携茶具 10个U型枕 6个数据线 30个马克杯 14个*奖品已实物为准,如遇缺货将替换同等价值礼品。活动须知请确保您的用户信息填写准确无误,以便我们及时为您送上精美奖品。请确保截图右上角华为云用户名与获奖信息保持一致,即截图、获奖信息与华为云账号保持一致视为一次有效抽奖,否则不予奖品发放。奖品将在活动结束后15个工作日内发放,如遇缺货将会替换同等价值奖品。如有任何疑问,欢迎随时联系我们的小助手。本活动最终解释权归PaaS生态夏日嘉年华活动所有。特别注意:用户参与活动产品截图中的华为云账号需与参与抽奖的华为云账号一致,否则获奖信息无效。
-
友好性开发者招募活动获奖名单如下:代金券将发放到填写问卷的华为云账号中账号姓名奖项hw41654141二等奖hw026853070二等奖huqi88二等奖hid_xt9_d1tmps0o6uv二等奖hid_q4h4b1qj5jbw1zc二等奖hid_i8imcfonfr8b82p二等奖hid_a_5e13tklt4bcd3二等奖GT-2504_92820634二等奖CCIHWY参与奖 了解友好性开发者招募活动为让文档内容更精准贴合实际场景、更全面覆盖使用需求,我们特发起本次友好开发者招募活动。无论你是熟悉业务的技术达人,还是刚接触产品的新手用户,只要你有想法、有经验,都欢迎加入进来 —— 分享你的使用心得、补充未覆盖的场景细节、提出内容优化建议。一旦您的建议经过专家评审团的认可与采纳,将有以下三个激励:1、实物激励: 根据反馈数量和质量设置月度最佳实物激励 有机会获得华为云资源(代金券)或HDC、HC大会门票2、荣誉激励: 邀请优秀开发者参与华为开发者大会、技术沙龙分享,扩大个人知名度3、资源与机会激励: 参与"扫地僧见面会"深度交流 推荐华为云合作伙伴计划,获得商业化项目推荐参与者不仅有机会获得每月活动礼品,还有可能被评为年度内容贡献官,享受更多荣誉和奖励,获得更多合作机会。我们期待着与您一起,共同打造更加优质、高效的云服务体验。参与方式第1步:实验前的准备实名登录:(已注册并实名可跳过)华为云账号实名认证,点击这里。(已设置可跳过)登录后设置社区昵称,点我设置。报名填写信息:点击报名填写报名信息,报名成功后我们将在每周三发放50元代金券做实验使用。第2步:开启您的云端体验,分享实践案例完成体验后截图在评论区。任务过程中,遇到控制台或者文档问题并提交问题工单(必须是开发者工单)。完成问卷调查。如有疑问或代金券的问题,请扫码进入企业微信群里沟通。 华为云产品介绍:函数工作流(FunctionGraph)是华为云提供的一款无服务器(Serverless)计算服务,无服务器计算是一种托管服务,服务提供商会实时为你分配充足的资源,您不需要预留专用的服务器或容量。使用FunctionGraph函数,只需编写业务函数代码并设置运行的条件,无需配置和管理服务器等基础设施,函数以弹性、免运维、高可靠的方式运行。 常见应用场景事件驱动类应用通过事件触发服务执行,比如文件上传到存储桶后,自动触发函数压缩图片、转换视频编码;实时处理数据流并存储。可灵活扩缩容,按处理时间收费,减少闲置成本。Web类应用结合云服务搭建高可用的 Web / 移动应用后端,例如小程序后端、聊天机器人接口。用户访问量激增时自动扩容,仅按函数运行时间计费,无需维护服务器集群。AI类应用可用于托管Stable Diffusion、MCP Server等热门 AI 应用,通过 API 网关构建稳定的对外服务通道。自动弹性分配计算资源,确保高效响应;计费方式按实际调用次数收取,开发者无需操心服务器运维,可专注投入业务逻辑的研发。 场景&任务说明目标:使用函数工作流FunctionGraph构建一个轻量化定时器,可实时返回距离指定时间的倒计时。 任务操作步骤:在函数工作流控制台创建一个新函数。编写用于计算并返回距离指定目标时间倒计时的函数代码。为函数创建APIG专享版触发器,通过浏览器访问调用URL获取倒计时信息。完成任务所需资源与示例代码:APIG专享版实例(代金券在报名链接填写后,每周三统一发放) APIG专享版触发器Python函数示例代码任务完成标准:成功在函数工作流中创建包含指定代码的函数,配置APIG专享版触发器并完成部署,访问 URL 后能正确显示当前时间、目标时间以及距离目标时间的倒计时。 其他说明: l 请您在操作过程中查阅华为云函数工作流FunctionGraph的帮助文档。l 在操作过程中,若您在产品控制台使用或查阅帮助文档时,遇到流程断点、内容难以理解等情况,请及时记录相关信息并在后续问卷中反馈,我们将对您提出的问题进行分析并进行针对性优化。TIPS: APIG专享版实例按小时收费,请注意在任务完成后及时删除资源,避免持续扣费。活动流程投稿时间:2025年7月16日-8月16日联合评审:2025年8月17日-8月26日奖项公示:2025年8月26-28日奖品发放:获奖名单公布后5个工作日内寄出评奖规则届时将有华为技术专家团参与评审,主要按照以下维度评分:评分维度分值释义分值阶梯总分值任务完成度对发布的场景&任务完成情况0、未完成任务1、根据文档要求完成任务,并且输出正确结果0/11问题工单反馈任务过程中,遇到控制台或者文档问题并提交问题工单(必须是开发者工单,文档难以理解,步骤不清晰均可反馈)0、问题未采纳1、问题描述清晰(如附带截图、操作路径、错误信息等),并且被采纳2、反馈问题、同时被产品部专家识别为高价值需求0/1/10可多次反馈,分数叠加问卷调查问卷调查完成情况0、未完成问卷调查1、完成问卷调查0/11奖项设置奖项设置名额最高限评选条件奖品设置一等奖5按要求完成任务、填写问卷,工单反馈被评为高价值需求开发者定制双肩包+面值3000元代金券二等奖20按要求完成任务、填写问卷、工单反馈总积分排名前20名开发者定制斜挎包+面值1000元代金券参与奖20按要求完成任务、填写问卷、工单反馈随机抽取20名开发者定制大号渔夫帽+面值300元代金券 代金券及周边礼物发放对象为:已完成实名认证的华为云用户。发放到填写报名问卷的账号及收货地址中,所有奖项只可获得一次,不可重复获奖。 重要说明 1、审核通过被采纳的开发者,将在下月初进行获奖名单公布和奖励发放,届时会有站内信通知,请及时关注并填写快递信息,过期未核对或填写视为放弃该期奖品。2、对于持续反馈高质量问题的开发者,每年度末还有年度贡献大礼包送上。特别声明:华为云有权根据自身运营安排,自主决定和调整本活动的具体规则,具体活动规则以活动页公布规则为准。相关规则一经公布即产生效力,您应当予以遵守。如您不认同以上规则,请谨慎参与本次活动。
-
📰 案例概览🚀 背景与简介华为开发者空间深度整合昇腾AI、鸿蒙、鲲鹏等根技术,开发者空间在HDC2025上迎来全面升级,新上线开发平台,集成了 AI原生应用引擎、AI Notebook、FunctionGraph云函数、Astro低代码四大核心能力,实现算力、模型、平台、应用层全栈优化,助力开发者高效完成编码→调测全流程,打造智能化开发体验。⚡️ AI Agent应用开发核心路径通过华为开发者空间开发平台提供的云函数(FunctionGraph)、MCP 构建、AI 原生应用引擎完成一站式 AI Agent 应用开发:创建云函数,完成 Serverless 部署——基于华为云提供的 FunctionGraph(基于 Serverless 技术)服务实现业务函数的部署构建和发布 MCP Server——基于华为开发者空间开发平台提供的模板快速构建和发布 MCP ServerAI Agent 应用开发和发布——基于 AI 原生应用引擎提供的可视化界面快速实现 AI Agent 应用的开发和发布🎯 案例选择使用华为开发者空间-开发平台,通过开发平台提供的云函数、AI Agent能力实现We码会议助手从MCP Server部署到创建AI会议Agent,再到发布使用AI 会议Agent,最后在浏览器中操作使用该AI会议Agent,实现端到端自动化会议创建等操作。🥏 案例流程🕹️ 流程说明:华为开发者空间-开发平台创建云函数,部署MCP Server代码;华为开发者空间-开发平台创建MCP;华为开发者空间-开发平台创建AI 会议Agent;发布体验AI会议Agent,成功创建WeLink会议并发送会议通知。✍️案例实操快速入门:使用MCP资产快速构建AI Agent应用 👈️👈️👈️ 原文点这里1. 华为开发者空间-开发平台安装预置的MCP;2. 构建AI Agent应用,添加安装好的MCP;3. 发布AI Agent应用并体验添加的MCP能力。通过开发平台AI原生应用引擎中提供的MCP资产和预置的的大模型,快速便捷地完成饮食推荐和12306车票助手Agent的构建和发布,轻松get华为开发者空间-开发平台中AI Agent工具的使用技巧。接下来趁热打铁,使用华为开发者空间-开发平台构建一个WeLink会议助手。实战演练:构建WeLink会议助手 👈️👈️👈️ 原文点这里1. 创建云函数部署MCP Server代码,创建触发器,生成调用URL;2. 华为开发者空间-开发平台创建MCP,以SSE的安装方式,配置云函数生成的调用URL;3. 华为开发者空间-开发平台创建AI 会议Agent,配置大模型和MCP;4. 发布体验AI会议Agent,创建WeLink会议,WeLink成功收到会议通知!!!🎉 案例最终效果
-
【华为开发者空间开发平台】产品体验官活动获奖名单如下:一、有效建议奖:昵称建议分值奖项礼品yd_2846377506.9有效建议奖第一名1000元开发者定制礼品banjin4.4有效建议奖第二名800元开发者定制礼品胡琦2.1有效建议奖第三名500元开发者定制礼品二、优质建议奖:昵称礼品yd_284637750100-200元开发者定制礼品banjin100-200元开发者定制礼品胡琦100-200元开发者定制礼品小草飞上天100-200元开发者定制礼品神一样的老师100-200元开发者定制礼品给无眠点压力100-200元开发者定制礼品yd_70527782100-200元开发者定制礼品yd_269585276100-200元开发者定制礼品yd_238822659100-200元开发者定制礼品cxw100-200元开发者定制礼品andyleung100-200元开发者定制礼品 恭喜以上11名获奖用户,请获奖用户通过以下问卷反馈奖品收件信息(9月3日前反馈有效),感谢大家对云声平台的关注和支持~华为开发者空间全新升级,开发平台重磅上线。此次开发平台集成了AI原生应用引擎、FunctionGraph云函数、Astro低代码、云开发环境能力,在算力、模型、平台和应用层都进行了大升级。基于这些全新特性,企业开发者可以在开发者空间开通大模型服务、部署MCP Server、创建Agent、可视化实现业务需求,从而快速开发上线一个企业级 AI 智能应用,让华为云和根生态技术的能量触达到每位开发者的指尖!大家赶快来体验吧,体验完后提交开发者空间优化建议,还可以领取开发者礼包,包括但不限于华为耳机、手环、鼠标、云宝等好礼,快叫上小伙伴一起提建议吧~ 【实践项目】体验项目项目名称难度系数功能体验开发平台:AI Agent/开发平台:低代码应用/开发平台:云函数 /开发平台:云开发环境/案例体验基于华为开发者空间Astro低代码应用平台,构建业务用户登录后台开发★★★★基于华为开发者空间Astro低代码应用平台,构建业务用户登录页面前台开发★★★★基于华为开发者空间开发平台 MCP资产快速构建AI Agent应用★★★基于华为开发者空间开发平台构建We码会议助手★★★★开发者空间 - 云开发环境使用指导 ★★★本地Xshell基于华为开发者空间云开发环境完成上传下载 ★★★详细信息请见“开发平台”,案例中心。 【活动时间】2025年7月1日-7月30日 【参与方式】01 体验开发者空间开发平台 》 02 去云声平台提建议 》 03 建议评估公示 》 04 获奖公示(活动结束后两周内) ps:建议标题需要以“开发者空间体验官”开头 【奖项设置】奖项设置评选条件获奖名额激励礼品有效建议奖1、有效建议数量不少于2条,有效建议由内部技术专家评审得出2、建议内容需针对上述实践项目3、有效建议中有不低于一条是关于功能体验的3名积分榜第1名:1000元礼品/人积分榜第2名:800元礼品/人积分榜第3名:500元礼品/人优质建议奖1、有效建议不少于2条,由内部技术专家评审选出10名每人100-200元开发者定制礼品【活动说明】1、建议预审通过即为有效建议,其中1条有效功能类建议为1分,1条有效体验类建议为0.3分,1条有效Bug类建议0.5分;有效建议奖与优质建议奖可叠加2、建议提交时需要在标题中以“【开发者空间体验官】”为建议标题开头,比如【开发者空间体验官】开发者空间增加XX/优化XX/导入XX等3、建议内容仅针对以上实践项目中“开发平台功能体验+开发平台案例体验”涉及的云产品,非以上实践项目涉及产品建议内容不参与此活动4、优质建议要求建议对云产品功能及优化改进有重要作用,优先从已被采纳的建议的选择;建议内容需要表述清晰,有明确的建议方案,最好有操作截图或链接等能进一步详细描述;高价值建议数量不限,且与有效建议奖可叠加,每位用户每月最多可获得一次。5、注意事项1)若出现积分相同且排名一致的情况,结合已实现和已采纳建议情况,由内部技术专家选出价值更高的建议用户给予奖励2)同一用户在同一页面(文档)提出的同一类用户体验问题(包括但不限于错别字、语句不通顺、视觉体验等),在通过审核后仅算作一条有效建议数3)若发现代他人提交优化建议,此建议分值只取原分值30%;若发现2次及以上重复提交他人建议,或3次及以上重复提交体验类相关建议进行恶意刷量(包括但不限于错别字、语句不通顺、视觉体验等),取消本人活动参与资格;在活动截止日前进行大量刷屏提交建议,或者重复提交同类型体验建议,直接取消活动参与资格4)以上兑换礼品均为仓库现有实物礼品,有货的情况下优先满足,其中1-3名可优先选择华为自营品牌电子礼品,礼品价值不能超过商品原价,无货则不可以指定,如遇商品缺货,将随机换成其他等价值礼品发放5)此【开发者空间体验官】与云声月度例行激励活动不叠加,若标题无【开发者空间体验官】标记,则有效建议默认参与月度例行激励
-
bug提交不上 ,报错
-
摘要:Serverless可以看作是一种云计算服务模型,它允许开发者在不需要管理服务器的情况下通过事件驱动的方式运行应用代码。软件架构的发展从原先的单体架构到近十几年的微服务架构,再到现在新兴的Serverless架构。单体架构通常把应用的逻辑和功能耦合在一起,部署在BMS裸金属机或VM上,耦合模式使得一些通用功能或通用逻辑无法灵活复用,经常出现重复造轮子的现象,架构整体上是相对封闭的。微服务时代对应用做了拆分,组件服务化,诞生了一系列优秀的设计原则,如接口标准化、CS/CD自动化,使得应用初步具备了弹性和自动容错的能力。 在Serverless的框架下,应用的粒度更细,例如以函数为粒度进行管理和开发迭代,应用通过事件驱动的方式触发运行。 Serverless可以看作是一种云计算服务模型,它允许开发者在不需要管理服务器的情况下通过事件驱动的方式运行应用代码,主要解决资源托管、调度、运维管理等一系列平台型问题,可以看作是DevOps的进一步延伸。 从传统微服务向Serverless演进的过程中,资源管理的边界逐步上移。在微服务时代,虚机/容器时代,开发者发布应用,需要管理虚拟化层或更向上的运行时细节;而从IaaS到Serverless,抽象度越来越高,底层细节的屏蔽度也越来越高,对于开发者而言,云的易用性性也在增强,门槛在降低。 Serverless将是下一代的云计算范式,得益于它在按因付费、自动弹性、免运维等方面的显著优势。通用全场景的Serverless能够帮助企业解决更广泛的计算和运行交付的问题。实际上,Serverless也在过去几年的发展中,逐步增长成为下一代云上交付、运行与交付标准的框架。Serverless的特点是按用计费、按需计费,如果终端没有流量,平台会自动回收应用资源,不需要为空闲时间付费,需要再次使用时,通过重新启动的方式再次加载。 冷启动或进一步延伸到高并发下的弹性问题,用户侧应用托管到Serverless平台上,如果频繁经历冷启动,应用的QoS下降,最直观体现在终端用户页面的Web 应用响应很慢,有超时的风险,导致用户侧体验下降。对云平台侧,冷启动和弹性关系着资源利用率,例如多租户进行混合部署时,预热效率的提升问题,以及平台多用户多应用流量并发上涨时的弹性问题。因此,冷启动和弹性问题是Serverless领域广泛关注的技术重点。 为了解决上述问题,华为云Serverless做了一系列的探索。 链路加速:通过高性能解压缩转换与共享内存加速技术,能够把性能提升2-11倍;通过公共依赖包分离、预解压与动态按需 link 技术,加速应用冷启动 5- 10 倍。池化预热、弹性调度:通过资源池化预热、分层预加载与弹性水位控制,实现毫秒级 (< 10 ms) 弹性。 智能预热:以智能、分层资源预热实现负载感知,自适应池化水位控制;采用负载、资源压力预测框架,实现良好扩展性、数据驱动、以及工程师经验的规则注入。 预留实例智能推荐策略:通过建模的方式推算给定时间段大概需要的实例数量,提供高性能、平衡、低成本三种预留实例配置策略。未来,边缘、中间件、托管、大数据将全面Serverless化,最底层是Serverless的BaaS的资源、资源池的自动化管理和快速弹性的能力。在底座之上,是跟业务相关的函数计算、容器和托管,三种共存。Serverless容器将下沉到Serverless底座,只需要对底座资源进行管理。在边缘端,传统的容器或是虚机可能无法支撑,采用Serverless + WASM是更可行的方案。
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
回顾中
热门标签