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
@Configuration public 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