主题
Ollama
Ollama 是什么?
Ollama 是一款先进的 AI 工具,让用户能够轻松地在本地(CPU 与 GPU 模式)搭建并运行大语言模型。借助 Ollama,用户可以使用诸如 Llama 2 这样的强大语言模型,甚至定制并创建自己的模型。Ollama 将模型权重、配置与数据打包进单个包,由一个 Modelfile 定义。它优化了搭建与配置的诸多细节,包括 GPU 使用。
关于 Ollama 的更多细节,可看看:
演讲
观看这段在 Docker Con 23 上的演讲:
观看这段由 Code to the Moon 制作的介绍:
开始使用
要开始使用,将以下依赖添加到你的项目的 pom.xml:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>1.22.0</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>ollama</artifactId>
<version>1.19.1</version>
</dependency>试一下当 Ollama 运行在 testcontainers 中时的简单聊天示例代码:
java
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Image;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.Container;
import org.testcontainers.ollama.OllamaContainer;
import org.testcontainers.utility.DockerImageName;
import java.io.IOException;
import java.util.List;
public class OllamaChatExample {
private static final Logger log = LoggerFactory.getLogger(OllamaChatExample.class);
static final String OLLAMA_IMAGE = "ollama/ollama:latest";
static final String TINY_DOLPHIN_MODEL = "tinydolphin";
static final String DOCKER_IMAGE_NAME = "tc-ollama/ollama:latest-tinydolphin";
public static void main(String[] args) {
// Create and start the Ollama container
DockerImageName dockerImageName = DockerImageName.parse(OLLAMA_IMAGE);
DockerClient dockerClient = DockerClientFactory.instance().client();
List<Image> images = dockerClient.listImagesCmd().withReferenceFilter(DOCKER_IMAGE_NAME).exec();
OllamaContainer ollama;
if (images.isEmpty()) {
ollama = new OllamaContainer(dockerImageName);
} else {
ollama = new OllamaContainer(DockerImageName.parse(DOCKER_IMAGE_NAME).asCompatibleSubstituteFor(OLLAMA_IMAGE));
}
ollama.start();
// Pull the model and create an image based on the selected model.
try {
log.info("Start pulling the '{}' model ... would take several minutes ...", TINY_DOLPHIN_MODEL);
Container.ExecResult r = ollama.execInContainer("ollama", "pull", TINY_DOLPHIN_MODEL);
log.info("Model pulling competed! {}", r);
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Error pulling model", e);
}
ollama.commitToImage(DOCKER_IMAGE_NAME);
// Build the ChatModel
ChatModel model = OllamaChatModel.builder()
.baseUrl(ollama.getEndpoint())
.temperature(0.0)
.logRequests(true)
.logResponses(true)
.modelName(TINY_DOLPHIN_MODEL)
.build();
// Example usage
String answer = model.chat("Provide 3 short bullet points explaining why Java is awesome");
System.out.println(answer);
// Stop the Ollama container
ollama.stop();
}
}如果你的 Ollama 在本地运行,你也可以试下面的聊天示例代码:
java
class OllamaChatLocalModelTest {
static String MODEL_NAME = "llama3.2"; // try other local ollama model names
static String BASE_URL = "http://localhost:11434"; // local ollama base url
public static void main(String[] args) {
ChatModel model = OllamaChatModel.builder()
.baseUrl(BASE_URL)
.modelName(MODEL_NAME)
.build();
String answer = model.chat("List top 10 cites in China");
System.out.println(answer);
model = OllamaChatModel.builder()
.baseUrl(BASE_URL)
.modelName(MODEL_NAME)
.responseFormat(JSON)
.build();
String json = model.chat("List top 10 cites in US");
System.out.println(json);
}
}试一下当 Ollama 运行在 testcontainers 中时的简单流式聊天示例代码:
java
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Image;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
import dev.langchain4j.model.output.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.Container;
import org.testcontainers.ollama.OllamaContainer;
import org.testcontainers.utility.DockerImageName;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class OllamaStreamingChatExample {
private static final Logger log = LoggerFactory.getLogger(OllamaStreamingChatExample.class);
static final String OLLAMA_IMAGE = "ollama/ollama:latest";
static final String TINY_DOLPHIN_MODEL = "tinydolphin";
static final String DOCKER_IMAGE_NAME = "tc-ollama/ollama:latest-tinydolphin";
public static void main(String[] args) {
DockerImageName dockerImageName = DockerImageName.parse(OLLAMA_IMAGE);
DockerClient dockerClient = DockerClientFactory.instance().client();
List<Image> images = dockerClient.listImagesCmd().withReferenceFilter(DOCKER_IMAGE_NAME).exec();
OllamaContainer ollama;
if (images.isEmpty()) {
ollama = new OllamaContainer(dockerImageName);
} else {
ollama = new OllamaContainer(DockerImageName.parse(DOCKER_IMAGE_NAME).asCompatibleSubstituteFor(OLLAMA_IMAGE));
}
ollama.start();
try {
log.info("Start pulling the '{}' model ... would take several minutes ...", TINY_DOLPHIN_MODEL);
Container.ExecResult r = ollama.execInContainer("ollama", "pull", TINY_DOLPHIN_MODEL);
log.info("Model pulling competed! {}", r);
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Error pulling model", e);
}
ollama.commitToImage(DOCKER_IMAGE_NAME);
StreamingChatModel model = OllamaStreamingChatModel.builder()
.baseUrl(ollama.getEndpoint())
.temperature(0.0)
.logRequests(true)
.logResponses(true)
.modelName(TINY_DOLPHIN_MODEL)
.build();
String userMessage = "Write a 100-word poem about Java and AI";
CompletableFuture<ChatResponse> futureResponse = new CompletableFuture<>();
model.chat(userMessage, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.print(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
futureResponse.complete(completeResponse);
}
@Override
public void onError(Throwable error) {
futureResponse.completeExceptionally(error);
}
});
futureResponse.join();
ollama.stop();
}
}如果你的 Ollama 在本地运行,你也可以试下面的流式聊天示例代码:
java
class OllamaStreamingChatLocalModelTest {
static String MODEL_NAME = "llama3.2"; // try other local ollama model names
static String BASE_URL = "http://localhost:11434"; // local ollama base url
public static void main(String[] args) {
StreamingChatModel model = OllamaStreamingChatModel.builder()
.baseUrl(BASE_URL)
.modelName(MODEL_NAME)
.temperature(0.0)
.build();
String userMessage = "Write a 100-word poem about Java and AI";
CompletableFuture<ChatResponse> futureResponse = new CompletableFuture<>();
model.chat(userMessage, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.print(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
futureResponse.complete(completeResponse);
}
@Override
public void onError(Throwable error) {
futureResponse.completeExceptionally(error);
}
});
futureResponse.join();
}
}参数
OllamaChatModel 与 OllamaStreamingChatModel 类可用 builder 模式,通过以下参数实例化:
| 参数 | 描述 | 类型 | 示例 |
|---|---|---|---|
httpClientBuilder | 参见可定制的 HTTP 客户端 | HttpClientBuilder | |
baseUrl | Ollama 服务器的 base URL。 | String | http://localhost:11434 |
defaultRequestParameters | ChatRequestParameters | ||
modelName | 要从 Ollama 服务器使用的模型名。 | String | |
temperature | 控制所生成响应的随机性。较高的值(例如 1.0)产出更多样的输出,而较低的值(例如 0.2)产出更确定的响应。 | Double | |
topK | 指定生成过程中每一步所考虑的最高概率词元的数量。 | Integer | |
topP | 通过为最高概率词元的累计概率设置阈值来控制所生成响应的多样性。 | Double | |
mirostat | Integer | ||
mirostatEta | Double | ||
mirostatTau | Double | ||
repeatLastN | Integer | ||
repeatPenalty | 对模型在生成输出中重复相似词元的行为施加惩罚。 | Double | |
seed | 设置随机种子,以使生成的响应可复现。 | Integer | |
numPredict | 每个输入提示词要生成的预测数。 | Integer | |
numCtx | Integer | ||
stop | 一组字符串,若被生成,将标记响应结束。 | List<String> | |
minP | Double | ||
responseFormat | 所生成输出的期望格式。TEXT 或 JSON,可选带 JSON Schema 定义 | ResponseFormat | |
think | 控制思考。 | Boolean | |
truncate | 控制服务器如何处理放不进上下文窗口的提示词。见下文。 | Boolean | true(服务器默认) |
returnThinking | Boolean | ||
timeout | 允许一次 API 调用完成的最长时间。 | Duration | PT60S |
customHeaders | 自定义 HTTP 头。 | Map<String, String> | |
logRequests | Boolean | ||
logResponses | Boolean | ||
listeners | 参见聊天模型可观测性 | List<ChatModelListener> | |
supportedCapabilities | AiServices API 所使用的模型能力集合(仅 OllamaChatModel 支持) | Set<Capability> | RESPONSE_FORMAT_JSON_SCHEMA |
maxRetries | API 调用失败时的最大重试次数。 | Integer |
用法示例
java
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.temperature(0.8)
.timeout(Duration.ofSeconds(60))
.build();结合 Spring Boot 的使用示例
导入 Ollama 的 Spring Boot starter:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama-spring-boot4-starter</artifactId>
<version>1.22.0-beta32</version>
</dependency>NOTE
该 starter 需要 Spring Boot 4。在 Spring Boot 3 上,请改用 langchain4j-ollama-spring-boot-starter。 详见 Spring Boot 集成。
然后配置 OllamaChatModel bean:
properties
langchain4j.ollama.chat-model.base-url=http://localhost:11434
langchain4j.ollama.chat-model.model-name=llama3.1
langchain4j.ollama.chat-model.temperature=0.8
langchain4j.ollama.chat-model.timeout=PT60SJSON 模式
java
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.responseFormat(ResponseFormat.JSON)
.temperature(0.8)
.timeout(Duration.ofSeconds(60))
.build();结构化输出
用 builder 定义 JSON schema
java
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.responseFormat(ResponseFormat.builder()
.type(ResponseFormatType.JSON)
.jsonSchema(JsonSchema.builder().rootElement(JsonObjectSchema.builder()
.addProperty("name", JsonStringSchema.builder().build())
.addProperty("capital", JsonStringSchema.builder().build())
.addProperty(
"languages",
JsonArraySchema.builder()
.items(JsonStringSchema.builder().build())
.build())
.required("name", "capital", "languages")
.build())
.build())
.build())
.temperature(0.8)
.timeout(Duration.ofSeconds(60))
.build();用 ChatRequest API 使用 JSON Schema
java
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
ChatResponse chatResponse = ollamaChatModel.chat(ChatRequest.builder()
.messages(userMessage("Tell me about Canada."))
.responseFormat(ResponseFormat.builder()
.type(ResponseFormatType.JSON)
.jsonSchema(JsonSchema.builder().rootElement(JsonObjectSchema.builder()
.addProperty("name", JsonStringSchema.builder().build())
.addProperty("capital", JsonStringSchema.builder().build())
.addProperty(
"languages",
JsonArraySchema.builder()
.items(JsonStringSchema.builder().build())
.build())
.required("name", "capital", "languages")
.build())
.build())
.build())
.build());
String jsonFormattedResponse = chatResponse.aiMessage().text();
/* jsonFormattedResponse value:
{
"capital" : "Ottawa",
"languages" : [ "English", "French" ],
"name" : "Canada"
}
*/结合 AiServices 的 Json Schema
当 OllamaChatModel 以受支持的能力 RESPONSE_FORMAT_JSON_SCHEMA 创建时,AIService 会自动从接口返回值生成 schema。更多参见结构化输出
java
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA)
.build();超出上下文窗口的提示词
默认情况下,Ollama 会丢弃提示词中放不进上下文窗口的部分,并基于剩余内容作答。响应是一个普通的 200,且 promptEvalCount 统计的是裁剪后的提示词,因此无论是响应还是词元用量都不会显示有输入丢失。
将 truncate 设为 false 可改为得到一个错误。此时服务器会以 HTTP 400 拒绝请求,并上报提示词大小与上下文大小:
java
ChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1:8b")
.numCtx(4096)
.truncate(false)
.build();这在多轮智能体循环中很有用——在那里对话随每个工具结果不断增长,而一个被悄然缩短的提示词比一个失败的请求更糟。保持 truncate 未设置可沿用服务器默认值。
思考 / 推理
思考特性受支持,由以下参数控制:
think: controls whether the LLM thinks and how:true: the LLM thinks and returns thoughts in a separatethinkingfieldfalse: the LLM does not thinknull(not set): reasoning LLMs (e.g., DeepSeek R1) will prepend thoughts, delimited by<think>and</think>, to the actual response
returnThinking: controls whetherthinkingfield from the API response is parsed 并在AiMessage.thinking()中返回,以及在使用OllamaStreamingChatModel时是否调用StreamingChatResponseHandler.onPartialThinking()与TokenStream.onPartialThinking()回调。默认禁用。
下面是一个如何配置思考的示例:
java
ChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("qwen3:0.6b")
.think(true)
.returnThinking(true)
.build();自定义消息
OllamaChatModel 与 OllamaStreamingChatModel 除标准聊天消息类型外,还支持自定义聊天消息。自定义消息可用于指定一个带任意属性的消息。这对某些模型很有用,例如 Granite Guardian,它利用非标准消息来评估用于检索增强生成(RAG)的检索上下文。
让我们看看如何用 CustomMessage 指定一个带任意属性的消息:
java
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("granite3-guardian")
.build();
String retrievedContext = "One significant part of treaty making is that signing a treaty implies recognition that the other side is a sovereign state and that the agreement being considered is enforceable under international law. Hence, nations can be very careful about terming an agreement to be a treaty. For example, within the United States, agreements between states are compacts and agreements between states and the federal government or between agencies of the government are memoranda of understanding.";
List<ChatMessage> messages = List.of(
SystemMessage.from("context_relevance"),
UserMessage.from("What is the history of treaty making?"),
CustomMessage.from(Map.of(
"role", "context",
"content", retrievedContext
))
);
ChatResponse chatResponse = ollamaChatModel.chat(ChatRequest.builder().messages(messages).build());
System.out.println(chatResponse.aiMessage().text()); // "Yes" (meaning risk detected by Granite Guardian)决策模型
Ollama 也能运行决策模型,它们以概率回答是/否、选择与量表问题,参见 Ollama 决策模型。