Solon v3.2.0

chat - 聊天模型接口方言及定制

</> markdown

框架已内置 OllamaChatDialect、OpenaiChatDialect 两种方言(基本够用),自动支持 Ollama 提供的模型接口与 Openai 规范的模型接口。还可以通过定制,实现更多的模型兼容。

1、方言接口

public interface ChatDialect extends AiModelDialect {
    //匹配检测
    boolean matched(ChatConfig config);

    //构建请求数据
    String buildRequestJson(ChatConfig config, ChatOptions options, List<ChatMessage> messages, boolean stream);

    //分析响应数据
    boolean parseResponseJson(ChatConfig config, boolean isStream, ChatResponseDefault resp, String respJson);
}

2、内置的方言适配

言方配置要求描述
openai 兼容 openai 的接口规范(默认)
ollamaprovider=ollama兼容 ollama 的接口规范
dashscopeprovider=dashscope兼容 dashscope (阿里云的平台百炼)的接口规范

配置示例:

solon.ai.chat:
  demo:
    apiUrl: "http://127.0.0.1:11434/api/chat" # 使用完整地址(而不是 api_base)
    provider: "ollama" # 使用 ollama 服务时,需要配置 provider
    model: "llama3.2"

3、OllamaChatDialect 定制参考

如果方言有组件注解,会自动注册。否则,需要手动注册:

ChatDialectManager.register(new OllamaChatDialect());

方言定制:

//@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 boolean parseResponseJson(ChatConfig config, boolean isStream, ChatResponseDefault resp, String json) {
        //解析
        ONode oResp = ONode.load(json);

        if (oResp.isObject() == false) {
            return false;
        }

        if (oResp.contains("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(isStream, 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));
            }
        }

        return true;
    }
}