SpringAI 从0.5到1
为什么是从0.5到1呢,因为Ollama的过程我就不再赘述,网上的教程也很多,本篇将使用Ollama+SpringAI进行叙述。
 开始
 创建一个SpringAI项目
你可以使用SpringIO的QuickStart,我这里贴出我的Pom(主要的一些东西)
| <dependency><groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-webflux</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.ai</groupId>
 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.ai</groupId>
 <artifactId>spring-ai-redis-store</artifactId>
 </dependency>
 
 | 
 配置好Ollama的连接配置
| spring:ai:
 ollama:
 base-url: https://x x x x
 chat:
 options:
 model: qwen2.5:32b
 temperature: 0
 init:
 embedding:
 additional-models: bge-large-zh-v1.5
 
 | 
 上手
 配置一个ChatClient
| @Configurationpublic class OllamaConfig {
 private final InMemoryChatMemory chatMemory = new InMemoryChatMemory();
 // InMemoryChatMemory 是大模型和用户对话的上下文
 @Bean
 public ChatClient chatClient(OllamaChatModel ollamaChatModel) {
 MessageChatMemoryAdvisor messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory);
 return ChatClient.builder(ollamaChatModel)
 .defaultSystem("你的首选语言是中文。") // 系统提示词
 .defaultAdvisors(messageChatMemoryAdvisor)
 .build();
 }
 }
 
 | 
 开始和大模型对话吧
|  @RestController@RequestMapping("/chat")
 @RequiredArgsConstructor
 public class AGIController {
 private static final Logger log = LoggerFactory.getLogger(AGIController.class);
 private final ChatClient chatClient;
 
 @RequestMapping(value = "test", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
 
 public Flux<String> test(String user) {
 return chatClient.prompt("你是智能助手")
 .user(user)
 .stream().content();
 }
 }
 
 | 
基本的一个大模型对话程序就完成了,但是这远远不到1的标准,我们还需要
 给大模型添加记忆
| chatClient.prompt("你是智能助手").user(user)
 .advisors(advisor -> advisor.param("chat_memory_conversation_id", 不同用户唯一ID)
 .param("chat_memory_response_size", 50))
 .stream().content();
 
 | 
 给予大模型其他能力
通过Tool Calling可以让大模型调用本地的方法
| @Tool(description = "获取用户信息工具,可以根据用户提供的名称获取到该用户的详细信息。需要给客户返回友好的信息。")public CustomerInfo getCustomerInfoTool(@ToolParam(description = "名称,例如王富贵") String name) {
 return Mono.fromCallable(() -> customerService.getCustomerInfo(name).block())
 .subscribeOn(Schedulers.boundedElastic()).block();
 }
 
 | 
定义工具,Tool和ToolParam是强烈建议的,大模型会通过工具和入参的描述来决定是否使用该工具进行调用
| @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface Tool {
 String name() default "";
 
 String description() default "";
 
 boolean returnDirect() default false;
 
 Class<? extends ToolCallResultConverter> resultConverter() default DefaultToolCallResultConverter.class;
 }
 
 | 
Tool还有个属性是returnDirect设置为True之后,将直接返回该方法返回的信息,不再交给AI渲染
BEFORE

AFTER

工具定义完成之后,我们需要调用大模型的时候告诉他,有这个工具
| ChatClientRequestSpec tools(String... toolNames);
 ChatClientRequestSpec tools(FunctionCallback... toolCallbacks);
 
 ChatClientRequestSpec tools(List<ToolCallback> toolCallbacks);
 
 ChatClientRequestSpec tools(Object... toolObjects);
 
 ChatClientRequestSpec tools(ToolCallbackProvider... toolCallbackProviders);
 
 | 
多种方法都可以给到模型,我这边是直接将定义tool的类传递进去
| chatClient.prompt("你是智能助手").user(user)
 .tools(userTool)
 .advisors(advisor -> advisor.param("chat_memory_conversation_id", 不同用户唯一ID)
 .param("chat_memory_response_size", 50))
 .stream().content();
 
 | 
当你问他,查询喵喵的信息的时候,大模型就会通过这个工具查询喵喵的信息。
你说你想在工具调用的时候将用户标识传进去,不想越权查询到其他用户的信息,怎么办
 上下文传递
如果你使用的是SpringMVC,那么你直接使用ThredLocal就可以了,本节主要对Flux用户解决问题。
当你按照平常上下文传递的方式进行操作的时候,发现在Spring Tool内获取不到你保存的Context,怎么办,我们可以使用Tool Context来解决这个问题。
| chatClient.prompt("你是智能助手").user(user)
 .tools(userTool)
 .toolContext(Map.of("userinfo", userinfo)
 .advisors(advisor -> advisor.param("chat_memory_conversation_id", 不同用户唯一ID)
 .param("chat_memory_response_size", 50))
 .stream().content();
 
 | 
这样在下文中就可以通过Mono.deferContextual(ctx -> Mono.just(ctx.get("userinfo")));来获取到信息了。
这样看来就完事大吉了?开发过程中你会发现你的TraceID,里面的和外面的不一样,这还是Tool Calling上下文不一致的问题
那咱们就手动给他传traceid和spanid吧
| private final Tracer tracer;
 chatClient.prompt("你是智能助手")
 .user(user)
 .tools(userTool)
 .toolContext(Map.of("traceid", tracer.currentSpan().context().traceId(),
 "spanid",tracer.currentSpan().context().spanId())
 .advisors(advisor -> advisor.param("chat_memory_conversation_id", 不同用户唯一ID)
 .param("chat_memory_response_size", 50))
 .stream().content();
 
 | 
对不起,这样传递进去,在Tool内不也不会用你给的信息,那咋办
 日志上下文对象传递
| private final Tracer tracer;
 Mono.deferContextual(ctx -> Mono.just(ctx.get("micrometer.observation")).map(obj -> {
 chatClient.prompt("你是智能助手")
 .user(user)
 .tools(userTool)
 .toolContext(Map.of("micrometer.observation",obj)
 .advisors(advisor -> advisor.param("chat_memory_conversation_id", 不同用户唯一ID)
 .param("chat_memory_response_size", 50))
 .stream().content();
 });
 
 | 
直接给他强制替换,好了,这样终于统一了
SpringAI到1了吗,还没有,等我后期更新SpringAI的RAG应用吧
 延展阅读
- Spring AI