• 【话题交流】人工智能发展也越来越火热,大家能高效运用AI了吗?
    人工智能发展也越来越火热,大家能高效运用AI了吗?
  • [大赛资讯] 初赛A榜格式分统计(截止7月20日23:59)
    团队名称格式得分精度得分西北智联161153CEATRG0149.5hid_t7v_sdh548dhkkz0148.5hid_cwfo0xxj8regp6r0146鸿蒙极客队162143.48BUPT-ParCIS160136.34notrickno154136.08武汉船院计算机2307#2153133.49ECNU_ELRM139132.15全都对队162131.56judgeyang98131myf gogogo98129.11二进制萝卜培育中心98127擎狮0126纳算力克大工坊105125.45不玊之客劣等兵朴昌罗桂夏0123奇点0122.73点子王0122.45破晓者155120.4想去研究大模型108119.9bupt735162117.47Decoder-Only161116123462115[object Object]0114.88浙安院云计算63112.54挑战杯揭榜挂帅华为0111.75说人话0111.11昇腾推理智速引擎0109.08试试0107.1蒜鸟你搞不赢队0106.55ken0102.35hid_b2ydyl88e3z7tqc160102.02智在必得098.79yangs_wdxw12096.88TEMP12096.88这对吗096.03拳头花可火091.64西北文科大学队13891武汉船院计算机2307090.71昇腾芯链088.33hid_77fv2kg9-fgvjfg087.81三角矩阵085.99随便起一个13585.08永宁永胜9883.93123--083.79PACKPACK11378.32华东理工大学AIMC实验室074.49昇腾智推大模型072.53CodeWisdom070.54GT-ejdkd068.7Create3267扬花落满肩064.14马桶蹲累了160.97急急急060.11AAA建材王哥058璃月医科大学孤云阁校区16257.22lab3083851.42fengerhu14946被资本做局041.24智枢拓界025.66hid_x7qejp3bft91lsd16012.02hid_hyh--co6xfj6nhk010challenger X07A-team02hw035532519500.4CCD队00.4没机基队00
  • [案例共创] 《智能OA革命引擎》华为云 AI Agent:智构未来交互,让决策如光随行
    华为云 AI Agent:智能 OA 的变革引擎 在当今数字化浪潮中,企业办公模式正经历着深刻变革,智能办公自动化(OA)成为提升企业竞争力的关键。华为云 AI Agent 凭借其强大的智能技术能力,正逐步成为构建智能 OA 系统的核心驱动力,为企业带来高效、智能、便捷的办公新体验。 一、华为云 AI Agent 赋能智能 OA 的技术基石 (一)强大的自然语言处理能力 华为云 AI Agent 集成了先进的自然语言处理(NLP)技术,能够精准理解员工在办公场景中的各类自然语言指令。无论是查询公司政策、流程,还是进行任务安排、会议预约,员工只需通过日常语言与 OA 系统交互,AI Agent 便能迅速解析指令意图,转化为准确的操作指令。例如,员工发出 “查询本季度市场推广预算使用情况的文档” 指令,AI Agent 可以快速定位到 OA 系统知识库中相关文档,并准确呈现给员工,极大提升了信息获取效率,摆脱了传统 OA 系统复杂的检索操作流程。 (二)智能决策支持 依托大数据分析与机器学习算法,华为云 AI Agent 为智能 OA 注入了智能决策能力。在企业日常运营中,涉及大量业务数据处理与决策场景,如项目进度跟踪、资源分配、风险预警等。AI Agent 能够实时收集、分析 OA 系统中各类业务数据,挖掘数据背后的规律和趋势,为管理者提供数据驱动的决策建议。以项目管理为例,AI Agent 可根据项目进度数据、人员资源数据、成本数据等,预测项目可能出现的延误风险,并给出优化资源分配、调整项目计划等针对性建议,助力管理者做出更科学、高效的决策,保障项目顺利推进。 (三)多模态交互能力 为满足多样化办公需求,华为云 AI Agent 具备多模态交互能力,不仅支持文本交互,还融合了语音、图像等多种交互方式。在一些办公场景中,语音交互更为便捷高效,员工通过语音即可快速完成信息查询、指令下达等操作,解放双手,提升办公灵活性。例如,在会议途中,员工无需手动输入,通过语音指令即可让 AI Agent 在 OA 系统中查询相关资料、记录会议要点。同时,对于一些包含图像信息的办公任务,如文档中的图片识别、流程审批中的签名识别等,AI Agent 也能准确处理,进一步丰富了 OA 系统的交互体验,适应不同办公环境和业务场景。 二、华为云 AI Agent 在智能 OA 中的核心应用场景 (一)智能流程自动化 在传统 OA 系统中,办公流程审批往往繁琐复杂,涉及多个环节和人员,容易出现流程卡顿、效率低下等问题。华为云 AI Agent 通过对企业业务流程的深度理解和自动化处理,实现了智能流程自动化。当一份审批流程发起后,AI Agent 能够自动识别流程类型、审批节点及相关规则,根据预设条件智能分配审批任务给相应负责人,并实时跟踪流程进度。若遇到审批环节超时未处理,AI Agent 会自动发出提醒,确保流程顺畅流转。例如,在合同审批流程中,AI Agent 可自动审核合同关键信息是否合规,对符合条件的合同快速推进审批流程,极大缩短了合同审批周期,提高了企业运营效率。 (二)智能客服与知识问答 员工在使用 OA 系统过程中,难免会遇到各种问题,如系统操作疑问、业务流程咨询等。华为云 AI Agent 为智能 OA 构建了强大的智能客服与知识问答体系。它能够基于企业知识库,快速响应用户问题,提供准确的解答和指导。无论是常见问题的自动回复,还是复杂业务问题的深度解析,AI Agent 都能应对自如。而且,AI Agent 具备学习能力,能够根据用户反馈不断优化答案,提升服务质量。例如,新员工入职后对公司请假流程不熟悉,通过 OA 系统的智能客服咨询,AI Agent 可立即给出详细的请假流程说明及操作指引,帮助新员工快速上手办公流程,同时也减轻了企业内部客服团队的工作压力。 (三)智能协作与任务管理 企业办公离不开团队协作与任务管理,华为云 AI Agent 为智能 OA 的协作与任务管理模块带来了智能化升级。在团队协作场景中,AI Agent 能够根据项目需求和团队成员的技能、工作量等因素,智能分配任务,确保任务分配合理、高效。同时,它还能实时跟踪任务进度,对任务执行过程中出现的问题及时预警,并提供协作建议。例如,在一个跨部门项目中,AI Agent 可根据各部门成员的专业技能和当前工作负载,合理分配项目任务,并在项目执行过程中,通过分析任务进度数据,发现某一环节可能因资源不足导致延误,及时提醒项目负责人调整资源配置,促进团队协作顺畅进行,提高项目整体执行效率。 三、华为云 AI Agent 助力智能 OA 的优势与价值 (一)提升办公效率,降低人力成本 通过自动化办公流程、快速响应知识查询、智能分配任务等功能,华为云 AI Agent 显著提升了企业办公效率。员工能够更快速地完成工作任务,减少时间浪费在繁琐的流程和信息查找上。同时,智能客服与流程自动化减少了人工干预需求,降低了企业人力成本投入。据相关数据统计,引入华为云 AI Agent 的智能 OA 系统,企业办公效率平均提升 30% 以上,人力成本降低 20% 左右。 (二)增强决策科学性,提升企业竞争力 基于大数据分析和机器学习的智能决策支持,使企业管理者能够获取更准确、及时的决策依据。在复杂多变的市场环境下,科学的决策能够帮助企业把握市场机遇,规避风险,提升企业竞争力。华为云 AI Agent 助力企业在项目管理、资源配置、战略规划等方面做出更优决策,推动企业持续发展。 (三)优化用户体验,提升员工满意度 多模态交互、智能客服、个性化任务推荐等功能,为员工带来了便捷、高效、个性化的办公体验。员工在使用智能 OA 系统过程中,感受到操作的流畅性和系统的智能性,从而提升了对企业办公环境的满意度,有助于提高员工工作积极性和忠诚度。 综上所述,华为云 AI Agent 凭借其强大的技术能力和丰富的应用场景,正深刻改变着智能 OA 的格局,为企业带来前所未有的办公变革。在未来,随着 AI 技术的不断发展和创新,华为云 AI Agent 将在智能 OA 领域发挥更大作用,助力企业实现数字化、智能化转型,迈向更高效、更智能的办公新时代。
  • [技术干货] AI赋能心理咨询 - 华为开发者空间基于DeepSeek与Dify的解决方案
    📰 案例概述🚀 背景与简介在数字化时代的快节奏下,现代人常被焦虑、压力等情绪困扰,却面临心理咨询资源分散、沟通门槛高、个性化支持不足等问题——传统咨询预约耗时,线上平台有多停留在浅层情绪疏导。基于此,本案聚焦利用ModelArts Studio和Dify.AI这两个强大的平台来构建心理咨询师应用。ModelArts Studio作为华为云推出的大模型即服务平台(简称为Maas服务),为用户提供丰富的大模型资源、全流程的模型开发托管服务,具备昇腾算子、显存优化及预置最优超参配置等优势,可帮助用户轻松实现模型获取、开发到部署的一站式操作。Dify则是一款开源的大语言模型应用开发平台,融合后端即服务和LLMOps理念,通过可声明式的YAML文件定义AI应用,有可视化的Prompt编排、运营、数据集管理等功能,支持数百个模型,为开发者提供了便携的AI应用搭建途径。案例优势:通过本案例可以学习到如何在ModelArts Studio领取满血版DeepSeek-r1模型,并结合Dify搭建本地知识库,使用其chatflow编排模式构建心理咨询师应用,体验如何使用Dify.AI提供的编排能力构建AI应用业务逻辑。🥏 案例流程🕹️ 流程介绍:免费领取华为MaaS DeepSeek满血版 Tokens;华为开发者空间部署和配置Dify.AI;在Dify创建知识库;创建心理咨询师应用。✍️ 案例实操🎉 基于DeepSeek和Dify构建心理咨询   👈👈👈  体验完整案例,请点击这里进行查看华为云提供了单模型200万免费Tokens,包含DeepSeek-R1&v3满血版,登录ModelArts Studio控制台领取免费额度;Dify是一个开源的AI应用开发平台与编排引擎,专注于帮助用户快速构建、部署和管理基于大语言模型的AI应用,打开华为开发者空间部署和配置Dify.AI;添加心理类相关数据构建知识库;导入DSL文件构建心理咨询师应用。🌈 案例最终效果       
  • [互动交流] 使用三层的CNN网络,训练13分钟、3000个epoch之后,达到了0.9673的准确率,本节使用LeNet - 5训练16分钟左右、3000个epoch之后,达到了0.9485的准确率,比上一节还低一点,分析下原因?
    使用三层的CNN网络,训练13分钟、3000个epoch之后,达到了0.9673的准确率,本节使用LeNet - 5训练16分钟左右、3000个epoch之后,达到了0.9485的准确率,比上一节还低一点,分析下原因?
  • [大赛资讯] 初赛A榜格式分统计(截止7月16日14:30)
    截止7月16日14:30前,各团队初赛A榜注:仅最高精度分对应的格式得分,如最高精度得分相同,以最早提交的最高精度得分为准。团队名称格式得分精度得分西北智联161153hid_t7v_sdh548dhkkz0148.5hid_cwfo0xxj8regp6r0146鸿蒙极客队162143.48BUPT-ParCIS160136.34CEATRG0135.5武汉船院计算机2307#2153133.49ECNU_ELRM139132.15全都对队162131.56judgeyang98131myf gogogo98129.11二进制萝卜培育中心98127擎狮0126纳算力克大工坊105125.45不玊之客劣等兵朴昌罗桂夏0123奇点0122.73点子王0122.45破晓者155120.4bupt735162117.47Decoder-Only161116123462115浙安院云计算63112.54试试0107.1ken0102.35hid_b2ydyl88e3z7tqc160102.02蒜鸟你搞不赢队0101.19智在必得098.79说人话098.77这对吗096.03TEMP095.17拳头花可火091.64武汉船院计算机2307090.71挑战杯揭榜挂帅华为088.8hid_77fv2kg9-fgvjfg087.81随便起一个13585.08昇腾芯链084.74yangs_wdxw082.65三角矩阵081.22PACKPACK11378.32永宁永胜075.39华东理工大学AIMC实验室074.49昇腾智推大模型072.53CodeWisdom070.54GT-ejdkd068.7急急急060.11璃月医科大学孤云阁校区16257.22想去研究大模型056.65马桶蹲累了056.05fengerhu14946扬花落满肩118.43Create017.11hid_x7qejp3bft91lsd16012.02智枢拓界011.59hid_hyh--co6xfj6nhk010challenger X07A-team02hw035532519500.4CCD队00.4没机基队00
  • [大赛资讯] 初赛A榜格式分统计(截止7月9日14点00分)
    截止7月9日14点00分前,各团队初赛A榜注:仅最高精度分对应的格式得分,如最高精度得分相同,以最早提交的最高精度得分为准。团队名称格式得分精度得分hid_t7v_sdh548dhkkz0148.5hid_cwfo0xxj8regp6r0146鸿蒙极客队162143.48CEATRG0135.5武汉船院计算机2307#2153133.49ECNU_ELRM144131.34擎狮0126纳算力克大工坊105125.45奇点0122.73点子王0122.45破晓者155120.4bupt735162117.47西北智联133117Decoder-Only161116123462115judgeyang0114.07浙安院云计算63112.54二进制萝卜培育中心0112.06BUPT-ParCIS76108.52myf gogogo0107.23试试0107.1不玊之客劣等兵朴昌罗桂夏0106.4ken0102.35hid_b2ydyl88e3z7tqc160102.02智在必得098.79这对吗096.03蒜鸟你搞不赢队092.13拳头花可火091.64武汉船院计算机2307090.71说人话089.05hid_77fv2kg9-fgvjfg087.81TEMP085.96yangs_wdxw082.65昇腾芯链082.39永宁永胜075.39华东理工大学AIMC实验室074.49挑战杯揭榜挂帅华为073.73CodeWisdom070.54GT-ejdkd068.7急急急060.11璃月医科大学孤云阁校区16257.22想去研究大模型056.65全都对队4846fengerhu14946扬花落满肩118.43Create017.11hid_x7qejp3bft91lsd16012.02hid_hyh--co6xfj6nhk010智枢拓界09.56challenger X07A-team02hw035532519500.4没机基队00
  • [技术干货] 基于华为开发者空间Astro低代码应用平台,构建Deepseek智能助手界面
    📰 案例概览🚀 背景与简介华为开发者空间是为全球开发者打造的专属云上成长空间,深度整合昇腾AI、鸿蒙、鲲鹏等华为根技术。开发者空间在HDC2025上迎来全面升级,新增AI原生应用引擎、AI Notebook、鸿蒙云手机、FunctionGraph云函数、Astro低代码等核心能力,并在算力、模型、平台、应用层实现全方位优化,助力开发者高效完成从编码到调测的全流程,打造智能AI应用开发新体验。智能助手模板是基于AI大模型定制化的智能助手解决方案,旨在为企业和开发者提供灵活、高效、智能的交互体验。通过对接先进的AI大模型,能够实现多场景、多领域的智能化应用,满足不同业务的需求,助力企业实现数字化转型和效率提升。随着AI技术的不断进步,智能助手模板将进一步扩展应用场景,为企业创造更大的价值。案例优势:智能助手模板支持无缝对接主流AI大模型,通过灵活的模型调用和优化机制,实现多场景、多领域的智能适配,满足不同业务需求。智能助手模板提供高度可定制化的前端配置能力,支持根据具体场景需求,灵活调整交互逻辑、功能模块及品牌视觉风格,打造贴合用户需求的智能化解决方案。🎯  本案例将通过华为开发者空间-低代码应用开发平台对接AI开发平台ModelArts,并以其中的DeepSeek-V3-32K服务模型为例,实现智能助手对话界面的搭建及后端功能开发。🥏 案例流程🕹️ 流程说明:领取华为开发者空间,登录华为开发者空间-低代码应用开发平台,新建低代码应用工程,进入Astro轻应用服务控制台主页,开发应用。登录ModelArts平台,领取免费服务tokens,配置DeepSeek-V3-32K服务模型。完成Deepseek智能助手低代码应用开发,发布应用。✍️ 案例实操🎉 AstroChat智能助手对话界面开发  👈👈👈  体验完整案例,点这里登录华为开发者空间-低代码应用开发平台,创建AstroChat标准应用;创建智能助手模板自定义组件:获取组件模板、上传自定义组件;登录ModelArts平台,领取免费服务tokens,获取DeepSeek模型关键参数API地址、模型名称、API Key;创建自定义连接器,设置连接器动作参数与认证信息,配置DeepSeek模型信息;创建服务编排,添加配置图元,启用服务编排;开发智能助手对话界面,设置agent-ui组件属性,测试发布智能助手应用。🌈  案例最终效果:
  • [大赛资讯] 初赛A榜格式分统计(截止7月2日12点30分)
    截止7月2日12点30分前,各团队初赛A榜注:仅最高精度分对应的格式得分,如最高精度得分相同,以最早提交的最高精度得分为准团队名称格式得分精度得分CEATRG0135.5ECNU_ELRM144131.34擎狮0126纳算力克大工坊112122.87点子王0118bupt735162117.47Decoder-Only16111612340113.18hid_t7v_sdh548dhkkz127112.62myf gogogo0107.23不玊之客劣等兵朴昌罗桂夏0106.4二进制萝卜培育中心0104.09ken0102.35智在必得098.79试试097.99这对吗096.03judgeyang095.29BUPT-ParCIS094.68蒜鸟你搞不赢队092.13拳头花可火091.64浙安院云计算5091说人话089.05昇腾芯链082.39破晓者074.35CodeWisdom070.54TEMP069.66永宁永胜7669.45GT-ejdkd068.7AIMC061.01急急急060.11璃月医科大学孤云阁校区16257.22想去研究大模型056.65hid_hyh--co6xfj6nhk010A-team02没机基队00
  • [案例共创] 【案例共创】基于仓颉 + DeepSeek + MCP 的智能膳食分析助手
    基于仓颉 + DeepSeek + MCP 的智能膳食分析助手一、项目背景及需求分析1.1 项目背景随着人们对健康饮食关注度的提升,越来越多用户希望借助 AI 助手实现个性化的饮食分析与管理。然而,目前市面上的饮食类应用普遍存在如下痛点:1️⃣. 缺乏智能分析:大多数仅记录卡路里,无法提供专业点评与优化建议;2️⃣. 知识更新不及时:难以结合最新营养研究进行推荐与判断;3️⃣. 缺乏可扩展性:难以适配特定人群(如高血压、糖尿病、健身人群等)的差异需求。本项目旨在构建一个基于大语言模型(DeepSeek)和结构化协议(MCP)的智能饮食健康助手,通过自然语言交互,帮助用户实现饮食数据结构化、健康风险识别与个性化建议生成。1.2 核心需求1️⃣ 食品实体识别能力系统应具备从自然语言中准确识别出饮食相关的食品实体与关键词的能力。例如,用户输入:“我今天吃了煎饼果子、卤牛肉、奶茶”,系统应能够自动识别出食品列表:["煎饼果子", "卤牛肉", "奶茶"] 该能力是后续营养分析与健康判断的基础,应支持中文短语、多食物组合、模糊词等多种表达方式。2️⃣ 饮食结构化分析能力(MCP 中间层处理)系统应通过 MCP 模块完成对用户输入饮食内容的语义解析与结构化处理,主要包括:应提取食品清单;应识别饮食行为时段(如早餐、晚餐、夜宵等);应对识别出的食物打上健康标签(如高糖、高油脂、高钠、低纤维等);应构造符合 LLM 输入规范的 Prompt,以支撑后续模型理解与生成。结构化分析结果应作为 DeepSeek 模型调用的核心中间数据。3️⃣ 合理饮食建议生成能力(基于 DeepSeek)系统应利用 DeepSeek 大模型,根据 MCP 构造的 Prompt 生成具有科学性、专业性与可操作性的饮食建议,具体应包括:对当前饮食存在的健康风险进行分析(如糖分摄入超标、缺乏蔬菜等);提供可替代食材建议(如炸鸡 → 烤鸡,奶茶 → 绿茶);给出搭配优化方案(如餐前饮水、搭配高纤维蔬菜等);提出个性化提示(如适合三高人群、健身人群、孕期饮食等)。模型输出应以自然语言形式呈现,逻辑清晰、结构明确,风格应贴近真实营养师表达方式。二、项目概述本项目旨在构建一套基于多模块协作的 智能膳食分析助手,通过自然语言输入,实现对个人饮食行为的结构化分析与健康建议生成,服务于日常健康管理、饮食优化与疾病预防等场景。系统采用模块化设计,主要由三大核心组成部分构成:2.1 技术选型模块技术栈职责🧑‍💻 客户交互仓颉语言提供终端或界面交互入口,接收用户自然语言输入并展示模型生成结果🔧 中间处理MCP 模块(Python + FastAPI)实现饮食语义解析、食物识别、健康标签打标、Prompt 生成与请求路由🤖 模型服务DeepSeek 大语言模型接收经过标准化构造的请求 Prompt,返回结构化营养建议、健康分析与饮食优化建议2.2 适用对象企业健康服务平台产品经理个人开发者高校学生营养师/健身爱好者病患群体2.3 案例时间环境配置:约30 分钟构建项目:约25分钟测试阶段:约10 分钟本案例总时长预计 65 分钟。2.4 项目流程整个系统的数据处理与响应流程如下所示:2.5 资源总览本案例预计花费 0 元。资源名称规格免费额度/价格开发者空间–云主机2CPU4g X86 Ubuntu 22.04 Server定制版0元CodeArts IDE for CangjieIDE 仓颉 云主机预装0元CodeArts IDE for PythonIDE Python 云主机预装0元Maas DeepSeek 模型服务试用200W Token0元2.6 架构亮点✅ 使用 MCP 解耦输入处理与模型调用,提升可扩展性与灵活性;✅ DeepSeek 专注自然语言生成任务,MCP 预处理提高 prompt 精度;✅ 仓颉终端可后续拓展为图形界面、Web 应用或移动端入口;✅ 本地配置与食物字典可按需调整,适配多种用户类型(如减脂、控糖、健身等);✅ 架构支持多模型/多来源接入,可拓展至 Claude、SparkDesk、ChatGPT 等后端模型。三、环境准备3.1 申请云主机面向广大开发者群体,华为开发者空间提供一个随时访问的“开发桌面云主机”、丰富的“预配置工具集合”和灵活使用的“场景化资源池”,开发者开箱即用,快速体验华为根技术和资源。如果还没有领取开发者空间云主机,可以参考免费领取云主机文档领取。领取云主机后可以直接进入华为开发者空间工作台界面,点击进入桌面连接云主机。3.2 免费领取DeepSeek R1满血版华为云提供了单模型200万免费Tokens,包含DeepSeek-R1&V3满血版,我们可以登录华为云ModelArts Studio(MaaS)控制台领取免费额度,这里我们选择DeepSeek-R1满血版来搭建我们的专属AI聊天机器人。在云主机桌面底部菜单栏,点击打开火狐浏览器。用火狐浏览器访问ModelArts Studio首页:cid:link_2,点击ModelArts Studio控制台跳转到登录界面,按照登录界面提示登录,即可进入ModelArts Studio控制台。签署免责声明。进入ModelArts Studio控制台首页,区域选择西南-贵阳一,在左侧菜单栏,选择在线推理 > 预置服务 > 免费服务,选择DeepSeek-R1-32K模型,点击领取额度,领取200万免费token。领取后点击调用说明,可以获取到对应的API地址、模型名称。红色方块为API 地址 红色箭头为 API KEY四、项目构建本案例采用 本地MCP 服务+ 本地仓颉AI机器人对话 (参考来自于 (https://gitcode.com/CaseDeveloper/Cangjie-Examples)4.1 MCP 服务构建4.1.1 打开 CodeArts IDE for Python4.1.2 新建项目项目名称 构建为: food_mcp 然后点击 创建4.1.3 代码实施mcp_server.py# -*- coding: utf-8 -*- # mcp_server.py """基于仓颉 + DeepSeek + MCP 的智能膳食分析助手""" from fastapi import FastAPI from pydantic import BaseModel import json from typing import List, Dict, Any from fastapi.responses import StreamingResponse from deepseek_client import stream_deepseek, async_stream_deepseek from text_match import extract_food_simple, extract_food from loguru import logger from cachetools import TTLCache import sys # 加载食物字典 with open("food_tags.json", encoding="utf-8") as f: FOOD_TAGS: Dict[str, Dict[str, Any]] = json.load(f) app = FastAPI() class Input(BaseModel): input: str # 全局缓存:prompt -> full_reply_text CACHE = TTLCache(maxsize=500, ttl=1800) # 30分钟自动过期 logger.remove() logger.add(sys.stdout, level="INFO", enqueue=False, backtrace=False) # ---- 基础角色词 ---- BASE_SYSTEM_PROMPT = ( "你是一名资深注册营养师,擅长以简洁的 Markdown 格式给出科学、可执行的饮食建议。" "所有回答需包含以下小节:\n" "1. 总体评价\n" "2. 推荐摄入(量/食材)\n" "3. 过量风险\n" "4. 不宜同食\n" "5. 行动建议\n" "回答请使用中文,并尽量在 300 字以内。" ) # ---- 场景化模板 ---- PROMPTS = { "nutrition_review": ( BASE_SYSTEM_PROMPT + "\n\n【场景】饮食点评。请先总体评价,再按上表 1~5 小节输出:{input}" ), "diet_plan": ( BASE_SYSTEM_PROMPT + "\n\n【场景】减脂期餐单。请输出 3 日食谱 (表格形式),并在每餐注明热量估计:{input}" ), "effects_inquiry": ( BASE_SYSTEM_PROMPT + "\n\n【场景】过量影响。请列出已知副作用及参考文献:{input}" ), "avoid_inquiry": ( BASE_SYSTEM_PROMPT + "\n\n【场景】相克查询。请说明不宜同食原因及替代方案:{input}" ), } def _process_input(user_input: str): """内部共用逻辑,返回分析结果和 prompt""" # === 1. 食物关键字匹配 === food_list, exact_hits, fuzzy_hits = extract_food(user_input, FOOD_TAGS.keys()) # 记录匹配详情到日志 logger.info(f"[匹配] 精确={exact_hits} 模糊={fuzzy_hits}") # === 2. 聚合静态信息 === tags = list({tag for food in food_list for tag in FOOD_TAGS[food].get("tags", [])}) effects = {food: FOOD_TAGS[food].get("effects") for food in food_list if FOOD_TAGS[food].get("effects")} avoid_with = list({aw for food in food_list for aw in FOOD_TAGS[food].get("avoid_with", [])}) diet_type = list({dt for food in food_list for dt in FOOD_TAGS[food].get("diet_type", [])}) # === 3. 根据关键词判定 mode === mode = "nutrition_review" if any(k in user_input for k in ["减肥", "减脂", "低卡", "少油", "瘦身", "个人食谱"]): mode = "diet_plan" elif any(k in user_input for k in ["吃多了", "过量", "上火", "副作用", "影响"]): mode = "effects_inquiry" elif any(k in user_input for k in ["不能一起", "相克", "不宜同食", "一起吃"]): mode = "avoid_inquiry" new_prompt_base = PROMPTS[mode].format(input=user_input) # === 4. 拼接静态附加信息 === notes = [] if effects: notes.append("【过量风险】" + ";".join(f"{food}:{desc}" for food, desc in effects.items())) if avoid_with: notes.append("【不宜同食】" + "、".join(avoid_with)) extra_info = ";".join(notes) if extra_info: new_prompt_base += ( "\n\n以下为已知静态信息(请务必先列出【过量风险】与【不宜同食】两个小节,并完整引用下列内容,否则视为回答不完整):" f"{extra_info}" ) # 要求模型以 JSON 输出,配合 response_format new_prompt = new_prompt_base + "\n请使用 markdown bullet list 输出建议。" return { "food_list": food_list, "tags": tags, "effects": effects, "avoid_with": avoid_with, "diet_type": diet_type, "mode": mode, "routed_input": new_prompt, } @app.post("/mcp") async def route_prompt(data: Input): user_input = data.input result = _process_input(user_input) return result @app.post("/mcp_stream") async def route_prompt_stream(data: Input): user_input = data.input result = _process_input(user_input) async def event_generator(): meta_json = json.dumps({k: v for k, v in result.items() if k != 'routed_input'}, ensure_ascii=False) # 发送 Meta 事件 yield f"event: meta\ndata: {meta_json}\n\n" prompt_key = result["routed_input"] # 若缓存命中,直接按20字符切片发送缓存内容 if prompt_key in CACHE: logger.info("[缓存] 命中") cached_text = CACHE[prompt_key] # SSE data 行不能包含裸换行,需逐行加前缀 payload_lines = [f"data: {line}" for line in cached_text.split("\n")] payload_block = "\n".join(payload_lines) yield f"event: token\n{payload_block}\n\n" yield "event: done\ndata: [DONE]\n\n" return logger.info("[缓存] 未命中") collected_chunks = [] async for chunk in async_stream_deepseek(prompt_key): collected_chunks.append(chunk) yield f"event: token\ndata: {chunk}\n\n" # 缓存完整内容 full_text = "".join(collected_chunks) CACHE[prompt_key] = full_text # 结束事件 yield "event: done\ndata: [DONE]\n\n" return StreamingResponse(event_generator(), media_type="text/event-stream") deepseek_client.py# deepseek_client.py # 基于 DeepSeek API 的客户端 import requests import json from typing import Generator import httpx # 从 config.json 读取配置 with open("config.json", encoding="utf-8") as f: config = json.load(f) DEEPSEEK_API_KEY = config["DEEPSEEK_API_KEY"] DEEPSEEK_URL = config["DEEPSEEK_URL"] MODEL_NAME = config["MODEL_NAME"] def call_deepseek(prompt): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {DEEPSEEK_API_KEY}" } payload = { "model": MODEL_NAME, "messages": [ {"role": "user", "content": prompt} ] } response = requests.post(DEEPSEEK_URL, headers=headers, json=payload) result = response.json() return result["choices"][0]["message"]["content"] # 新增:流式推理生成器 def stream_deepseek(prompt) -> Generator[str, None, None]: """Yield generated content chunks from DeepSeek API using SSE.""" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {DEEPSEEK_API_KEY}" } payload = { "model": MODEL_NAME, "messages": [ {"role": "user", "content": prompt} ], "stream": True, "max_tokens": 2048, "stream_options": {"include_usage": True}, } # 使用 stream=True 触发增量输出 response = requests.post(DEEPSEEK_URL, headers=headers, json=payload, stream=True) # iter_lines 将保持连接并逐行读取 for line in response.iter_lines(decode_unicode=True): if not line: continue # DeepSeek/SSE 行以 "data: " 开头 if line.startswith("data: "): data_str = line[6:] # 结束标志 if data_str.strip() == "[DONE]": break # 解析 JSON,获取增量内容 try: data_json = json.loads(data_str) # usage 块 choices 为空,跳过 choices = data_json.get("choices", []) if not choices: continue delta = choices[0]["delta"].get("content", "") if delta: yield delta except json.JSONDecodeError: # 忽略无法解析的片段 continue # 异步流式生成器 async def async_stream_deepseek(prompt): """异步生成器,从 DeepSeek API 生成内容(SSE)。""" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {DEEPSEEK_API_KEY}" } payload = { "model": MODEL_NAME, "messages": [ {"role": "user", "content": prompt} ], "stream": True, "max_tokens": 2048, "stream_options": {"include_usage": True}, } async with httpx.AsyncClient(timeout=None) as client: async with client.stream("POST", DEEPSEEK_URL, headers=headers, json=payload) as resp: async for line in resp.aiter_lines(): if not line: continue if line.startswith("data: "): data_str = line[6:] if data_str.strip() == "[DONE]": break try: data_json = json.loads(data_str) choices = data_json.get("choices", []) if not choices: continue delta = choices[0]["delta"].get("content", "") if delta: yield delta except json.JSONDecodeError: continue test_pipeline.py# -*- coding: utf-8 -*- # test_pipeline.py import requests from deepseek_client import call_deepseek import json from requests.exceptions import ChunkedEncodingError def test_diet_analysis(): user_input = "中午吃了炸鸡和奶茶,晚上吃了牛肉火锅" # 调用 MCP mcp_resp = requests.post("http://localhost:8001/mcp", json={"input": user_input}) mcp_data = mcp_resp.json() print("MCP 返回:", mcp_data) # 调用 DeepSeek reply = call_deepseek(mcp_data["routed_input"]) print("DeepSeek 回复:", reply) def test_diet_plan(): user_input = "我想要一个一周减肥计划,主要食物包括香菇、鸡胸肉和燕麦" mcp_resp = requests.post("http://localhost:8001/mcp", json={"input": user_input}) mcp_data = mcp_resp.json() print("MCP 返回:", mcp_data) reply = call_deepseek(mcp_data["routed_input"]) print("DeepSeek 回复:", reply) def test_effects_inquiry(): user_input = "香菇吃多了会怎样?" mcp_resp = requests.post("http://localhost:8001/mcp", json={"input": user_input}) mcp_data = mcp_resp.json() print("MCP 返回:", mcp_data) reply = call_deepseek(mcp_data["routed_input"]) print("DeepSeek 回复:", reply) def test_avoid_inquiry(): user_input = "牛奶和虾能一起吃吗?" mcp_resp = requests.post("http://localhost:8001/mcp", json={"input": user_input}) mcp_data = mcp_resp.json() print("MCP 返回:", mcp_data) reply = call_deepseek(mcp_data["routed_input"]) print("DeepSeek 回复:", reply) def test_stream(): user_input = "我晚上吃什么比较好呢? 中午吃了炸鸡和奶茶" try: resp = requests.post( "http://localhost:8001/mcp_stream", json={"input": user_input}, stream=True, timeout=None, ) print("Stream start →", resp.status_code) meta_printed = False for line in resp.iter_lines(decode_unicode=True): if not line: continue # SSE: 可能含 event: xxx if line.startswith("event: "): current_event = line[7:] # 下一行应该是 data: continue if not line.startswith("data: "): continue data = line[6:] # current_event 默认为 token current_event = locals().get("current_event", "token") if current_event == "meta": try: meta_obj = json.loads(data) pretty_meta = json.dumps(meta_obj, ensure_ascii=False) print("Meta:", pretty_meta) except json.JSONDecodeError: print("Meta:", data) meta_printed = True print("\n--- AI 回复 ---\n", end="") continue if current_event == "done": print("\n✅ Stream done") break # 其他 token 输出 print(data, end="", flush=True) except ChunkedEncodingError: # 服务器已发送完毕但提前关闭连接,可忽略 print("\n⚠️ 连接提前关闭,已接收全部内容。") finally: try: resp.close() except Exception: pass if __name__ == "__main__": # test_diet_analysis() # test_diet_plan() # test_effects_inquiry() # test_avoid_inquiry() test_stream() text_match.py# -*- coding: utf-8 -*- # text_match.py import jieba import difflib from typing import Iterable, List, Set, Tuple def extract_food( user_text: str, food_vocab: Iterable[str], fuzzy_threshold: float = 0.8, ) -> Tuple[List[str], List[str], List[str]]: """根据用户输入提取食物关键词。 1. 先使用 jieba 分词命中精确词。 2. 对分词结果做模糊匹配,解决同义词/花式写法。 Args: user_text: 用户原始输入字符串。 food_vocab: 食物静态数据库的键集合。 fuzzy_threshold: SequenceMatcher 相似度阈值 (0-1)。 Returns: 去重后的食物列表,按出现顺序返回。 """ vocab_set: Set[str] = set(food_vocab) tokens = jieba.lcut(user_text, cut_all=False) hits: List[str] = [] exact_hits: List[str] = [] fuzzy_hits: List[str] = [] added: Set[str] = set() for token in tokens: # 精确匹配 if token in vocab_set and token not in added: hits.append(token) exact_hits.append(token) added.add(token) continue # 模糊匹配 for food in vocab_set: if food in added: continue if difflib.SequenceMatcher(None, token, food).ratio() >= fuzzy_threshold: hits.append(food) fuzzy_hits.append(food) added.add(food) break return hits, exact_hits, fuzzy_hits # 向后兼容的简化接口 def extract_food_simple(user_text: str, food_vocab: Iterable[str], fuzzy_threshold: float = 0.8) -> List[str]: """返回仅 food list 的旧版接口,供其他模块调用。""" return extract_food(user_text, food_vocab, fuzzy_threshold)[0] food_tags.json{ "炸鸡": { "tags": ["高油脂", "高热量"], "effects": "经常食用可能导致能量过剩、血脂升高", "avoid_with": ["啤酒"], "diet_type": ["增重期", "周末放纵"], "recipe_hint": "可使用空气炸锅,减少50%油脂摄入" }, "奶茶": { "tags": ["高糖", "高热量"], "effects": "过量摄入易导致胰岛素抵抗、体重增加", "avoid_with": [], "diet_type": ["偶尔犒劳"], "recipe_hint": "选择低糖或无糖版本,减少珍珠" }, "可乐": { "tags": ["高糖"], "effects": "长期高糖饮料摄入可增加蛀牙及肥胖风险", "avoid_with": ["咖啡"], "diet_type": ["偶尔犒劳"], "recipe_hint": "选择零度可乐或气泡水替代" }, "卤牛肉": { "tags": ["高钠", "高蛋白"], "effects": "高钠摄入可能升高血压", "avoid_with": [], "diet_type": ["增肌期"], "recipe_hint": "配合蔬菜食用平衡营养" }, "牛肉火锅": { "tags": ["高油脂", "高钠"], "effects": "高盐高油易导致水肿", "avoid_with": ["冰啤酒"], "diet_type": ["增重期"], "recipe_hint": "控制汤底油脂和盐分,搭配蔬菜" }, "蔬菜汤": { "tags": ["低热量", "健康推荐"], "effects": "一般安全", "avoid_with": [], "diet_type": ["减脂期", "日常维稳"], "recipe_hint": "少盐少油" }, "白灼虾": { "tags": ["高蛋白", "健康推荐"], "effects": "嘌呤偏高,痛风患者需控制", "avoid_with": ["维生素C高的水果"], "diet_type": ["增肌期", "减脂期"], "recipe_hint": "控制蘸料用量" }, "香菇": { "tags": ["高纤维", "低脂"], "effects": "过量可能导致胀气", "avoid_with": ["寒性食物"], "diet_type": ["减脂期", "日常维稳"], "recipe_hint": "烹饪前泡发充分" }, "燕麦": { "tags": ["高纤维", "低GI"], "effects": "摄入过多可能引起腹胀", "avoid_with": [], "diet_type": ["减脂期", "增肌期"], "recipe_hint": "配合蛋白质食物提高饱腹" }, "糙米": { "tags": ["高纤维", "低GI"], "effects": "富含植酸,影响矿物质吸收,建议与其他谷物搭配", "avoid_with": [], "diet_type": ["减脂期", "日常维稳"], "recipe_hint": "提前浸泡12小时再煮" }, "炸薯条": { "tags": ["高油脂", "高热量"], "effects": "反式脂肪酸摄入风险", "avoid_with": ["汽水"], "diet_type": ["偶尔犒劳"], "recipe_hint": "可使用空气炸锅替代油炸" } , "牛油果": { "tags": ["高脂肪", "高纤维", "健康推荐"], "effects": "脂肪含量高,减脂期注意总热量", "avoid_with": [], "diet_type": ["增肌期", "日常维稳"], "recipe_hint": "搭配全麦面包或沙拉" }, "酸奶": { "tags": ["高蛋白", "益生菌"], "effects": "乳糖不耐人群可能腹胀", "avoid_with": ["高糖谷物"], "diet_type": ["减脂期", "增肌期", "日常维稳"], "recipe_hint": "选择无糖或低糖版本" }, "披萨": { "tags": ["高油脂", "高热量"], "effects": "高钠高脂,注意摄入频次", "avoid_with": ["可乐"], "diet_type": ["周末放纵"], "recipe_hint": "选择薄底、加多蔬菜版本" }, "红薯": { "tags": ["低GI", "高纤维", "健康推荐"], "effects": "过量可致腹胀", "avoid_with": [], "diet_type": ["减脂期", "日常维稳"], "recipe_hint": "蒸煮可保留营养" }, "鲑鱼": { "tags": ["高蛋白", "Omega-3"], "effects": "富含EPA/DHA,有助心血管健康", "avoid_with": ["富含草酸蔬菜"], "diet_type": ["增肌期", "日常维稳"], "recipe_hint": "推荐清蒸或空气炸锅" }, "绿茶": { "tags": ["低热量", "抗氧化"], "effects": "空腹或过量可致胃刺激", "avoid_with": ["牛奶"], "diet_type": ["减脂期", "日常维稳"], "recipe_hint": "餐后一小时饮用更佳" }, "巧克力": { "tags": ["高糖", "高脂"], "effects": "摄入过多易长痘和增重", "avoid_with": [], "diet_type": ["偶尔犒劳"], "recipe_hint": "优选 70% 以上黑巧" }, "能量饮料": { "tags": ["高糖", "咖啡因"], "effects": "过量可能导致心悸、睡眠障碍", "avoid_with": ["咖啡"], "diet_type": ["比赛备战", "偶尔提神"], "recipe_hint": "控制每日总咖啡因 < 400mg" } } config.json{ "DEEPSEEK_API_KEY": "刚刚申请的Key", "DEEPSEEK_URL": "申请得到的Url", "MCP_PORT": 8001, "MODEL_NAME": "DeepSeek-R1" } requirements.txtfastapi uvicorn requests httpx jieba cachetools loguru4.1.4 安装依赖点击 终端pip install -r requirements.txt -i https://mirrors.huaweicloud.com/repository/pypi/simple安装成功 示例图 如果显示仍然缺少依赖 请关闭ide 重新打开即可4.2 调试 MCP终端上执行命令uvicorn mcp_server:app --port 8001 运行成功示例图下一步 新建终端 点加号执行 测试程序python test_pipeline.py执行测试成功示例图 这里我们就可以看到 流式响应的一个效果然后我们最小化 这个 CodeArts IDE for Python4.3 仓颉交互AIChat(魔改MCP)打开 仓颉编辑器4.3.1 新建项目我们这里名称就叫 food_bot 然后点击 创建4.3.2 代码实施config.json{ "model": "", "api_key": "", "base_url": "http://localhost:8001/mcp_stream", "system_prompt": "" } cjpm.toml[dependencies] [package] cjc-version = "0.53.13" compile-option = "" description = "nothing here" link-option = "" name = "food_bot" output-type = "executable" src-dir = "" target-dir = "" version = "1.0.0" package-configuration = {} src/main.cjpackage food_bot import std.console.Console import std.collection.ArrayList func cli_chat(env_info: EnvInfo, stream!: Bool) { println("\n欢迎使用智能膳食分析助手,输入exit退出") var history: ArrayList<(String, String)> = ArrayList<(String, String)>() while (true) { print("Input: ") var prompt: String = "" match(Console.stdIn.readln()) { case Some(str1: String) => prompt=str1 case None => continue } if (prompt == "exit" || prompt == "exit()") { break } if (prompt == "clear") { history.clear() println("Output: 已清理历史对话信息。") continue } // 根据配置或命令行参数判断是否启用流式模式 var use_stream: Bool = stream if (!use_stream && env_info.base_url.endsWith("/mcp_stream")) { use_stream = true } print("ChatBox: ") let response_option: Option<String> = if (use_stream) { stream_chat(prompt, env_info, history) } else { chat(prompt, env_info, history) } match (response_option) { case Some(response: String) => println("${response}") history.append((prompt, response)) case None => println("发生错误,正在重试一次...") let retry_option = if (use_stream) { stream_chat(prompt, env_info, history) } else { chat(prompt, env_info, history) } match(retry_option) { case Some(resp2: String) => println("${resp2}") history.append((prompt, resp2)) case None => println("重试失败,即将退出") break } } } } main(args: Array<String>): Int64 { let env_info = load_env_info() var stream: Bool = false if (args.size == 1) { if (args[0] == "--stream") { stream = true } else { println("参数无效,仅支持 --stream 以开启流式输出") } } cli_chat(env_info, stream: stream) return 0 } env_info.cjpackage food_bot import encoding.json.stream.* import std.fs.File import std.fs.Path import std.io.ByteArrayStream public class EnvInfo <: JsonDeserializable<EnvInfo> & JsonSerializable { public let model: String // 模型名称 public let api_key: String // api密钥 public let base_url: String // 调用接口路径 public let system_prompt: String // 预置系统提示词 public init(model: String, api_key: String, base_url: String, system_prompt: String) { this.model = model this.api_key = api_key this.base_url = base_url this.system_prompt = system_prompt } public static func fromJson(r: JsonReader): EnvInfo { var temp_model: String = "" var temp_api_key: String = "sk-xxx" var temp_base_url: String = "http://xxx.xxx.xxx/v1" var temp_system_prompt: String = "You are a helpful assistant." while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "model" => temp_model = r.readValue<String>() case "api_key" => temp_api_key = r.readValue<String>() case "base_url" => temp_base_url = r.readValue<String>() case "system_prompt" => temp_system_prompt = r.readValue<String>() case _ => () } } r.endObject() break case _ => throw Exception() } } return EnvInfo(temp_model, temp_api_key, temp_base_url, temp_system_prompt) } public func toJson(w: JsonWriter): Unit { w.startObject() w.writeName("model").writeValue(this.model) w.writeName("api_key").writeValue(this.api_key) w.writeName("base_url").writeValue(this.base_url) w.writeName("system_prompt").writeValue(this.system_prompt) w.endObject() w.flush() } } public func save_env_info(): Unit { // 该函数用于测试EnvInfo类的序列化为json的能力,顺便生成一个env_sample.json样本做为参考 let env_path = Path("env_sample.json") if (File.exists(env_path)) { File.delete(env_path) } let file = File.create(env_path) let env_info = EnvInfo( "xxxx", "sk-xxxxxx", "http://xxx.xxx.xxx/v1/chat/completions", "You are a helpful assistant." ) var byte_stream = ByteArrayStream() var json_writer = JsonWriter(byte_stream) let write_config = WriteConfig.pretty json_writer.writeConfig = write_config env_info.toJson(json_writer) file.write(byte_stream.readToEnd()) println("`env_sample.json` save ok") file.close() } public func load_env_info(): EnvInfo { // 用于加载配置文件 let env_path = Path("config.json") if (!File.exists(env_path)) { throw Exception("The config file not exists, please check again") } let file = File.openRead(env_path) let file_str: Array<UInt8> = file.readToEnd() var byte_stream = ByteArrayStream() byte_stream.write(file_str) let json_reader = JsonReader(byte_stream) let env_info: EnvInfo = EnvInfo.fromJson(json_reader) file.close() // println("model: ${env_info.model}") // println("api_key: ${env_info.api_key}") // println("base_url: ${env_info.base_url}") // println("system_prompt: ${env_info.system_prompt}") return env_info } chat.cjpackage food_bot import encoding.json.stream.* import net.http.ClientBuilder import net.http.HttpHeaders import net.http.HttpRequestBuilder import net.tls.TlsClientConfig import net.tls.CertificateVerifyMode import std.collection.ArrayList import std.io.ByteArrayStream import std.time.Duration import std.unicode.UnicodeExtension // for String.trim() // ===== 可配置常量 ===== public let READ_TIMEOUT_SECONDS: Int64 = 300 // 长轮询 SSE 建议 300 秒 // =========================== public enum RoleType { User | Assistant | System } public func role_type_to_str(role: RoleType): Option<String> { return match(role) { case RoleType.User => Some("user") case RoleType.Assistant => Some("assistant") case RoleType.System => Some("system") } } public func str_to_role_type(role_option_str: Option<String>): RoleType { return match(role_option_str) { case Some(str) => match(str) { case "user" => RoleType.User case "assistant" => RoleType.Assistant case "system" => RoleType.System case _ => RoleType.Assistant } case None => RoleType.Assistant } } public struct Message<: JsonDeserializable<Message> & JsonSerializable { public let role: RoleType public var content: String public init(role: RoleType, content: String) { this.role = role this.content = content } public static func fromJson(r: JsonReader): Message { var temp_role: Option<String> = None var temp_content: String = "" while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match(n) { case "role" => temp_role = r.readValue<Option<String>>() case "content" => temp_content = r.readValue<String>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for Message") } } let role_type: RoleType = str_to_role_type(temp_role) return Message(role_type, temp_content) } public func toJson(w: JsonWriter) { w.startObject() w.writeName("role").writeValue<Option<String>>(role_type_to_str(this.role)) w.writeName("content").writeValue<String>(this.content) w.endObject() w.flush() } } public struct ChatRequest <: JsonSerializable { private let model: String private let messages: ArrayList<Message> private let max_tokens: Int64 private let stream: Bool public init( model: String, messages: ArrayList<Message>, max_tokens: Int64, stream: Bool ) { // construction function with messages this.model = model this.messages = messages this.max_tokens = max_tokens this.stream = stream } public init( model: String, prompt: String, history: ArrayList<(String, String)>, system_prompt: String, max_tokens: Int64, stream: Bool ){ // construction function with prompt and system_prompt this.model = model this.messages = ArrayList<Message>([ Message(RoleType.System, system_prompt) ]) for ((use_msg, bot_msg) in history) { this.messages.append(Message(RoleType.User, use_msg)) this.messages.append(Message(RoleType.Assistant, bot_msg)) } this.messages.append(Message(RoleType.User, prompt)) this.max_tokens = max_tokens this.stream = stream } public init( model: String, prompt: String, history: ArrayList<(String, String)>, system_prompt: String, stream: Bool ){ // construction function with prompt and default arguments this.model = model this.messages = ArrayList<Message>([ Message(RoleType.System, system_prompt) ]) for ((use_msg, bot_msg) in history) { this.messages.append(Message(RoleType.User, use_msg)) this.messages.append(Message(RoleType.Assistant, bot_msg)) } this.messages.append(Message(RoleType.User, prompt)) this.max_tokens = 2000 this.stream = stream } public func toJson(w: JsonWriter) { w.startObject() w.writeName("model").writeValue<String>(this.model) w.writeName("messages").writeValue<ArrayList<Message>>(this.messages) w.writeName("max_tokens").writeValue<Int64>(this.max_tokens) w.writeName("stream").writeValue<Bool>(this.stream) w.endObject() w.flush() } } public struct Choice <: JsonDeserializable<Choice> & JsonSerializable { public let index: Int32 public let message: Option<Message> public let delta: Option<Message> public let finish_reason: Option<String> public let logprobs: Option<Float64> // dashscope for qwen need public init( index: Int32, message: Option<Message>, delta: Option<Message>, finish_reason: Option<String>, logprobs: Option<Float64> ) { this.index = index this.message = message this.delta = delta this.finish_reason = finish_reason this.logprobs = logprobs } public static func fromJson(r: JsonReader): Choice { var temp_index: Int32 = -1 var temp_message: Option<Message> = None var temp_delta: Option<Message> = None var temp_finish_reason: Option<String> = None var temp_logprobs: Option<Float64> = None while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "index" => temp_index = r.readValue<Int32>() case "message" => temp_message = r.readValue<Option<Message>>() case "delta" => temp_delta = r.readValue<Option<Message>>() case "finish_reason" => temp_finish_reason = r.readValue<Option<String>>() case "logprobs" => temp_logprobs = r.readValue<Option<Float64>>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for Choice") } } return Choice(temp_index, temp_message, temp_delta, temp_finish_reason, temp_logprobs) } public func toJson(w: JsonWriter) { w.startObject() w.writeName("index").writeValue<Int32>(this.index) w.writeName("message").writeValue<Option<Message>>(this.message) w.writeName("delta").writeValue<Option<Message>>(this.delta) w.writeName("finish_reason").writeValue<Option<String>>(this.finish_reason) w.writeName("logprobs").writeValue<Option<Float64>>(this.logprobs) w.endObject() w.flush() } } public struct Usage <: JsonDeserializable<Usage> & JsonSerializable { public let prompt_tokens: UInt64 public let completion_tokens: UInt64 public let total_tokens: UInt64 public init(prompt_tokens: UInt64, completion_tokens: UInt64, total_tokens: UInt64) { this.prompt_tokens = prompt_tokens this.completion_tokens = completion_tokens this.total_tokens = total_tokens } public static func fromJson(r: JsonReader): Usage { var temp_prompt_tokens: UInt64 = 0 var temp_completion_tokens: UInt64 = 0 var temp_total_tokens: UInt64 = 0 while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "prompt_tokens" => temp_prompt_tokens = r.readValue<UInt64>() case "completion_tokens" => temp_completion_tokens = r.readValue<UInt64>() case "total_tokens" => temp_total_tokens = r.readValue<UInt64>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for Usage") } } return Usage(temp_prompt_tokens, temp_completion_tokens, temp_total_tokens) } public func toJson(w: JsonWriter) { w.startObject() w.writeName("prompt_tokens").writeValue<UInt64>(this.prompt_tokens) w.writeName("completion_tokens").writeValue<UInt64>(this.completion_tokens) w.writeName("total_tokens").writeValue<UInt64>(this.total_tokens) w.endObject() w.flush() } } public struct ChatResponse <: JsonDeserializable<ChatResponse> { // some api names `id`, and some names `request_id` public let id: Option<String> public let request_id: Option<String> public let system_fingerprint: Option<String> public let model: String public let object: String public let created: UInt64 public let choices: ArrayList<Choice> public let usage: Option<Usage> public init( id: Option<String>, request_id: Option<String>, system_fingerprint: Option<String>, model: String, object: String, created: UInt64, choices: ArrayList<Choice>, usage: Option<Usage> ) { this.id = id this.request_id = request_id this.system_fingerprint = system_fingerprint this.model = model this.object = object this.created = created this.choices = choices this.usage = usage } public static func fromJson(r: JsonReader): ChatResponse { var temp_id: Option<String> = None var temp_request_id: Option<String> = None var temp_system_fingerprint: Option<String> = None var temp_model: String = "" var temp_object: String = "" var temp_created: UInt64 = 0 var temp_choices: ArrayList<Choice> = ArrayList<Choice>([]) var temp_usage: Option<Usage> = None while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "id" => temp_id = r.readValue<Option<String>>() case "request_id" => temp_request_id = r.readValue<Option<String>>() case "system_fingerprint" => temp_system_fingerprint = r.readValue<Option<String>>() case "model" => temp_model = r.readValue<String>() case "object" => temp_object = r.readValue<String>() case "created" => temp_created = r.readValue<UInt64>() case "choices" => temp_choices = r.readValue<ArrayList<Choice>>() case "usage" => temp_usage = r.readValue<Option<Usage>>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for ChatResponse") } } return ChatResponse( temp_id, temp_request_id, temp_system_fingerprint, temp_model, temp_object, temp_created, temp_choices, temp_usage ) } } public func get_domain( url: String ): String { var temp_url = url if (temp_url.startsWith("https://")) { temp_url = temp_url["https://".size..] } else if (temp_url.startsWith("http://")) { temp_url = temp_url["http://".size..] } let domain: String = temp_url.split("?")[0].split("/")[0] return domain } public func build_http_client( prompt: String, env_info: EnvInfo, history: ArrayList<(String, String)>, stream!: Bool ){ // prepare input data // If we are targeting the custom `/mcp_stream` backend we must send // a very simple JSON body `{ "input": "<prompt>" }` instead of the // OpenAI-style `ChatRequest`. Detect this via URL suffix. let is_mcp_stream = env_info.base_url.endsWith("/mcp_stream") var post_data: Array<UInt8> if (is_mcp_stream) { var local_stream = ByteArrayStream() let local_writer = JsonWriter(local_stream) // { "input": "..." } local_writer.startObject() local_writer.writeName("input").writeValue(prompt) local_writer.endObject() local_writer.flush() post_data = local_stream.readToEnd() } else { var array_stream = ByteArrayStream() let json_writer = JsonWriter(array_stream) let chat_res = ChatRequest( env_info.model, prompt, history, env_info.system_prompt, stream ) chat_res.toJson(json_writer) post_data = array_stream.readToEnd() } // build headers var headers: HttpHeaders = HttpHeaders() if (!is_mcp_stream) { // local backend doesn't require auth header headers.add("Authorization", "Bearer ${env_info.api_key}") } headers.add("Content-Type", "application/json") if (stream) { headers.add("Accept", "text/event-stream") } let request = HttpRequestBuilder() .url(env_info.base_url) .method("POST") .body(post_data) .readTimeout(Duration.second * READ_TIMEOUT_SECONDS) .addHeaders(headers) .build() let client = if (env_info.base_url.startsWith("https")) { var tls_client_config = TlsClientConfig() tls_client_config.verifyMode = CertificateVerifyMode.TrustAll tls_client_config.domain = get_domain(env_info.base_url) ClientBuilder() .tlsConfig(tls_client_config) .build() } else { ClientBuilder().build() } return (request, client) } public func chat( prompt: String, env_info: EnvInfo, history: ArrayList<(String, String)> ): Option<String> { let (request, client) = build_http_client( prompt, env_info, history, stream: false ) var result_message: Option<String> = None var res_text = "" try { // call api let response = client.send( request ) // read result (support max revice 100k data) let buffer = Array<Byte>(102400, item: 0) let length = response.body.read(buffer) res_text = String.fromUtf8(buffer[..length]) // println("res_text: ${res_text}") var input_stream = ByteArrayStream() input_stream.write(res_text.toArray()) // convert text to ChatResponse object let json_reader = JsonReader(input_stream) let res_object = ChatResponse.fromJson(json_reader) let choices: ArrayList<Choice> = res_object.choices if (choices.size > 0) { let message = choices[0].message.getOrThrow() // println("message: ${message.content}") result_message = Some(message.content) } else { println("can't found any response") } } catch (e: Exception) { println("ERROR: ${e.message}, reviced text is ${res_text}") } client.close() return result_message } public func stream_chat( prompt: String, env_info: EnvInfo, history: ArrayList<(String, String)> ): Option<String> { let (request, client) = build_http_client( prompt, env_info, history, stream: true ) let is_mcp_stream = env_info.base_url.endsWith("/mcp_stream") var result_response: String = "" var temp_text2 = "" try { // call api let response = client.send( request ) // read result let buffer = Array<Byte>(10240, item: 0) if (is_mcp_stream) { var done = false var current_event = "" while(!done) { let length = response.body.read(buffer) if (length == 0) { break } let res_text = String.fromUtf8(buffer[..length]) for (line in res_text.split("\n")) { let trimmed = line.trim() if (trimmed.size == 0) { continue } if (trimmed.startsWith("event: ")) { current_event = trimmed["event: ".size..] continue } if (trimmed.startsWith("data: ")) { let data = trimmed["data: ".size..] if (current_event == "token") { // 累积 token,暂不直接打印 result_response = result_response + data } else if (current_event == "ping") { // 服务器心跳,忽略即可 () } else if (current_event == "error") { println("服务器错误: ${data}") done = true result_response = "" // force empty so caller sees None break } else if (current_event == "done") { done = true break } } } if (done) { break } } } else { var finish_reason: Option<String> = None while(finish_reason.isNone() && temp_text2 != "[DONE]") { let length = response.body.read(buffer) let res_text = String.fromUtf8(buffer[..length]) for (temp_text in res_text.split("\n")) { temp_text2 = if (temp_text.startsWith("data: ")) { temp_text["data: ".size..] } else { temp_text } if (temp_text2.size == 0) { continue } if (temp_text2 == "[DONE]") { break } var input_stream = ByteArrayStream() input_stream.write(temp_text2.toArray()) // convert text to ChatResponse object let json_reader = JsonReader(input_stream) let res_object = ChatResponse.fromJson(json_reader) let choices: ArrayList<Choice> = res_object.choices if (choices.size > 0) { finish_reason = choices[0].finish_reason if (finish_reason.isNone()) { let delta = choices[0].delta.getOrThrow() result_response = result_response + delta.content } } else { println("can't found any response") } } } } } catch (e: Exception) { println("ERROR: ${e.message}, reviced text is ${temp_text2}") } client.close() if (result_response.size > 0) { return Some(result_response) } else { return None } } 4.4 调试AIChat点击 终端执行 命令cjpm run运行成功示例图输入测试语句例如:我中午吃了炸鸡和奶茶还有香菇 我晚上吃点什么比较好呢?切回 CodeArts IDE for Python 切换 终端我们就可以看到 MCP 服务端 接受到请求的日志然后我们切回 仓颉编辑器 查看 这是响应正常 那么我们来测试 缓存 的效果 再次发出这个命令我中午吃了炸鸡和奶茶还有香菇 我晚上吃点什么比较好呢?你会看到它 立刻返回了结果然后我们可以 切换到 Python编辑器 看一下 命中缓存的日志💡这是两次请求1️⃣请求未命中则去请求DeepSeek2️⃣请求相同数据 则命中缓存五、项目总结5.1 MCPMCP,全称 Model Context Protocol,中文叫“模型上下文协议”。你可以把它想象成 AI 的“USB 接口” --让不同的 AI 模型、工具和应用程序能用统一的方式交流。那么我的理解是:它更像是一个适配器来调节各种AI不同的接口 达到一致的效果,让AI的交流更加简单,即使没有身份预设,走MCP是完美的让AI成为你的最佳助手。5.2 项目tag本次案例参考:https://devstation.connect.huaweicloud.com/space/devportal/casecenter/9a5b50615ac44a21af30f8699c81efc6/1https://devstation.connect.huaweicloud.com/space/devportal/casecenter/5a7144a66d1b43e088592ae447d518ed/1https://gitcode.com/CaseDeveloper/Cangjie-Examples简单的实现了 MCP并发(案例教程没有提及 uvicorn mcp_server:app --port 8001 --workers 4 )后续可拓展例如:food_bot 🔜 vue等各种UI 前端 各种适配机器人food_mcp🔜 独立开发为自己更加需要 模式更加契合的"USB" 让AI更加懂你关于缓存方面:缓存可以采用本地文件、redis等更加高性能的方案来替换这样就可以让实现由AI回答 到AI理解到了所表达和呈现的每一个需求点六、活动地址​ 如果对该项目有疑问,可以评论区留言交流~如果喜欢可以点点赞~​ 我正在参加【案例共创】第4期 基于华为开发者空间+仓颉/DeepSeek/MCP完成应用构建开发实践 https://bbs.huaweicloud.com/forum/thread-02127182415062274055-1-1.html
  • [分享交流] 【分享交流】AI编程助手的伦理边界
    随着Ai编码的普及化,大众化,多起代码版权诉讼引发争议。当AI生成的代码片段可能包含受版权保护的代码时,开发者该如何规避法律风险?欢迎大家讨论
  • [案例共创] 【案例共创】基于仓颉、DeepSeek与RGF的AI桌宠开发实践
    前言不久前在仓颉的交流群中看到了关于仓颉案例共创的活动消息,于是想到可以用仓颉结合自身所长开发一个AI桌面宠物供大家娱乐学习。正好笔者封装的仓颉桌面渲染库 RGF_CJ 和控件库 RGF_UI 能满足这方面的需求,那么说干就干!成品效果开发前的准备在正式进行应用的开发之前,需要先准备好相关的依赖。领取华为云DeepSeek免费资源华为云为开发者提供了单模型200万Tokens的免费额度,包含DeepSeek-R1&V3满血版,登录华为云ModelArts Studio(MaaS)控制台即可领取,此处可以选择DeepSeek-R1满血版来搭建AI聊天桌宠。提示:点击此链接领取:https://www.huaweicloud.com/product/modelarts/studio.html进入网站签署服务声明领取免费额度并查看调用说明通过说明中的链接去创建API点击按钮创建API填写API相关信息获取API并保存提示:请保存好此处的API链接,后续代码开发中会用到。获取RGF依赖文件在后续应用的开发中会依赖到RGF_UI库,此库依赖到一些文件,可以从RGF_UI的库中获取。这些依赖文件后续会链接入应用中去。提示:点击此链接下载:https://gitcode.com/raozj/RGF_UI/tree/master/libs进入RGF_UI库的Libs目录,下载依赖文件保存依赖文件,留待后续使用安装仓颉STDX及相关依赖因为是基于仓颉开发通信相关的应用,因此需要依赖到STDX中的stdx.net.http、stdx.encoding.json、stdx.net.tls等包,而这些包自Cangjie v0.60.5版本开始从std标准库中移出,因此需要自行安装这些包以及这些包所依赖的OpenSSL。提示:点击此链接下载:https://gitcode.com/Cangjie/Cangjie-STDX注’ 由于笔者发文时Cangjie v0.60.5版本尚且处于内测阶段,因此若开发者无内测资格则无法下载到此版本的相关文件。关于 STDX 和 OpenSSL 的安装请参考上述链接中官方的文档,此处不做赘述。序列帧合图资源制作作为一个桌面宠物,自然需要拥有对应的动画,对此可以使用DragonBones、Spine或者Adobe Effect等软件制作。制作动画笔者在此使用的是DragonBones,关于DragonBones的安装和使用不在本文的探讨范畴之内,读者可自行选择喜欢的动画制作软件,设计并制作完成动画后输出序列帧以用于应用的开发。设计动画制作出序列帧制作合图资源如果每帧的图片都单独存储,对于应用的初始化加载和渲染都是一个较大的负担,因此建议将一类的序列帧动画按照相等宽高、间距合并为一张图片。由于手动合图比较麻烦,因此笔者写了一个Python脚本,快速的将目录中的序列帧图片按照指定的宽度分布数量合并为一张图片。注’ 由于此部分不属于文章核心部分,因此直接贴出代码以供参考,读者可通过其它工具或代码实现自己个性化的合图方案。import os import cv2 import numpy as np def load_images_from_folder(folder): """ 加载指定文件夹下的所有 .png 图像 """ image_files = [f for f in os.listdir(folder) if f.lower().endswith('.png')] image_files.sort() # 按文件名排序 images = [] for filename in image_files: file_path = os.path.join(folder, filename) # 读取图像(支持中文路径) with open(file_path, 'rb') as f: img_data = np.frombuffer(f.read(), dtype=np.uint8) img = cv2.imdecode(img_data, cv2.IMREAD_UNCHANGED) images.append(img) print(f"已加载: {filename}") return images def stitch_images(images, cols, bg_color=(0, 0, 0, 0)): """ 将图像按指定列数拼接成一张大图 :param images: 图像列表 :param cols: 每行显示的图像数量 :param bg_color: 背景颜色(支持 RGBA) :return: 拼接后的图像 """ if not images: raise ValueError("没有图像可供拼接") h, w = images[0].shape[:2] rows = (len(images) + cols - 1) // cols # 创建空白背景图 channels = images[0].shape[2] if len(images[0].shape) == 3 else 1 if channels == 1: bg_color = (bg_color[0], ) # 灰度图 elif channels == 3: bg_color = bg_color[:3] # RGB elif channels == 4: bg_color = bg_color[:4] # RGBA canvas = np.full((h * rows, w * cols, channels), bg_color, dtype=np.uint8) for idx, img in enumerate(images): row = idx // cols col = idx % cols x = col * w y = row * h canvas[y:y+h, x:x+w] = img return canvas def main(): folder = input("请输入包含PNG图像的文件夹路径:").strip() if not os.path.isdir(folder): print("输入的不是有效文件夹路径!") return try: cols = int(input("请输入每行显示的图像数量:")) except ValueError: print("请输入有效的数字!") return output_path = input("请输入输出图像的完整路径(如 output.png):").strip() images = load_images_from_folder(folder) if not images: print("未找到任何PNG图像!") return # 检查所有图像是否尺寸一致 first_h, first_w = images[0].shape[:2] for img in images[1:]: h, w = img.shape[:2] if h != first_h or w != first_w: print("警告:图像尺寸不一致,可能影响拼接效果。") # 拼接图像 print("开始拼接图像...") stitched_image = stitch_images(images, cols) # 保存结果 cv2.imwrite(output_path, stitched_image) print(f"拼接完成,已保存为: {output_path}") if __name__ == '__main__': main() 开发AI桌宠基本规划首先对项目目录和文件进行基本的规划,后续的开发就基于规划进行。目录规划Yez/ -------------------------------------------# 项目主目录 ├─libs/ ---------------------------------------# 依赖库 ├─res/ ----------------------------------------# 资源 │ └─SequenceFrame/ ---------------------------# 序列帧资源 │ └─skip_white/ ---------------------------# 椰椰的白色皮肤资源 ├─src/ ----------------------------------------# 源代码目录 └─tool/ ---------------------------------------# 工具目录文件规划Yez/ ├┈cjpm.lock ├┈cjpm.toml ├─libs/ │ ├┈libimm32.a -------------------------------# Windows 输入法库 │ ├┈resources.rc.o ---------------------------# 资源配置清单 内部声明应用依赖的最低系统版本 │ └┈libRgf.dll -------------------------------# RGF渲染库依赖的动态库 ├─res/ │ ├┈config.json ------------------------------# 椰椰的配置文件 │ └─SequenceFrame/ │ └─skip_white/ │ ├┈blink.json -------------------------# 眨眼合图描述文件 │ ├┈blink.png --------------------------# 眨眼动画合图 │ ├┈jump.json --------------------------# 跳跃合图描述文件 │ ├┈jump.png ---------------------------# 跳跃动画合图 │ ├┈speak.json -------------------------# 说话合图描述文件 │ ├┈speak.png --------------------------# 说话动画合图 │ ├┈wait.json --------------------------# 待机合图描述文件 │ └┈wait.png ---------------------------# 待机动画合图 ├─src/ │ ├┈chat.cj ----------------------------------# 大模型网络通信代码 │ ├┈config.cj --------------------------------# 配置管理代码 │ ├┈main.cj ----------------------------------# 程序主入口代码 │ ├┈res.cj -----------------------------------# 资源管理代码 │ ├┈win_anm.cj -------------------------------# 动画绘制窗口代码 │ ├┈win_input.cj -----------------------------# 输入框窗口代码 │ └┈win_text.cj ------------------------------# 鼠标文本渲染代码 └─tool/ └┈picture_puzzle.py ------------------------# python 合图脚本代码链接文件与库配置在开发之前,需要现为当前项目链接依赖文件和添加依赖库。链接文件在之前的小节中,下载了RGF_UI的依赖文件压缩包,在下载的压缩包内应该包含三个文件,即libRgf.dll、resources.rc.o和libimm32.a。这三个文件中,libRgf.dll是RGF_CJ库的依赖文件,必须随应用打包发布;resources.rc.o文件是Windows资源描述文件,读者也可以自行编写resources.rc文件并生成此文件(需设置最低支持Windows 8,即内核NT 6.2);libimm32.a文件是自绘编辑框控件依赖的文件,主要提供输入法相关的操作能力。在仓颉包管理配置toml文件中,需要加入链接上述文件的代码:[target] [target.x86_64-w64-mingw32] link-option = "-L ./libs -l:libRgf.dll -l user32 -l imm32" 库配置完成链接后,还需要为应用声明依赖RGF_CJ和RGF_UI包,并且为RGF_CJ包配置宏。提示:点击此链接查看依赖库RGF_CJ:https://gitcode.com/Cangjie-SIG/RGF_CJ提示:点击此链接查看依赖库RGF_UI:https://gitcode.com/raozj/RGF_UI[dependencies] [dependencies.rgf] git = "https://gitcode.com/Cangjie-SIG/RGF_CJ.git" [dependencies.rgfui] git = "https://gitcode.com/raozj/RGF_UI.git" [profile] [profile.customized-option] rgfCfg = "--cfg \"RGF_LANG=zh-cn\"" 上述代码中配置的“RGF_LANG=zh-cn”表示:当RGF_CJ出现异常时,捕获输出的异常提示内容为中文。配置加载模块笔者希望桌宠能够具备记忆配置的能力,因此需要为桌宠写一个配置的加载、保存模块,这个模块会记住桌宠所使用的皮肤、最后所在的桌面位置等信息,以确保桌宠重启后依然符合之前的情况。编写一个配置管理相关的恶汉单例类ConfigMgr,单例具备两个方法,一个用于从文件中加载配置,一个用于写出配置到文件中去。(其实也可以直接编写静态方法或直接编写函数而不使用类,看大家自己的选择吧。)代码如下:// Yez/src/config.cj // 配置管理类 class ConfigMgr { private static let _instance:ConfigMgr = ConfigMgr() public static prop i:ConfigMgr{ get(){ _instance } } private init() {} ... /** * 加载配置文件 * @return 是否成功 */ public func readConfig():Bool{ ... } /** 写出配置文件 */ public func writeConfig():Unit{ ... } ... } 编写一个ConfigInfo类,这个类用于存放实际的配置项,并且提供转换为Json的能力,代码如下:// Yez/src/config.cj class ConfigInfo <: Serializable<ConfigInfo> { // 椰椰右下角窗口坐标系的X坐标 var _x:Int32 = Int32.Min // 椰椰右下角窗口坐标系的Y坐标 var _y:Int32 = Int32.Min // 椰椰使用的皮肤 var _skip = "skip_white" // 椰椰画面缩放比例 var _zoom:Float32 = 0.125 // 椰椰的字体大小 var _fontSize:Float32 = 16.1 public func serialize(): DataModel { return DataModelStruct().add(field<Int32>("x", _x)) .add(field<Int32>("y", _y)) .add(field<String>("skip", _skip)) .add(field<Float32>("zoom", _zoom)) .add(field<Float32>("fontSize", _fontSize)) } public static func deserialize(dm: DataModel): ConfigInfo { var dms = match (dm) { case data: DataModelStruct => data case _ => throw Exception("this data is not DataModelStruct") } var result = ConfigInfo() result._x = Int32.deserialize(dms.get("x")) result._y = Int32.deserialize(dms.get("y")) result._skip = String.deserialize(dms.get("skip")) result._zoom = Float32.deserialize(dms.get("zoom")) result._fontSize = Float32.deserialize(dms.get("fontSize")) return result } } 将ConfigInfo以成员变量的形式加入到ConfigMgr中,并且由ConfigMgr对外提供访问属性,代码如下:// Yez/src/config.cj // 配置管理类 class ConfigMgr { // 配置信息 private var configImpl:ConfigInfo = ConfigInfo() ... public mut prop x:Int32{ get(){ return configImpl._x } set(val){ configImpl._x = val } } public mut prop y:Int32{ get(){ return configImpl._y } set(val){ configImpl._y = val } } public mut prop skip:String{ get(){ return configImpl._skip } set(val){ configImpl._skip = val } } public prop zoom:Float32{ get(){ return configImpl._zoom } } public prop fontSize:Float32{ get(){ return configImpl._fontSize } } } 接下来只要完善readConfig和writeConfig的读取和写出逻辑即可,代码如下:// Yez/src/config.cj // 配置文件路径 const CONFIG_FILE_PATH = "./res/config.json" ... public func readConfig():Bool{ // 判断目录是否存在 if(exists(CONFIG_FILE_PATH)){ let seqFramePath = canonicalize(CONFIG_FILE_PATH) let bytes = File.readFrom(seqFramePath) var jv = JsonValue.fromStr(StringReader(ByteBuffer(bytes)).readToEnd()) var dm = DataModel.fromJson(jv) configImpl = ConfigInfo.deserialize(dm) } // 目前始终返回 true // 当配置不存在时,则使用默认配置 return true } public func writeConfig():Unit{ if(!exists(CONFIG_FILE_PATH)){ File.create(CONFIG_FILE_PATH) } let seqFramePath = canonicalize(CONFIG_FILE_PATH) var Anm = configImpl.serialize() var dm = Anm.toJson() File.writeTo(seqFramePath,dm.toString().toArray()) } 最后,在入口函数中启用配置加载与写出即可。// Yez/src/main.cj main(): Int64 { // 加载配置文件数据 if(!ConfigMgr.i.readConfig()){ return 0 } ... // 保存配置到文件中去 ConfigMgr.i.writeConfig() return 0 } 资源加载模块除了配置之外,应用资源也是一个重要的部分,这部分的完整性会影响到应用是否正常渲染。首先采用和Config部分类似的结构准备恶汉单例类ResMgr和动画信息类AnmInfo,代码如下:// Yez/src/res.cj // 资源管理类 class ResMgr { private static let _instance:ResMgr = ResMgr() public static prop i:ResMgr{ get(){ _instance } } private init() {} // 动画 private static const ANM_BLINK ="blink" private static const ANM_JUMP ="jump" private static const ANM_SPEAK ="speak" private static const ANM_WAIT ="wait" private let _skipImg:HashMap<String, Array<(AnmInfo,MemBitmap)>> = HashMap<String, Array<(AnmInfo,MemBitmap)>>() public func loadRes():Bool{ ... } public func unloadRes(){ ... } public prop skip:HashMap<String, Array<(AnmInfo,MemBitmap)>>{ get(){ return _skipImg } } } class AnmInfo <: Serializable<AnmInfo> { // 单帧图片宽度 var width:Int32 = 0 // 单帧图片高度 var height:Int32 = 0 // 合图图片每行图片数量 var hCount:Int32 = 0 // 总帧数 var frames:Int32 = 0 ... } 然后就是实现在loadRes方法中加载合图及合图相关配置文件,代码逻辑还就是:从"./res/SequenceFrame"目录开始确保目录存在,然后遍历SequenceFrame目录下的所有皮肤目录,并且将皮肤目录内的合图资源及对应的配置文件载入到成员中去。如果目录下的资源文件不完整,则不会加载对应的内容到成员中。// Yez/src/res.cj /** * 代码较为复杂且不是核心代码,读者了解功能即可。 * 若需要查看相关代码,可下载项目查看。 */ public func loadRes():Bool{ ... } public func unloadRes(){ //释放所有图像资源(设备无关资源) for(skipPath in _skipImg){ skipPath[1][0][1].release() skipPath[1][1][1].release() skipPath[1][2][1].release() skipPath[1][3][1].release() } } 最后,在入口函数中启用资源加载与释放即可。// Yez/src/main.cj main(): Int64 { ... // 加载依赖资源 if(ResMgr.i.loadRes()){ ... } // 释放一切可能被加载的资源 ResMgr.i.unloadRes() ... return 0 } 大模型模块接下来便是与AI通信的核心部分了,此部分需要完成与大模型的互通。我们需要准备一个LLM类,这个类负责基于API、URL等信息与大模型建立链接,并且解析信息。// Yez/src/chat.cj class LLM { let client: Client let history = StringBuilder() public LLM( // 大模型URL链接 let url!: String, // 大模型 API Key let key!: String, // 大模型类型标识 let model!: String, // 上下文 let context!: Bool = false ) { var config = TlsClientConfig() config.verifyMode = TrustAll client = ClientBuilder() .tlsConfig(config) // AI 服务响应有时候比较慢,这里设置为无限等待 .readTimeout(Duration.Max) .build() } /** * 编码函数,将角色与内容编码为字符串 * return 字符串 */ func encode(role: Role, content: String) { ... } /** * 发送函数,将对话数据发送到服务器 * return HttpResponse */ func send(input: String, stream!: Bool = false) { ... } /** * 分析函数,解析服务器返回的数据并且提取其中有效的文本内容 * return 有效内容 */ func parse(text: String, stream!: Bool = false) { ... } /** * 流式对话 * @param input 输入的文本 * @param task 流式返回内容的回调函数 * @param taskEnd 返回内容结束的回调函数 */ public func chat(input: String, task!: (String) -> Unit = {o => print(o)},taskEnd!:()->Unit = {=>}) { ... } /** * 非流式对话 */ public func chat(input!: String) { ... } /** * 历史记忆预置 */ public func preset(context: Array<(Role, String)>) { ... } /** * 历史记忆清除 */ public func reset() { ... } } 根据华为云提供的调用参考,完善 send 方法代码如下:// Yez/src/chat.cj func send(input: String, stream!: Bool = false) { let message = encode(I, input) let content = '{"model":"${model}","messages":[${history}${message}],"stream":${stream}}' if (context) { history.append(message) } let request = HttpRequestBuilder() .url(url) .header('Authorization', 'Bearer ${key}') .header('Content-Type', 'application/json') .header('Accept', if (stream) { 'text/event-stream' } else { 'application/json' }) .body(content) .post() .build() client.send(request) } 根据华为云返回的数据格式,完善 parse 方法代码如下:// Yez/src/chat.cj func parse(text: String, stream!: Bool = false) { let json = JsonValue.fromStr(text).asObject() let choices = json.getFields()['choices'].asArray() if(choices.size() == 0){ return "" } // 流式和非流式情况下,这个字段名称不同 let key = if (stream) { 'delta' } else { 'message' } let message = choices[0].asObject().getFields()[key].asObject() if(message.containsKey("content")){ let content = message.getFields()['content'].asString().getValue() // 移除开头的两个 \n if(content == "\n\n"){ return "" } return content }else{ return "" } } 最后实现核心的流式对话部分,代码如下:// Yez/src/chat.cj public func chat(input: String, task!: (String) -> Unit = {o => print(o)},taskEnd!:()->Unit = {=>}) { // 根据传入的字符串构建上发服务器的 HttpResponse let response = send(input, stream: true) // 准备一个字符串缓冲区 let output = StringBuilder() // 准备一个字节缓冲区 let buffer = Array<Byte>(1024 * 8, {i=>0}) // 获取返回的内容 var length = response.body.read(buffer) while (length != 0) { // 将字节缓冲区内的内容按照UTF-8编码解析为字符串 let text = String.fromUtf8(buffer[..length]) const INDEX = 6 // 按行分割 for (line in text.split('\n', removeEmpty: true)) { // 确保获取的是完整的:data: {...} if (line.size > INDEX && line[INDEX] == b'{' && line[line.size - 1] == b'}') { let json = line[INDEX..line.size] // 分析出有效信息 let slice = parse(json, stream: true) if (context) { output.append(slice) } // 若有效信息不为空,则通过回调函数返回 if(slice != ""){ task(slice) } } } length = response.body.read(buffer) } if (context) { history.append(',${encode(AI, output.toString())},') } // 所有处理结束后,执行结束回调 taskEnd() } LLM类是一个较为通用的类,但是使用较为繁琐,因此对于外部调用而言,只需要一个简化的实例即可。因此,可以再编写一个独立的懒汉单例LLMImpl类,并且仅提供一个对外实现流式对话的chat方法,代码如下:// Yez/src/chat.cj class LLMImpl { private static var _i: Option<LLMImpl> = None public static prop i:LLMImpl { get(){ if(_i.isNone()){ _i = LLMImpl() } return _i.getOrThrow() } } private let robot:LLM // 初始化一个对话模型 private init(){ robot = LLM( // 华为云大模型调用URL url: 'https://maas-cn-southwest-2.modelarts-maas.com/v1/infers/8a062fd4-7367-4ab4-a936-5eeb8fb821c4/v1/chat/completions', // 如果示例自带的密钥失效,请自行注册,https://www.huaweicloud.com/product/modelarts/studio.html key: '/* !!! 这里填写读者自己的 API Key !!! */', // 要使用的 大模型 model: 'DeepSeek-R1', context: true ); // 设定基本信息 robot.preset([(System, #"我会自称“椰椰”并亲昵的在三句话之内回复所有问题。对于限定长度内无法阐述清晰的问题,我会表现出呆傻可爱的效果并说出“宕机”。如果对我说“拍照”或“截图”,我就会说“咔嚓~”。如果问我是谁开发的,我会说出“尧佥&椰子”。"#)]) } /** * 流式对话 * @param content 输入的文本 * @param task 流式返回内容的回调函数 * @param end 返回内容结束的回调函数 */ public func chat(content:String, task!: (String) -> Unit = {o => print(o)},end!:()->Unit = {=>}){ robot.chat(content,task:task,taskEnd:end) } } 注’ 完整的代码请参考提供的代码文件桌宠渲染接下来,需要实现可视化的窗口渲染,赋予“椰椰”一副“身体”。动画播放窗口为了实现AI桌宠的渲染,首先需要有一个窗口。对此,笔者选择基于RGF_CJ中的WinBase类,实现一个自己的窗口类WinMain。// Yez/src/win_anm.cj // 使用 RGF_CJ 的宏实现代码简化 @LifeCycle // 自动管理 设备相关资源 的释放 @HookProc // 自动挂载窗口类的消息事件方法 class WinMain <: WinBase{ ... // 窗口类初始化时,传入 ResMgr 载入的资源(设备无关资源) init(anm:Array<(AnmInfo, MemBitmap)>){ _anmRes = anm } /** * 加载设备无关资源 -> 设备相关资源 * @note 本部分将资源ResMgr类中的图片资源,载入到窗口类中 */ private func loadAnm(anm:Array<(AnmInfo, MemBitmap)>){ surface .createBitmapFromMemory(_bmps[0],anm[0][1]) .createBitmapFromMemory(_bmps[1],anm[1][1]) .createBitmapFromMemory(_bmps[2],anm[2][1]) .createBitmapFromMemory(_bmps[3],anm[3][1]) } /** * 创建设备资源事件,窗口渲染表面创建完成后,开始载入资源 */ public func createDeviceResources():Bool{ // 这里调用 loadAnm 载入图片资源 loadAnm(_anmRes) return true; } /** * 窗口创建完成事件,当窗口创建完成时,开始渲染动画 */ public func created(hwnd:RgfHwnd,x:Int32, y:Int32, nWidth:Int32, nHeight:Int32):Unit{ ... } /** * 绘制窗口事件,此处根据当前的动画状态渲染窗口 */ public override func onPaint(wRect:Rect):Unit{ surface.clear(0.0,0.0,0.0,0.0) match(_nowState){ case Wait => {=> // 等待状态的动画渲染比较特殊,因为需要实现眼睛跟随鼠标移动 if(_bmps[3].isValid() && _bmps[0].isValid()){ // 这一部分计算当前渲染帧的静态部分,即:身体, // 并且将这部分内容渲染到窗口 .... // 然后下面计算出鼠标对眼睛造成的偏移量 // 并且将眼睛渲染到身体上 if(_anmStep < _anmRes[0][0].frames){ .... }else{ .... } } }() case Jump => {=> // 这里渲染椰椰的跳跃动画 if(_bmps[1].isValid()){ .... } }() case Speak => {=> // 这里渲染椰椰正在说话的动画 if(_bmps[2].isValid()){ .... } }() } } } 在入口函数中,启用UIMain并且创建窗口,代码如下:// Yez/src/win_anm.cj main(): Int64 { ... if(ResMgr.i.loadRes()){ // 开始创建UI UIMain{=> // 确保皮肤配置有效 if(!ResMgr.i.skip.contains(ConfigMgr.i.skip)){ ConfigMgr.i.skip = ResMgr.i.skip.keys().toArray()[0] } // 创建窗口类 let anmWin:WinMain = WinMain(ResMgr.i.skip.get(ConfigMgr.i.skip).getOrThrow()) // 先随便在桌面 (0,0) 的位置创建一个 10像素×10像素 的窗口 // 窗口类内部会在显示前根据配置中的信息自动布局到正确位置和尺寸 anmWin.createWin(uiGetContext(), "Yez", 0, 0, 10, 10, 0, WinStyle.e2n(WinStyle.Popup), // 使用的是 TopMost 分层窗口(支持透明背景) dwExStyle:WinStyleEx.Default | WinStyleEx.TopMost, // 渲染使用的是 Direct2D 计数 layered:true,substrate:Substrate.Direct2D ) } } ... } 完成上述代码后,窗口已经可以正确的渲染动画了注’ 完整的代码请参考提供的代码文件编辑框窗口对于编辑框的开发是比较繁琐的,因此选择直接继承RGF_UI的UIInput类,然后将此控件修改为窗口,首先需要创建一个编辑框窗口类InputWin,代码如下:// Yez/src/win_input.cj public open class InputWin <: UIInput{ // 为编辑框设置独立的UI样式,而不是继承全局的样式 let css:UITheme = UITheme() init(){ super(theme:css) // 从配置中获取字体大小 css.fontSizeM = ConfigMgr.i.fontSize // 更新样式配置 css.update() } /** * 获取输入框窗口预期出现的位置 * @return 编辑框窗口坐标 * @note 本部分计算编辑框在当前椰椰所在桌面中心时的位置 */ private func getTargetLocation():Point{ // 方案二、桌面中心 let monitor = rsGetMonitorFromPoint(rsGetCursorPos()) let pos = Point( ((monitor.right - monitor.left) - INPUT_WIN_WIDTH) / 2, ((monitor.bottom - monitor.top) - INPUT_WIN_HEIGHT) / 2 ) return pos } /** * 创建窗口 */ public func createWin(hwndParent:RgfHwnd):RgfHwnd{ // 获取出现位置坐标 let pos = getTargetLocation() // 创建一个置顶层的无边框编辑框窗口 let retv = super.createWin( uiGetContext(),"",pos.x,pos.y,INPUT_WIN_WIDTH,INPUT_WIN_HEIGHT,hwndParent, WinStyle.PopupWindow | WinStyle.ClipChildren, dwExStyle:WinStyleEx.Default | WinStyleEx.TopMost, substrate:Substrate.Direct2D,layered:false,bgMod:BackGroundMod.CoverNotDraw,drawInterval:0 ) // 设置默认的内容为:“想对椰椰说什么:” content = INPUT_PROMPT // 创建好就显示窗口 show(true) return retv } } 完成编辑框窗口的创建后,还需要能响应相关的输入事件。对此,笔者希望:a) 当编辑框被按下或输入内容时,如果内容为默认内容,则清空提示内容;b) 当编辑框失去焦点时,窗口主动销毁自身;c) 当编辑框按下回车键时,将编辑框内容上发给大模型,并开始处理返回消息。实现自动清空内容很简单,代码如下:// Yez/src/win_input.cj /** 鼠标左键按下时,判断内容并清空 */ public override open func onLButtonDown(e:EvMouse):Unit{ if(content == INPUT_PROMPT){ content = "" } super.onLButtonDown(e) } /** 键盘按下某键时,判断内容并清空 */ public override open func onKeyDown(e:EvKey):Unit{ if(content == INPUT_PROMPT){ content = "" } super.onKeyDown(e) } 得益于RGF_CJ库的安全性,窗口销毁会自动释放资源,避免了繁琐的资源释放,和释放时机的管理。实现窗口失去焦点就是否窗口的代码如下:// Yez/src/win_input.cj /** 窗口失去焦点时,销毁窗口并释放窗口资源 */ public override open func onKillFocus(e:EvKillFocus):Unit{ destroyWin() } 希望实现按下回车键触发对话,则需要在对应的事件中调用LLMImpl类的流式对话方法,代码如下:// Yez/src/win_input.cj /** 字符输入事件 */ public override open func onChar(e:EvChar):Unit{ // 判断输入的是否为回车键 if(e.char == 0x0D/*VK_RETURN*/){ // 是回车键就销毁本窗口 destroyWin() // 并且调用 开始回答函数 start() // 然后调用 LLMImpl 类的流式对话方法 // 并且传入 流的回调函数 以及 回答结束的函数 LLMImpl.i.chat(content,task:task,end:end) }else{ super.onChar(e) } } // 回复流 public var task:(String) -> Unit = {str=>} // 开始回答 public var start:() -> Unit = {=>} // 开始回答 public var end:() -> Unit = {=>} 编辑框控件的效果如下图所示:注’ 完整的代码请参考提供的代码文件反馈窗口完成上述内容后,输入与处理已经基本完备,接下来需要实现文本的显示,这一部分涉及到内容绘制,同样还是基于WinBase类进行窗口开发。创建一个反馈文本的窗口类 WinText,并且准备基本的资源代码如下:// Yez/src/win_text.cj @Abbr // 文本色彩值转Color对象宏 @LifeCycle @HookProc open class WinText <: WinBase{ // 内容纯色画刷 private let _brhCnt:SolidColorBrush // 背景纯色画刷 private let _brhbg:SolidColorBrush // 文本渲染核心 private let _textCore:TextCore // 文本格式 private let _textformat:TextFormat // 文本布局 private let _textLayout:TextLayout // 文本内容 private var _text:String = "" /** 创建窗口 */ public func createWin( x:Int32, y:Int32, nWidth:Int32, nHeight:Int32, hWndParent:RgfHwnd, substrate!:Substrate = Substrate.Direct2D ):RgfHwnd{ ... } /** 文本对象管理方法,根据配置和窗口尺寸生成文本渲染依赖的对象 */ private func createFontObj(){ let rect:Rect = surface.getRect() surface.createTextCore(_textCore) .createTextFormat(_textformat,ConfigMgr.i.fontSize) .createTextLayout(_textLayout,_text,_textformat,Float32(rect.right),Float32(rect.bottom)) } /** 创建设备/渲染相关资源事件 */ public override open func createDeviceResources():Bool{ // 创建 纯黑内容画刷 和 白色半透明背景画刷 surface.createSolidColorBrush(_brhCnt,"#000",1.0) .createSolidColorBrush(_brhbg,"#FFFA",1.0) // 创建文本相关对象 createFontObj() return true; } } 完成渲染事件,确保内容正确渲染// Yez/src/win_text.cj // 绘制窗口 public override open func onPaint(wRect:Rect):Unit{ surface.clear(0.0,0.0,0.0,0.0) // 透明模式 // 渲染背景圆角矩形 surface.fillRoundedRectangle(surface.getRect(),4.0,4.0,_brhbg) // 渲染大模型返回的文本内容 surface.drawTexts(_textCore,_brhCnt,_textLayout,originX:0.0,originY:0.0) } 完成显示文本方法。当设置文本时,若窗口类已经存在,就更新内部文本;若窗口不存在,就根据当前内容创建窗口并显示。// Yez/src/win_text.cj public func showText(str:String,p:RgfHwnd):Unit{ // 获取光标位置 let pos = rsGetCursorPos() // 获取光标所在屏幕矩形范围 let monitor = rsGetMonitorFromPoint(pos) // 如果窗口未创建,就创建窗口 if(!isCreated()){ createWin(pos.x,pos.y,100,100,p) } if(_textLayout.isValid()){ if(str != _text){ // 更新文本内容 _text = str; // 创建文本布局信息 surface.createTextLayout(_textLayout,_text,_textformat,Float32(monitor.right - monitor.left) / 2.0,Float32(monitor.bottom - monitor.top) / 2.0) } // 获取文本自动布局后的测量数据 let m = _textLayout.getMetrics() let w = Int32(m.getOrThrow().width) let h = Int32(m.getOrThrow().height) 按照测量数据移动窗口 if(pos.x > (monitor.right - monitor.left) / 2){ move(pos.x - w,pos.y - h,w,h) }else{ move(pos.x,pos.y - h,w,h) } } // 窗口显示 show(true) } 文本控件的效果如下图所示:注’ 完整的代码请参考提供的代码文件联结各个窗口逻辑完成上述各个控件后,需要实现各个组件之间的联合。实现流式信息与动画的联动// Yez/src/win_anm.cj /** 文本变更事件 */ func onTextChange(str:String):Unit{ if(!_firstText){ _firstText = true // 当文本输出开始,切换“跳跃”动画状态到“说话”动画状态 play(AnmState.Speak) } _text += str _textBox.showText(_text,hWnd) } /** 大模型开始输出文本 */ func onTextStart():Unit{ killTimer(0) _text = "" // 给大模型上发请求后,播放“跳跃”动画 play(AnmState.Jump) _firstText = false } /** 大模型结束输出文本 */ func onTextEnd():Unit{ // 大模型输出结束后,延迟5s,以让用户完整阅读完内容 const cj_bug_val:Option<CFunc<(HWND, UInt32, UInt64, UInt32) -> Unit>> = None setTimer(0,5000,cj_bug_val) } /** 时钟事件 */ func onTimer(e:EvTimer):Unit{ killTimer(0) // 用户阅读完内容后,销毁文本框窗口 _textBox.destroyWin() // 播放“等待”动画 play(AnmState.Wait) } 双击“椰椰”时打开输入框窗口// Yez/src/win_anm.cj // 客户区双击鼠标左键 func onLButtonDblClk(e:EvMouse):Unit{ // 在“等待”状态下双击“椰椰” match(_nowState){ case AnmState.Wait => {=> // 销毁文本框(虽然已无可能还显示,不过保险起见) _textBox.destroyWin() if(!_inputBox.isCreated()){ // 创建文本输入框窗口 _inputBox.createWin(hWnd) }else{ // 如果窗口存在就移动输入框窗口 // 这部分适用于希望输入框跟随鼠标位置的情况 // 对于方案二的桌面居中而言,没有必要 _inputBox.move() } () }() case _ => () } } 结语至此,仓颉AI桌宠“椰椰”就基本开发完成了,其实还有很多规划的内容可以增加,比如当和椰椰说“咔嚓”或者“拍照”时激活截图功能。这些内容就留给读者继续探索啦!本文相关链接本文项目代码库:https://gitcode.com/raozj/YezRGF_CJ库:https://gitcode.com/Cangjie-SIG/RGF_CJRGF_UI库:https://gitcode.com/raozj/RGF_UIRGF案例库:https://gitcode.com/raozj/RGF_CJ_Example华为云ModelArts:https://www.huaweicloud.com/product/modelarts/studio.html
  • [技术干货] 赛题解读|2025年度中国青年“揭榜挂帅”擂台赛·华为赛道
    挑战杯是“挑战杯”全国大学生系列科技学术竞赛的简称,是由共青团中央、中国科协、教育部和全国学联共同主办的全国性的大学生课外学术实践竞赛。今年,本届大赛调整为中国青年科技创新“揭榜挂帅”擂台赛,旨在进一步围绕“硬科技”创新、“卡脖子”关键核心技术攻关。华为深度参与本届“揭榜挂帅”,助力科技创新,设置22个奖项。总奖金池30万元,其中擂主团队奖金10万元。获奖者更有机会进入华为人才储备池,并优先获得实习及就业的推荐机会。 详情请点击:cid:link_001 赛题介绍赛题名称:推理大模型的训练调优与性能加速助力全栈自主AI赛题背景在大语言模型(LLMs)迅速发展的浪潮中,推理能力和运行效率的显著进步正推动着AI技术走向更广泛的应用领域。2025年,以OpenAI的o1系列和DeepSeek R1为代表的模型,不仅在复杂任务如数学解题、编程上展现出卓越能力,还通过技术创新如量化、模型并行(MOE)、知识蒸馏等,大幅提升了模型在轻量化设备上的部署可行性。 华为命题旨在应用全栈自主AI开发工具链,基于昇腾AI云服务、Ascend-snt9b AI芯片、CANN异构计算架构和ModelArts开发平台,提升轻量级模型的推理能力和性能。参赛者将利用强化学习、微调、知识蒸馏、量化等方法优化指定模型,同时探索模型部署于端侧设备的潜力,促进模型在实际场景中的应用。 赛题说明本次比赛分为初赛和总决赛,具体要求如下:初赛:组委会提供Qwen轻量级模型和示例工程,参赛团队选择强化学习(如PPO)或知识蒸馏技术,在ModelArts环境中对模型进行微调,同时优化推理性能。初赛阶段分为A榜和B榜,其中B榜的性能部分要求选手必须有算子融合、优化方面的工作。初赛最终排名以B榜成绩和代码核查结果为准,筛选出一定数量的队伍入围。总决赛(终审擂台赛):参赛者需准备PPT进行答辩,介绍技术方案和应用demo,阐述推理能力和性能优化的方法及其实际应用场景。酌情加分项:使用华为开发者空间提供的相关资源和服务、端侧应用鸿蒙适配、及其他华为开放能力调用。 * 具体的指导文档点击文末「 阅读原文 」 获取赛题详情哦! 02 技术能力华为昇腾AI云服务技术能力解读全栈协同优化,构建AI云底座:昇腾AI云服务重构了云基础设施,利用多样化算力池、高性能缓存池和分级存储池等多层池化技术,减少由上到下的计算代价,大大提升了资源利用率,充分释放了昇腾算力规模。 全套工具链,让AI落地更简单:AI开发生产线ModelArts,提供了包括昇腾工具链、SDK、CLI、IDE插件等端到端生产工具链,支持数据管理、模型开发、训练、推理等全流程MLOps开发,提供了统一资源调度能力,提升AI开发效率,降低AI开发门槛和成本。 开箱即用,一站式大模型开发服务:ModelArts Studio大模型即服务平台(简称MaaS),提供了简单易用的模型开发工具链,支持大模型定制开发,让模型应用与业务系统无缝衔接,降低企业AI落地的成本与难度。 * ModelArts Studio大模型即服务平台:cid:link_1华为昇腾计算技术能力解读极致性能,极简应用:CANN是昇腾针对AI场景推出的异构计算架构,向上支持多种AI框架,包括MindSpore、PyTorch等,向下服务AI处理器与编程,发挥承上启下的关键作用,是提升昇腾AI处理器计算效率的关键平台。 * CANN 助力构筑昇腾AI算力平台:cid:link_3可视化,插件化:MindStudio是华为面向昇腾AI开发者提供的全流程工具链,致力于提供端到端的昇腾AI应用开发解决方案,使能开发者高效完成模型迁移与调优、模型转换与压缩、算子开发与编译等。 * MindStudio 全流程开发工具链:cid:link_2基于上述能力,参赛开发者可以提升开发效率:使用昇腾AI云服务,开发者可以在完善的工具链基础上,实现从数据处理、算法训练到模型部署的全流程开发,更快地进行模型迭代。 提升模型性能:借助高效的模型迁移调优工具链、硬件亲和的高性能算子,以及开放的自定义算子开发能力,开发者可快速提升模型推理性能。 提升应用效果:借助优质稳定的基础大模型,使用便捷的微调和测评工具,开发者可以快速构建自己的大模型应用,提高任务精度。 03 应用场景智能终端交互手机/平板AI助手:通过轻量化模型优化(如量化、知识蒸馏),可在端侧实现低延迟的语音交互、实时翻译、日程管理等,避免云端依赖。智能家居控制:优化后的模型可部署在家庭网关或边缘设备中,实现本地化自然语言指令解析(如“调节空调温度”),提升隐私性与响应速度。金融与客服智能客服系统:通过知识蒸馏压缩模型,在银行APP或呼叫中心本地部署,实现低延迟的合规性问答与交易指导。风险实时监控:轻量化模型分析交易流水,快速识别欺诈行为,提升边缘计算场景下的响应效率。医疗与生物技术电子病历分析:在医疗终端部署优化模型,辅助医生快速生成诊断建议(需结合联邦学习保障数据安全)。基因数据推理:通过模型并行技术加速基因组序列分析,缩短科研周期。 04 技术案例美图随着美图AIGC应用版图扩大,算力供应十分紧张,华为云为美图提供从昇腾AI云服务、大模型到上层应用的全方位支持,与美图共建全栈AI解决方案。通过华为云ModelArts一站式AI开发平台,为美图提供数据处理、算法开发、模型训练、模型管理、模型部署等全流程AI开发技术能力,满足复杂场景需求。千卡集群线性度95%、昇腾云脑1000+故障模式、故障发现<1min等领先的AI基础设施能力,保障美图的大规模训练快速迭代。
  • [大赛资讯] 重磅来袭!2025年度中国青年“揭榜挂帅”擂台赛·华为赛道邀你来战!
    挑战杯是“挑战杯”全国大学生系列科技学术竞赛的简称,是由共青团中央、中国科协、教育部和全国学联共同主办的全国性的大学生课外学术实践竞赛。团中央于 2021 年首次在第十七届“挑战杯”全国大学生课外学术科技作品竞赛中设置了“揭榜挂帅”专项赛。三届大赛来,累计吸引来自全国上千所高校、数万名学子踊跃参与。为汇聚更多青年科创人才的智慧力量,本届大赛调整为中国青年科技创新“揭榜挂帅”擂台赛,旨在进一步围绕“硬科技”创新、“卡脖子”关键核心技术攻关。2025年度中国青年“揭榜挂帅”擂台赛·华为赛道火热来袭!本次比赛聚焦大模型的推理能力提升和性能优化,参赛团队需基于昇腾全栈AI技术,使用强化学习、知识蒸馏等技术,提升选定的轻量级模型在数学计算、逻辑推理、代码生成等任务上的能力,并通过算子融合与优化、模型结构优化、量化等技术,保证精度的同时提升模型推理性能。诚邀各大高校精英云端竞技,打擂揭榜。一、即刻解锁赛事全攻略关于本次大赛,我们已为你整理总结了赛程安排、详细的参赛指导等选手关注的信息,点击海报查看。二、组队闯关!届时开放精英社群��想Get技术文档?寻找最强队友?与华为专家实时互动?扫码添加小助手,回复暗号“挑战杯”秒入群!在这里,与全国顶尖开发者共研大模型推理优化方案,一起竞技打擂,开发创新应用,助力全栈自主AI技术的新篇章!详情请点击:cid:link_0直达报名通道,攀登技术巅峰!
  • 移动携手华为发布移动云信息技术融合应用创新产业生态联合体
    4月25日,由中国移动通信集团主办的“云擎未来 智信天下”2023移动云大会在苏州金鸡湖国际会议中心顺利召开。华为技术有限公司高级副总裁曹既斌受邀出席此次大会,并参加了移动云信息技术融合应用创新产业生态联合体发布仪式。移动云信息技术融合应用创新产业生态联合体的发布,标志着中国移动将凝聚合力共同构建开放包容、合作共赢的产业生态。当前,中国移动与华为正迈入新的合作周期,华为也将秉持"一切皆服务"的战略,与移动云强强联合,持续做强技术底座,为产业创造更多价值,为建设数字中国注入澎湃动能。        在本届移动云大会上,华为云计算技术有限公司副总裁黄瑾做了主题为“一切皆服务,共促云产业,深耕数智化”的演讲,阐述了华为云对于产业的思考,业务战略,以及与移动云合作的成果及未来展望。“在数字转型的关键时刻,华为云通过一切皆服务,共创行业新价值,助力企业数智化转型,共建繁荣生态共赢。中国移动和华为在云计算领域的战略合作,取得较好进展,践行“一切皆服务”,把研发投入所产生的技术和产品、数字化转型经验,伙伴的能力,都开放在云上作为云服务,提升竞争力,同时更好的共同服务客户。”
总条数:3403 到第
上滑加载中