LangChain4j学习5:AI Services
例如,您可能想从文本中提取信息,如文本中提到的人员列表, 或将自由格式的产品评论转换为结构化形式,包含 String productName、Sentiment sentiment、List claimedProblems 等字段。以这种方式使用 ChatMemory 时,重要的是要清除不再需要的对话记忆,以避免内存泄漏。如果您想从 LLM 接收结构化输出(例如,复杂的 Java 对象,而不是 S
AI大模型面试圣经
大模型开发者宝藏
Dify高效AI工作流智能体
SystemMessage 和 UserMessage
多模态
AI 服务目前不支持多模态, 请使用低级 API。
返回类型
AI 服务方法可以返回以下类型之一:
String - 在这种情况下,LLM 生成的输出将不经任何处理/解析直接返回
结构化输出支持的任何类型 - 在这种情况下, AI 服务将在返回之前将 LLM 生成的输出解析为所需类型
任何类型都可以额外包装在 Result 中,以获取有关 AI 服务调用的额外元数据:
TokenUsage - AI 服务调用期间使用的令牌总数。如果 AI 服务对 LLM 进行了多次调用 (例如,因为执行了工具),它将汇总所有调用的令牌使用情况。
Sources - 在 RAG 检索期间检索到的 Content
已执行的工具
FinishReason
示例:
interface Assistant {
@UserMessage("Generate an outline for the article on the following topic: {{it}}")
Result<List<String>> generateOutlineFor(String topic);
}
Result<List> result = assistant.generateOutlineFor(“Java”);
List outline = result.content();
TokenUsage tokenUsage = result.tokenUsage();
List sources = result.sources();
List toolExecutions = result.toolExecutions();
FinishReason finishReason = result.finishReason();
结构化输出
如果您想从 LLM 接收结构化输出(例如,复杂的 Java 对象,而不是 String 中的非结构化文本), 您可以将 AI 服务方法的返回类型从 String 更改为其他类型。
备注
有关结构化输出的更多信息可以在这里找到。
几个例子:
返回类型为 boolean
interface SentimentAnalyzer {
@UserMessage("Does {{it}} has a positive sentiment?")
boolean isPositive(String text);
}
SentimentAnalyzer sentimentAnalyzer = AiServices.create(SentimentAnalyzer.class, model);
boolean positive = sentimentAnalyzer.isPositive(“It’s wonderful!”);
// true
返回类型为 Enum
enum Priority {
CRITICAL, HIGH, LOW
}
interface PriorityAnalyzer {
@UserMessage("Analyze the priority of the following issue: {{it}}")
Priority analyzePriority(String issueDescription);
}
PriorityAnalyzer priorityAnalyzer = AiServices.create(PriorityAnalyzer.class, model);
Priority priority = priorityAnalyzer.analyzePriority(“The main payment gateway is down, and customers cannot process transactions.”);
// CRITICAL
返回类型为 POJO
class Person {
@Description("first name of a person") // 您可以添加可选描述,帮助 LLM 更好地理解
String firstName;
String lastName;
LocalDate birthDate;
Address address;
}
@Description(“an address”) // 您可以添加可选描述,帮助 LLM 更好地理解
class Address {
String street;
Integer streetNumber;
String city;
}
interface PersonExtractor {
@UserMessage("Extract information about a person from {{it}}")
Person extractPersonFrom(String text);
}
PersonExtractor personExtractor = AiServices.create(PersonExtractor.class, model);
String text = “”"
In 1968, amidst the fading echoes of Independence Day,
a child named John arrived under the calm evening sky.
This newborn, bearing the surname Doe, marked the start of a new journey.
He was welcomed into the world at 345 Whispering Pines Avenue
a quaint street nestled in the heart of Springfield
an abode that echoed with the gentle hum of suburban dreams and aspirations.
“”";
Person person = personExtractor.extractPersonFrom(text);
System.out.println(person); // Person { firstName = “John”, lastName = “Doe”, birthDate = 1968-07-04, address = Address { … } }
JSON 模式
在提取自定义 POJO(实际上是 JSON,然后解析为 POJO)时, 建议在模型配置中启用"JSON 模式"。 这样,LLM 将被强制以有效的 JSON 格式响应。
备注
请注意,JSON 模式和工具/函数调用是类似的功能, 但有不同的 API,用于不同的目的。
当您_始终_需要 LLM 以结构化格式(有效的 JSON)响应时,JSON 模式很有用。 此外,通常不需要状态/记忆,因此与 LLM 的每次交互都独立于其他交互。 例如,您可能想从文本中提取信息,如文本中提到的人员列表, 或将自由格式的产品评论转换为结构化形式,包含 String productName、Sentiment sentiment、List claimedProblems 等字段。
另一方面,当 LLM 应该能够执行某些操作时,工具/函数很有用 (例如,查询数据库、搜索网络、取消用户预订等)。 在这种情况下,向 LLM 提供带有预期 JSON 模式的工具列表,它会自主决定 是否调用其中任何一个来满足用户请求。
早期,函数调用经常用于结构化数据提取, 但现在我们有了 JSON 模式功能,它更适合这个目的。
以下是如何启用 JSON 模式:
对于 OpenAI:
对于支持结构化输出的较新模型(例如,gpt-4o-mini、gpt-4o-2024-08-06):
OpenAiChatModel.builder()
...
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA)
.strictJsonSchema(true)
.build();
查看此处了解更多详情。
对于较旧的模型(例如 gpt-3.5-turbo、gpt-4):
OpenAiChatModel.builder()
…
.responseFormat(“json_object”)
.build();
对于 Azure OpenAI:
AzureOpenAiChatModel.builder()
…
.responseFormat(new ChatCompletionsJsonResponseFormat())
.build();
对于 Vertex AI Gemini:
VertexAiGeminiChatModel.builder()
…
.responseMimeType(“application/json”)
.build();
或通过从 Java 类指定显式模式:
VertexAiGeminiChatModel.builder()
…
.responseSchema(SchemaHelper.fromClass(Person.class))
.build();
从 JSON 模式:
VertexAiGeminiChatModel.builder()
…
.responseSchema(Schema.builder()…build())
.build();
对于 Google AI Gemini:
GoogleAiGeminiChatModel.builder()
…
.responseFormat(ResponseFormat.JSON)
.build();
或通过从 Java 类指定显式模式:
GoogleAiGeminiChatModel.builder()
…
.responseFormat(ResponseFormat.builder()
.type(JSON)
.jsonSchema(JsonSchemas.jsonSchemaFrom(Person.class).get())
.build())
.build();
从 JSON 模式:
GoogleAiGeminiChatModel.builder()
…
.responseFormat(ResponseFormat.builder()
.type(JSON)
.jsonSchema(JsonSchema.builder()…build())
.build())
.build();
对于 Mistral AI:
MistralAiChatModel.builder()
…
.responseFormat(MistralAiResponseFormatType.JSON_OBJECT)
.build();
对于 Ollama:
OllamaChatModel.builder()
…
.responseFormat(JSON)
.build();
对于其他模型提供商:如果底层模型提供商不支持 JSON 模式, 提示工程是您最好的选择。此外,尝试降低 temperature 以获得更确定性的结果。
更多示例
流式处理
AI 服务可以使用 TokenStream 返回类型逐个令牌流式处理响应:
interface Assistant {
TokenStream chat(String message);
}
StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv(“OPENAI_API_KEY”))
.modelName(GPT_4_O_MINI)
.build();
Assistant assistant = AiServices.create(Assistant.class, model);
TokenStream tokenStream = assistant.chat(“Tell me a joke”);
tokenStream.onPartialResponse((String partialResponse) -> System.out.println(partialResponse))
.onRetrieved((List contents) -> System.out.println(contents))
.onToolExecuted((ToolExecution toolExecution) -> System.out.println(toolExecution))
.onCompleteResponse((ChatResponse response) -> System.out.println(response))
.onError((Throwable error) -> error.printStackTrace())
.start();
Flux
您也可以使用 Flux 代替 TokenStream。 为此,请导入 langchain4j-reactor 模块:
dev.langchain4j langchain4j-reactor 1.0.0-beta3interface Assistant {
Flux chat(String message);
}
流式处理示例
聊天记忆
AI 服务可以使用聊天记忆来"记住"之前的交互:
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
在这种情况下,所有 AI 服务调用都将使用相同的 ChatMemory 实例。 然而,如果您有多个用户,这种方法将不起作用, 因为每个用户都需要自己的 ChatMemory 实例来维护各自的对话。
解决这个问题的方法是使用 ChatMemoryProvider:
interface Assistant {
String chat(@MemoryId int memoryId, @UserMessage String message);
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
.build();
String answerToKlaus = assistant.chat(1, “Hello, my name is Klaus”);
String answerToFrancine = assistant.chat(2, “Hello, my name is Francine”);
在这种情况下,ChatMemoryProvider 将提供两个不同的 ChatMemory 实例,每个记忆 ID 一个。
以这种方式使用 ChatMemory 时,重要的是要清除不再需要的对话记忆,以避免内存泄漏。要使 AI 服务内部使用的聊天记忆可访问,只需让定义它的接口扩展 ChatMemoryAccess 接口即可。
interface Assistant extends ChatMemoryAccess {
String chat(@MemoryId int memoryId, @UserMessage String message);
}
这使得可以访问单个对话的 ChatMemory 实例,并在对话终止时删除它。
String answerToKlaus = assistant.chat(1, “Hello, my name is Klaus”);
String answerToFrancine = assistant.chat(2, “Hello, my name is Francine”);
List messagesWithKlaus = assistant.getChatMemory(1).messages();
boolean chatMemoryWithFrancineEvicted = assistant.evictChatMemory(2);
备注
请注意,如果 AI 服务方法没有用 @MemoryId 注解的参数, ChatMemoryProvider 中 memoryId 的值将默认为字符串 “default”。
备注
请注意,不应对同一个 @MemoryId 并发调用 AI 服务, 因为这可能导致 ChatMemory 损坏。 目前,AI 服务没有实现任何机制来防止对同一 @MemoryId 的并发调用。
单个 ChatMemory 示例
每个用户 ChatMemory 示例
单个持久化 ChatMemory 示例
每个用户持久化 ChatMemory 示例
工具(函数调用)
AI 服务可以配置 LLM 可以使用的工具:
class Tools {
@Tool
int add(int a, int b) {
return a + b;
}
@Tool
int multiply(int a, int b) {
return a * b;
}
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.tools(new Tools())
.build();
String answer = assistant.chat(“What is 1+2 and 3*4?”);
在这种情况下,LLM 将在提供最终答案之前请求执行 add(1, 2) 和 multiply(3, 4) 方法。 LangChain4j 将自动执行这些方法。
有关工具的更多详细信息可以在这里找到。
RAG
AI 服务可以配置 ContentRetriever 以启用简单 RAG:
EmbeddingStore embeddingStore = …
EmbeddingModel embeddingModel = …
ContentRetriever contentRetriever = new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.contentRetriever(contentRetriever)
.build();
配置 RetrievalAugmentor 提供更大的灵活性, 启用高级 RAG功能,如查询转换、重新排序等:
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(…)
.queryRouter(…)
.contentAggregator(…)
.contentInjector(…)
.executor(…)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.retrievalAugmentor(retrievalAugmentor)
.build();
有关 RAG 的更多详细信息可以在这里找到。
更多 RAG 示例可以在这里找到。
自动审核
示例
链接多个 AI 服务
随着您的 LLM 驱动应用程序逻辑变得越来越复杂, 将其分解成更小的部分变得越来越重要,这也是软件开发中的常见做法。
例如,在系统提示中塞入大量指令以应对所有可能的场景, 容易出错且效率低下。如果指令太多,LLM 可能会忽略一些指令。 此外,指令呈现的顺序也很重要,这使得整个过程更具挑战性。
这一原则也适用于工具、RAG 以及模型参数,如 temperature、maxTokens 等。
您的聊天机器人可能不需要始终了解您拥有的每一个工具。 例如,当用户只是向聊天机器人打招呼或说再见时, 让 LLM 访问数十或数百个工具既昂贵又有时甚至危险 (每个包含在 LLM 调用中的工具都会消耗大量的 token) 并可能导致意外结果(LLM 可能会产生幻觉或被操纵以使用非预期输入调用工具)。
关于 RAG:同样,有时需要向 LLM 提供一些上下文, 但并非总是如此,因为这会增加额外成本(更多上下文 = 更多 token) 并增加响应时间(更多上下文 = 更高延迟)。
关于模型参数:在某些情况下,您可能需要 LLM 具有高度确定性, 因此您会设置较低的 temperature。在其他情况下,您可能会选择较高的 temperature,等等。
重点是,更小、更具体的组件更容易且更便宜地开发、测试、维护和理解。
另一个需要考虑的方面涉及两个极端:
您是否希望您的应用程序高度确定性, 应用程序控制流程,而 LLM 只是组件之一?
或者您是否希望 LLM 拥有完全自主权并驱动您的应用程序?
或者根据情况混合使用两者? 当您将应用程序分解为更小、更易管理的部分时,所有这些选项都是可能的。
AI 服务可以作为常规(确定性)软件组件使用并与之结合:
您可以一个接一个地调用 AI 服务(即链接)。
您可以使用确定性和 LLM 驱动的 if/else 语句(AI 服务可以返回 boolean)。
您可以使用确定性和 LLM 驱动的 switch 语句(AI 服务可以返回 enum)。
您可以使用确定性和 LLM 驱动的 for/while 循环(AI 服务可以返回 int 和其他数值类型)。
您可以在单元测试中模拟 AI 服务(因为它是一个接口)。
您可以单独对每个 AI 服务进行集成测试。
您可以单独评估并找到每个 AI 服务的最佳参数。
等等
让我们考虑一个简单的例子。 我想为我的公司构建一个聊天机器人。 如果用户向聊天机器人打招呼, 我希望它用预定义的问候语回应,而不依赖 LLM 生成问候语。 如果用户提出问题,我希望 LLM 使用公司的内部知识库生成回应(即 RAG)。
以下是如何将此任务分解为 2 个独立的 AI 服务:
interface GreetingExpert {
@UserMessage("Is the following text a greeting? Text: {{it}}")
boolean isGreeting(String text);
}
interface ChatBot {
@SystemMessage("You are a polite chatbot of a company called Miles of Smiles.")
String reply(String userMessage);
}
class MilesOfSmiles {
private final GreetingExpert greetingExpert;
private final ChatBot chatBot;
...
public String handle(String userMessage) {
if (greetingExpert.isGreeting(userMessage)) {
return "Greetings from Miles of Smiles! How can I make your day better?";
} else {
return chatBot.reply(userMessage);
}
}
}
GreetingExpert greetingExpert = AiServices.create(GreetingExpert.class, llama2);
ChatBot chatBot = AiServices.builder(ChatBot.class)
.chatLanguageModel(gpt4)
.contentRetriever(milesOfSmilesContentRetriever)
.build();
MilesOfSmiles milesOfSmiles = new MilesOfSmiles(greetingExpert, chatBot);
String greeting = milesOfSmiles.handle(“Hello”);
System.out.println(greeting); // Greetings from Miles of Smiles! How can I make your day better?
String answer = milesOfSmiles.handle(“Which services do you provide?”);
System.out.println(answer); // At Miles of Smiles, we provide a wide range of services …
注意我们如何使用更便宜的 Llama2 来完成识别文本是否为问候语这一简单任务, 而使用更昂贵的 GPT-4 和内容检索器(RAG)来完成更复杂的任务。
这是一个非常简单且有些幼稚的例子,但希望它能说明这个想法。
现在,我可以模拟 GreetingExpert 和 ChatBot,并单独测试 MilesOfSmiles。 我还可以分别对 GreetingExpert 和 ChatBot 进行集成测试。 我可以分别评估它们,并为每个子任务找到最优参数, 或者从长远来看,甚至可以为每个特定子任务微调一个小型专用模型。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)