目录

如何基于阿里云OpenSearch-LLM搭建智能客服平台

如何基于阿里云OpenSearch LLM搭建智能客服平台

需求

我想做一个系统的AI客服,希望回答是能够有插图(当然我可以先构建自己的知识库),比如用户问:怎么修改密码,答复里面能配上我之前在知识库中准备好的图片。

怎么创建实例及导入知识库

代码中怎么调用

有很多参数的设置自己参考文档,如选择大模型,富文本支持,以及流式应答

我的实际代码如下,是参考的官网文档实现

引入包

 <!-- opensearch -->
 <dependency>
     <groupId>com.aliyun.opensearch</groupId>
     <artifactId>aliyun-sdk-opensearch</artifactId>
     <version>6.0.0</version>
 </dependency>

 <dependency>
     <groupId>com.aliyun</groupId>
     <artifactId>sts20150401</artifactId>
     <version>1.1.6</version>
 </dependency>
 <dependency>
     <groupId>com.aliyun</groupId>
     <artifactId>tea-openapi</artifactId>
     <version>0.3.7</version>
 </dependency>
 <dependency>
     <groupId>com.aliyun</groupId>
     <artifactId>tea-console</artifactId>
     <version>0.0.1</version>
 </dependency>
 <dependency>
     <groupId>com.aliyun</groupId>
     <artifactId>tea-util</artifactId>
     <version>0.2.23</version>
 </dependency>
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>1.2.83</version>
 </dependency>
 <dependency>
     <groupId>org.apache.httpcomponents</groupId>
     <artifactId>httpclient</artifactId>
     <version>4.5.13</version>
 </dependency>
 <!-- opensearch -->

构建OpenSearchClient对象

@Configuration
@AllArgsConstructor
@Slf4j
public class AliConfig {

    private AppConfigProperties appConfigProperties;

    @Bean
    public OpenSearchClient createOpenSearchClient() {
        OpenSearch openSearch = new OpenSearch(appConfigProperties.getAliAccessKeyID(),
                appConfigProperties.getAliAccessKeySecret(), "http://opensearch-cn-shanghai.aliyuncs.com").setTimeout(90000);
        return new OpenSearchClient(openSearch);
    }
}

AppConfigProperties

@Component
@ConfigurationProperties(prefix = "config")
@Data
public class AppConfigProperties {
    private String aliAccessKeyID;
    private String aliAccessKeySecret;
}

yml配置如下

config:
  aliAccessKeyID: xxx
  aliAccessKeySecret: xxx

Controller如下

@RestController
@RequestMapping("/msg/v1/llm")
@AllArgsConstructor
@Slf4j
public class LlmController {

    private OpenSearchClient openSearchClient;

    @GetMapping("/streamChatWithWeb")
    public void streamChatWithWeb(String content, String appName, HttpServletResponse response) throws IOException {
        if (StringUtils.isBlank(appName)) {
            appName = "baitian";
        }
        String path = "/apps/" + appName + "/actions/knowledge-search";
        response.setContentType("text/event-stream;charset=UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Connection", "keep-alive");

        Map<String, Object> question = new HashMap<>();
        question.put("text", content);
        question.put("type", "TEXT");

        Map<String, Object> chat = new HashMap<>();
        chat.put("stream", true);
        chat.put("rich_text_strategy", "extend_response");

        Map<String, Object> options = new HashMap<>();
        options.put("chat", chat);

        Map<String, Object> root = new HashMap<>();
        root.put("question", question);
        root.put("options", options);

        Map<String, String> params = new HashMap<>();
        params.put("_POST_BODY", JsonUtil.toJson(root));

        PrintWriter writer = response.getWriter();
        try {
            HttpResponse httpResponse = openSearchClient.callForHttpResponse(path, params, "POST");
            InputStream responseBodyStream = httpResponse.getEntity().getContent();
            BufferedReader br = new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8));

            String line;
            while ((line = br.readLine()) != null) {
                writer.write(line);
                writer.flush();
            }
            br.close();
            // 请求成功
            openSearchClient.getHttpClientManager().getClientTracer().success(httpResponse, "");
        } catch (OpenSearchClientException e) {
            log.error(e.getMessage(), e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

测试,我们在浏览器里面直接访问 localhost:8001/msg/v1/llm/streamChatWithWeb?content=采购订单模块怎么用? 就能看到对应的流式输出了

通过WebClient 怎么调用

有时候我们的应用部署在内网环境,需要通过前置机来调用外网,这是可以把上面的对接LLM的应用放到前置机环境,然后内网服务器调用这个LLM应用的接口,那怎么调用一个流式输出的接口呢?参考如下

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
@Service
@AllArgsConstructor
public class LlmStreamService {

    private final WebClient llmWebClient;


    /**
     * 对外暴露:调用一次就得到一个 Flux<String>(逐块)
     */
    public Flux<String> streamAnswer(String content) {
        return llmWebClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/msg/v1/llm/streamChatWithWeb")
                        .queryParam("content", content)
                        .build())
                .accept(MediaType.TEXT_PLAIN)
                .retrieve()
                .bodyToFlux(DataBuffer.class)
                .doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
                .map(buf -> {
                    byte[] bytes = new byte[buf.readableByteCount()];
                    buf.read(bytes);
                    DataBufferUtils.release(buf);
                    return new String(bytes, StandardCharsets.UTF_8);
                })
                .limitRate(32)
                .timeout(Duration.ofSeconds(30))
                .retryBackoff(3, Duration.ofSeconds(1));
    }
}

WebClientConfig

@Configuration
@AllArgsConstructor
public class WebClientConfig {

    private YrtConfig yrtConfig;

    @Bean
    public WebClient webClient() {
        return WebClient.builder()
                .baseUrl(yrtConfig.getPushAddress())
                .build();
    }
}

Controller

@RestController
@RequestMapping("/v1/llm")
@AllArgsConstructor
@Slf4j
public class LlmController {

    private final LlmStreamService llmStreamService;

    @GetMapping("/streamChatWithWeb")
    public Flux<String> streamChatWithWeb(String content, HttpServletResponse response) {
        response.setContentType("text/event-stream;charset=UTF-8");
        return llmStreamService.streamAnswer(content);
    }
}

此时浏览器访问:localhost:8001/v1/llm/streamChatWithWeb?content=耗材柜怎么使用?就能看到输出了

https://i-blog.csdnimg.cn/direct/fc29a71024fb436198a1f9f38b09ffe6.png

用阿里云百炼怎么实现?

我这个使用的是阿里云OpenSearch LLM,看中的就是其支持富文本的回答功能,后来研究了一下说传统的LLM也能支持,但是需要自己处理一下,大概的逻辑如下,有空了研究一下

以下内容基于deepseek生成

核心思路

实现图文回答的关键在于:将图片的URL地址和对应的文字描述精准地存入知识库。当大模型检索到相关知识时,它不仅能获取文字,还能获取到图片的链接,并遵循你的指令,以Markdown格式将图片渲染出来。

整个流程可以分为三大阶段:

  1. 知识库准备与处理:精心准备图文内容。
  2. 百炼平台配置:创建模型并关联知识库,设置关键指令。
  3. 应用部署与测试:将客服系统集成到你的网站或应用中。

第一阶段:知识库准备与处理(最关键的一步)

你的知识库文档需要以一种模型能理解的方式将文字和图片关联起来。

方法一(推荐):在文本中直接插入图片链接(Markdown格式)

这是最直接有效的方法。当你准备知识库的TXT、PDF、Word等文档时,使用Markdown语法来嵌入图片。

  • 操作步骤
    1. 将你的图片上传到一个公开可访问的文件存储服务上,例如:
      • 阿里云对象存储OSS(最推荐,与百炼同属阿里云,内网传输更快更稳定)
      • 或其他图床服务(如Imgur、SM.MS)
      • 你的网站自己的CDN服务器
    2. 获取每一张图片的公网URL地址(以.jpg, .png等结尾)。
    3. 在编写知识库文档时,在对应的文字位置,使用Markdown语法插入图片。
      • 语法:![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=%E5%9B%BE%E7%89%87%E7%9A%84URL%E5%9C%B0%E5%9D%80&pos_id=img-ipYo7mSN-1756206854283)
      • 示例

        如何修改密码?

        您好,修改密码的流程非常简单,请参照以下步骤操作:

        1. 首先,登录您的账户,点击右上角的【个人中心】。
        2. 在个人中心页面,选择左侧菜单的【安全设置】。
          https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fyour-bucket.oss-cn-beijing.aliyuncs.com%2Fhelp%2Fstep1.jpg&pos_id=img-bMPyBCi8-1756206854284
        3. 在安全设置页面,找到“登录密码”选项,点击右侧的【修改】按钮。
          https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fyour-bucket.oss-cn-beijing.aliyuncs.com%2Fhelp%2Fstep2.jpg&pos_id=img-Kx61Xofg-1756206854284
        4. 按照提示,验证原密码后,输入您的新密码并确认。
        5. 点击【提交】,即可完成密码修改。

方法二:分离式关联(文字和图片URL在同一段落)

如果你觉得Markdown太复杂,也可以简单地将图片URL放在对应文字描述的后面。

  • 示例

    问题:怎么修改密码?
    回答:请参照以下步骤修改密码:第一步,点击个人中心。图片URL:https://your-bucket.oss-cn-beijing.aliyuncs.com/help/step1.jpg。第二步,点击安全设置中的修改按钮。图片URL:https://your-bucket.oss-cn-beijing.aliyuncs.com/help/step2.jpg。

方法三:利用表格

创建一个CSV或Excel文件,一列是问题,一列是回答文本,另一列是对应的图片URL。在百炼接入知识库时,可以很好地处理表格数据。

问题回答图片URL
怎么修改密码?请先登录账户,进入个人中心的安全设置页面…https://…/step1.jpg, https://…/step2.jpg

准备好知识库文档后,将其上传到阿里云百炼的“知识库”功能中,并进行切分向量化**。


第二阶段:在阿里云百炼平台配置

  1. 创建模型应用

    • 进入百炼控制台,创建一个新的“模型应用”。
    • 选择适合的底层大模型(如通义千问系列),并将其与你刚刚创建并处理好的知识库进行关联。
  2. 设置系统指令(System Prompt) - 这是让模型返回图片的关键!

    你需要在模型的系统指令中明确要求模型在回答时使用Markdown格式渲染图片。这相当于给模型下达“工作原则”。

    • 推荐指令(可根据你的需求修改):

      你是一名专业的客服助手,负责解答用户关于我们产品和服务的问题。请严格根据知识库提供的信息进行回答。
      
      **重要规则:**
      1.  如果知识库的回答中包含了图片链接(通常以 .jpg, .png, .gif 等结尾),你**必须**在回复中使用Markdown格式渲染图片,以便用户直接查看。
      2.  图片的Markdown格式为:`![图片描述](图片URL)`。
      3.  请确保图片描述是准确且有帮助的,如果知识库中没有提供描述,你可以根据图片URL的内容自行生成一个简洁的描述。
      4.  保持回答友好、清晰且乐于助人。

第三阶段:测试与部署

  1. 在调试窗格进行测试

    • 保存你的模型应用和系统指令设置。
    • 在右侧的调试窗格中,输入测试问题,例如:“怎么修改密码?”
    • 检查回复:你应该看到模型返回的回答中,不仅包含了文字步骤,还正确地将图片以渲染后的形式显示了出来(而不是纯URL文本)。
  2. 部署上线

    • 测试通过后,你可以通过百炼提供的API接口或一键部署功能,将你的AI客服模型集成到你的网站、小程序、APP或客服系统中。
    • 前端界面在接收到模型返回的Markdown内容后,需要使用支持渲染Markdown的组件(例如很多Web框架都有相关的Markdown渲染库)来正确显示文字和图片。

总结与注意事项

  • 图片存储:务必确保你的图片存储在公网可访问的位置,并且URL是有效的。如果图片存储在阿里云OSS,可以考虑设置CDN加速来提升用户加载速度。
  • 成本考量:图片的存储和流量可能会产生少量费用(OSS存储和下行流量费),但通常成本很低。
  • 图片描述(Alt Text)![描述](url)中的“描述”部分非常重要,它是在图片无法加载时显示的替代文本,同时对搜索引擎优化(SEO)和视障用户读屏软件也很友好。
  • 多轮对话:如果你的客服场景涉及多轮对话,需要在系统指令中进一步明确在多轮交互中何时以及如何展示图片。

按照以上步骤,你就能在阿里云百炼上成功搭建一个能够智能、准确回复且图文并茂的AI客服系统了。祝你成功!