首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

Solon AI 开发学习 5 - chat - 支持哪些模型?及方言定制

1、支持哪些聊天模型? 支持聊天模型,其实是支持接口风格。比如 DeepSeek-V3 官网的接口兼容 openai;在 ollama 平台是另一种接口风格;在阿里百炼则有两种接口风格,一种兼容 openai,另一种则是百炼专属风格;在模力方舟(ai.gitee)则是兼容 openai。 聊天模型的这种接口风格,称为聊天方言(简称,方言)。ChatConfig 通过provider或apiUrl识别模型服务是由谁提供的。并自动选择对应的聊天方言适配。 框架内置的方言适配有: 言方 配置要求 描述 openai 兼容 openai 的接口规范(默认) ollama provider=ollama 兼容 ollama 的接口规范 dashscope provider=dashscope 兼容 dashscope (阿里云的平台百炼)的接口规范(v3.5.1 后基于 apiUrl 自动识别,不要配置 provider) 那支持哪些聊天模型? 所有兼容 openai 的平台服务(比如“模力方舟”、“硅基流动”、“魔搭社区(魔力空间)”、“Xinference”、“火山引擎”、“智谱”、“讯飞火星”、“百度千帆”、“阿里百炼” 等),都兼容 所有 ollama 平台上的模型,都兼容 所有 阿里百炼 平台上的模型(同时提供有 “百炼” 和 “openai” 两套接口),都兼容 构建示例: ChatModel chatModel = ChatModel.of("http://127.0.0.1:11434/api/chat") //使用完整地址(而不是 api_base) .provider("ollama") .model("llama3.2") .headerSet("x-demo", "demo1") .build(); 2、自带的方言依赖包 方言依赖包 描述 org.noear:solon-ai 包含 solon-ai-core 和下面所有的方言包。一般引用这个 org.noear:solon-ai-dialect-openai 兼容 openai 的方言包 org.noear:solon-ai-dialect-ollama 兼容 ollama 的方言包 org.noear:solon-ai-dialect-dashscope 兼容 dashscope 的方言包 提醒:一般匹配不到方言时?要么是 provider 配置有问题,要么是 pom 缺少相关的依赖包。 3、聊天方言接口定义 public interface ChatDialect extends AiModelDialect { //是否为默认 default boolean isDefault() { return false; } //匹配检测 boolean matched(ChatConfig config); //构建请求数据 String buildRequestJson(ChatConfig config, ChatOptions options, List<ChatMessage> messages, boolean isStream); //构建助理消息节点 ONode buildAssistantMessageNode(Map<Integer, ToolCallBuilder> toolCallBuilders); //构建助理消息根据直接返回的工具消息 AssistantMessage buildAssistantMessageByToolMessages(List<ToolMessage> toolMessages); //分析响应数据 boolean parseResponseJson(ChatConfig config, ChatResponseDefault resp, String respJson); //分析工具调用 List<AssistantMessage> parseAssistantMessage(ChatResponseDefault resp, ONode oMessage); } 3、OllamaChatDialect 定制参考 如果方言有组件注解,会自动注册。否则,需要手动注册: ChatDialectManager.register(new OllamaChatDialect()); 方言定制参考: import org.noear.snack4.ONode; import org.noear.solon.Utils; import org.noear.solon.ai.AiMedia; import org.noear.solon.ai.AiUsage; import org.noear.solon.ai.media.Audio; import org.noear.solon.ai.chat.ChatChoice; import org.noear.solon.ai.chat.ChatConfig; import org.noear.solon.ai.chat.ChatException; import org.noear.solon.ai.chat.ChatResponseDefault; import org.noear.solon.ai.chat.dialect.AbstractChatDialect; import org.noear.solon.ai.chat.message.AssistantMessage; import org.noear.solon.ai.chat.message.UserMessage; import org.noear.solon.ai.chat.tool.ToolCall; import org.noear.solon.ai.chat.tool.ToolCallBuilder; import org.noear.solon.ai.media.Image; import org.noear.solon.ai.media.Video; import org.noear.solon.core.util.DateUtil; import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * Ollama 聊天模型方言 * * @author noear * @since 3.1 */ //@Component public class OllamaChatDialect extends AbstractChatDialect { private static OllamaChatDialect instance = new OllamaChatDialect(); public static OllamaChatDialect getInstance() { return instance; } /** * 匹配检测 * * @param config 聊天配置 */ @Override public boolean matched(ChatConfig config) { return "ollama".equals(config.getProvider()); } @Override protected void buildChatMessageNodeDo(ONode oNode, UserMessage msg) { oNode.set("role", msg.getRole().name().toLowerCase()); if (Utils.isEmpty(msg.getMedias())) { oNode.set("content", msg.getContent()); } else { oNode.set("content", msg.getContent()); AiMedia demo = msg.getMedias().get(0); if (demo instanceof Image) { oNode.set("images", msg.getMedias().stream().map(i -> i.toDataString(false)).collect(Collectors.toList())); } else if (demo instanceof Audio) { oNode.set("audios", msg.getMedias().stream().map(i -> i.toDataString(false)).collect(Collectors.toList())); } else if (demo instanceof Video) { oNode.set("videos", msg.getMedias().stream().map(i -> i.toDataString(false)).collect(Collectors.toList())); } } } @Override public ONode buildAssistantMessageNode(Map<Integer, ToolCallBuilder> toolCallBuilders) { ONode oNode = new ONode(); oNode.set("role", "assistant"); oNode.set("content", ""); oNode.getOrNew("tool_calls").asArray().then(n1 -> { for (Map.Entry<Integer, ToolCallBuilder> kv : toolCallBuilders.entrySet()) { //有可能没有 n1.addNew().set("id", kv.getValue().idBuilder.toString()) .set("type", "function") .getOrNew("function").then(n2 -> { n2.set("name", kv.getValue().nameBuilder.toString()); n2.set("arguments", ONode.ofJson(kv.getValue().argumentsBuilder.toString())); }); } }); return oNode; } @Override public boolean parseResponseJson(ChatConfig config, ChatResponseDefault resp, String json) { //解析 ONode oResp = ONode.ofJson(json); if (oResp.isObject() == false) { return false; } if (oResp.hasKey("error")) { resp.setError(new ChatException(oResp.get("error").getString())); } else { resp.setModel(oResp.get("model").getString()); resp.setFinished(oResp.get("done").getBoolean()); String done_reason = oResp.get("done_reason").getString(); String createdStr = oResp.get("created_at").getString(); if (createdStr != null) { createdStr = createdStr.substring(0, createdStr.indexOf(".") + 4); } Date created = DateUtil.parseTry(createdStr); List<AssistantMessage> messageList = parseAssistantMessage(resp, oResp.get("message")); for (AssistantMessage msg1 : messageList) { resp.addChoice(new ChatChoice(0, created, done_reason, msg1)); } if (resp.isFinished()) { long promptTokens = oResp.get("prompt_eval_count").getLong(); long completionTokens = oResp.get("eval_count").getLong(); long totalTokens = promptTokens + completionTokens; resp.setUsage(new AiUsage(promptTokens, completionTokens, totalTokens, oResp)); if(resp.hasChoices() == false) { resp.addChoice(new ChatChoice(0, created, "stop", new AssistantMessage(""))); } } } return true; } @Override protected ToolCall parseToolCall(ONode n1) { int index = -1; //n1.get("index").getInt();它是没有值的 String callId = n1.get("id").getString(); ONode n1f = n1.get("function"); String name = n1f.get("name").getString(); ONode n1fArgs = n1f.get("arguments"); String argStr = n1fArgs.getString(); index = name.hashCode(); if (n1fArgs.isValue()) { //有可能是 json string n1fArgs = ONode.ofJson(argStr); } Map<String, Object> argMap = null; if (n1fArgs.isObject()) { argMap = n1fArgs.toBean(Map.class); } return new ToolCall(index, callId, name, argStr, argMap); } }

优秀的个人博客,低调大师

DDD 学习与感悟 —— 总是觉得自己在 CRUD 怎么办?

一、DDD是什么? DDD全名叫做Domins drives Design;领域驱动设计。再说的通俗一点就是:通过领域建模的方式来实现软件设计。 问题来了:什么是软件设计?为什么要进行软件设计? 软件开发最主要的目的就是:解决一个问题(业务)而产生的一个交付物(系统)。而软件设计旨在高效的实现复杂项目软件。也就是说软件设计是从业务到系统之间的桥梁。 而DDD则是在复杂业务场景下一种更高效更合理的软件设计思维方式和方法论。 二、以前的软件设计思维是什么? 绝大部分从事软件开发的人,不管是在学校还是刚开始工作,都是从ER图开始。即直接通过业务设计数据库模型和数据关联关系。这种思维根深蒂固的印在了这些人的头脑里(包括我自己)。因此在软件设计过程中习惯性的直接将业务转化为数据模型,面向数据开发。也就是我们所说的CRUD。我们有时候也会看到一些博客看到或者听到一些同事在说:这个业务有什么难的,不就是CRUD么? 不可否认的是,在软件生命周期初期,通过CRUD这种方式我们可以快速的实现业务规则,交付项目。然而一个系统的生命周期是很长的并且维护阶段的生命周期占绝大部分比例。 随着业务的发展,业务规则越来越复杂,通过CRUD这种粗暴方式,让工程代码越来越复杂,通常一个方法可能会出现几百甚至上千行代码,各种胶水代码和业务逻辑混合在一起,导致很难理解。 这种系统交接给另一个同学或者新进来的同学后,可能需要花费很长的时间才能理解这个方法,原因就是因为这种胶水代码淹没了业务核心规则。所以在现实场景中,我们经常会听到,上一个开发是SB,或者自嘲自己是在屎山上面继续堆屎。 三、DDD思想下的软件设计 DDD的思想是基于领域模型来实现软件设计。那么,什么是领域模型?领域模型怎么得来呢? DDD思想,将软件的复杂程度提前到了设计阶段。基于DDD思想,我们的设计方式完全变了。 统一语言 首先,将业务方、领域专家以及相关的产研人员都聚拢在一起,共同探讨出业务场景和要解决的问题,统一语言。来确保所有人对于业务的理解都是一致的。 这里的统一语言不是指某种具体的技术语言,而是一种业务规则语言。所有人必须要能够理解这种统一语言。 战略设计 其次,我们根据待解决的问题空间,进行战略设计。所谓的战略设计就是根据问题空间在宏观层面识别出限界上下文。比如说一个电商业务,我们需要交付一个电商系统,根据电商业务的特点,需要划分出用户、商品、订单、仓储等限界上下文,每一个限界上下文都是一个独立的业务单元,具有完整的业务规则。 识别领域模型 然后,再分别针对上下文内的业务领域进行建模,得到领域模型。在DDD思想中,领域模型中通常包含实体、值对象、事件、领域服务等概念。我们可以通过“事件风暴”的方式来识别出这些概念。 注意,“事件风暴”和“头脑风暴”是有区别的。“头脑风暴”的主要目的是通过发散思维进行创新,而“事件风暴”是DDD中的概念,其主要目的是所有人一起根据统一语言和业务规则识别出事件。再根据事件识别出实体、值对象、领域服务、指令、业务流等领域模型中的概念。 所谓事件指的是已经发生了的事情。比如用户下了一个订单、用户取消了订单、用户支付了订单等 根据事件,我们可以识别出实体,比如上面这个例子中的订单实体,以及指令:取消、支付、下单等。 程序设计 识别出领域模型之后,我们就可以根据领域模型来指导我们进行程序设计了。这里的程序设计包括业务架构、数据架构、核心业务流程、系统架构、部署架构等。需要注意的是,在进行程序设计时,我们依然要遵循DDD中的设计规范。否则很容易走偏方向。 编写代码 有了完整的程序设计之后,我们就可以进行实际的工程搭建以及代码编写了。 这个阶段需要注意的是,我们需要遵循DDD思想中的架构设计和代码设计。实际上这个阶段也是非常困难的。因为基于DDD思想下的工程架构和我们传统的工程架构不一样。 基于DDD思想下,编码过程中我们经常会遇到的一个问题是:这个代码应该放在哪里合适。 工程结构 在DDD中,标准的工程结构分为4层。用户接口层、应用层、领域层和基础设施层。 DDD中,构建软件结构思维有六边形架构、CQRS架构等,它们是一种思想,是从逻辑层面对工程结构进行划分,而我们熟知的SOA架构以及微服务架构是从物理逻辑层面对工程结构进行划分,它们有着本质的区别,但是目标都是一样的:构建可维护、可扩展、可测试的软件系统。 代码编写 在DDD中,最为复杂的便是领域层,所有的业务逻辑和规则都在这里实现。因此我们经常会遇到一个问题就是代码应该放在哪里。 在具体落地过程中会遇到这些问题,解决这些问题没有银弹,因为不同的业务有不同的处理方式,这个时候我们需要与领域专家们讨论,得出大家都满意的处理方案。 代码重构 没有不变的业务。因此我们需要结合业务的发展而不断迭代更新我们的领域模型,通过重构的方式来挖掘隐形概念,再根据这些隐形概念去不断的调整我们的战略设计以及领域模型。使得整个软件系统的发展也是螺旋式迭代更新的过程。 通过以上的介绍,我们实现DDD的过程如下:  四、总结 通过对于DDD的理解,其实不难发现,程序员的工作重心变了,程序员其实不是在编写代码,而是在不断的摸索业务领域知识,尤其是复杂业务。 所以如果总是觉得自己在CRUD,有可能不是你做的业务没价值,而是自己对于业务的理解还不够深;如果总是沉迷于代码编写,可能你的发展空间就会受限了。 作者:京东科技 孙黎明 来源:京东云开发者社区 转载请注明来源

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册