Genkit Ollama 插件全解析:从 CHANGELOG 看本地大模型接入的实现细节
【免费下载链接】genkitOpen-source framework for building agentic apps in JavaScript, Go, Dart, and Python, built and used in production by Google项目地址: https://gitcode.com/GitHub_Trending/ge/genkit
本文以
genkit-ollama插件的 CHANGELOG 为骨架,逐一拆解其中新增、变更与修复的每一项能力:OllamaConfig采样参数、视觉模型media支持、可调用的request_headers、超时传播、连接错误处理与工具 schema 推断,并结合仓库源码(models.py、plugin_api.py、embedders.py)与测试用例(plugin_api_test.py)说明其底层实现原理。读完本文,你将完整掌握在 Genkit(Python)中把 Ollama 本地模型接入聊天、流式生成、工具调用、多模态与向量嵌入的配置方法与排查手段。
一、CHANGELOG 说了什么:一次"转正"带来的能力跃迁
genkit-ollama的 CHANGELOG 采用 Keep a Changelog 格式并遵循语义化版本,其 [Unreleased] 条目集中记录了一次关键升级:插件从社区状态转正为一等公民(first-party),同时带来了配置、错误处理与元数据层面的一批实质改进。逐条翻译过来,本次变更涵盖六个方向:
- 新增:
OllamaConfig采样参数、OllamaSupports.media视觉开关、可调用请求头、超时传播、OllamaConnectionError、EmbeddingDefinition根导出、可运行示例; - 变更:插件元数据按 API 类型声明能力、
request_headers真正生效、工具输入 schema 推断; - 修复:
top_p参数映射错误。
本文后续各节将按"配置参数 → 能力声明 → 网络与错误处理 → 导出与示例 → 行为修复"的顺序,把每一条都讲透。
二、OllamaConfig:六个 Ollama 专属采样旋钮
2.1 字段速览
OllamaConfig继承 Genkit 公共的ModelConfig,并追加六个 Ollama 专属字段,声明位置在 models.py:
| 字段 | 类型 | 含义 | 落点 |
|---|---|---|---|
think | bool \| 'low' \| 'medium' \| 'high' \| None | 推理模型思维链开关/强度 | chat/generate 的顶层请求参数 |
keep_alive | float \| str \| None | 模型在内存中的驻留时长 | 顶层请求参数 |
num_ctx | int \| None | 上下文窗口大小(token 数) | 采样options |
min_p | float \| None | 最小概率阈值过滤低置信 token | 采样options |
seed | int \| None | 随机种子,可复现输出 | 采样options |
num_predict | int \| None | 最多生成的 token 数 | 采样options |
2.2 参数如何分流:options与顶层 kwargs 的两条路径
源码注释明确指出:think与keep_alive是 Ollamachat/generate调用的顶层请求参数,而不是采样选项options(放在options内会被服务端拒绝)。因此 models.py 中的两个静态方法分工明确:
build_request_options(config):归一化配置为 snake_case 的采样选项字典。其中 Genkit 的max_output_tokens会映射为 Ollama 的num_predict(显式给出的num_predict优先);stop_sequences映射为stop;version/api_key这类 Genkit 记账字段被剔除;OllamaConfig的extra键(例如repeatPenalty)也会以 snake_case 原样透传,保证新采样参数无需升级 SDK 即可到达服务端。build_request_kwargs(config):只提取think与keep_alive作为顶层 kwargs 返回。
一个典型用法(来自 README.md):
from genkit_ollama import OllamaConfig # 推理模型 + 32k 上下文窗口,模型常驻内存 1 小时 response = await ai.generate( model='ollama/deepseek-r1', prompt='Plan a small REST API.', config=OllamaConfig( think=True, num_ctx=32_000, keep_alive='1h', temperature=0.2, ), )2.3think参数的两层语义
think支持布尔值或low/medium/high强度字符串。在响应组装阶段,models.py 的_build_multimodal_chat_response/_build_generate_response会优先读取 Ollama 响应中的message.thinking(或generate响应的thinking字段),将其包装为前置的ReasoningPart,从而让 Dev UI 把思维链与最终答案分开渲染;当模型没有独立的thinking字段、而是把思维链内联在<think>...</think>标签中时,只要请求显式开启了think(_thinking_requested判定),插件会用_parse_thinking正则((?is)<(?:think|thinking)>(.*?)</(?:think|thinking)>,与 Go 插件的thinkingRegex对齐)把思维链剥离出来。该兜底仅作用于完整(非流式)响应,避免标签在流式分片中被打断而误判。
三、OllamaSupports.media:视觉模型的选入式能力声明
OllamaSupports定义于 models.py,默认值为tools=True, media=False:
class OllamaSupports(BaseModel): tools: bool = True media: bool = Falsemedia默认关闭是刻意设计:媒体能力按模型逐个选入(opt-in),避免向 Dev UI 虚假声明底层模型并不具备的能力。启用方式如下(README.md):
from genkit_ollama import ModelDefinition, Ollama, OllamaSupports Ollama(models=[ModelDefinition(name='llava', supports=OllamaSupports(media=True))])需要说明的是,ModelDefinition默认api_type=OllamaAPITypes.CHAT(见 models.py 与 constants.py 中CHAT/GENERATE两个枚举值)。与之相关的多媒体实现细节是:Ollama Python 客户端的Image类型只接受 base64 字符串、原始字节或本地文件路径,不接受 HTTP URL 或完整 data URI。因此 models.py 的_resolve_image会分三种情况预处理MediaPart.url:
- data URI(
data:image/jpeg;base64,...):剥掉前缀,返回裸 base64 字符串; - HTTP/HTTPS URL:用共享的
get_cached_client下载为原始字节,并附带User-Agent: Genkit/1.0 (...)请求头,规避 Wikipedia 等站点对无 UA 请求的 403 拦截; - 本地路径 / 裸 base64:原样透传给
Image。
这也是 Python 插件与 JS 插件唯一的行为分叉:JS 侧由 Ollama 服务端原生处理 URL 抓取,而 Python 侧必须客户端显式下载。
四、request_headers:从"存而不用"到"按请求解析"
CHANGELOG 明确记录了两点:request_headers现在接受同步或异步可调用对象,且被真正传播到ollama.AsyncClient(此前是"存了但从未发送")。类型定义在 plugin_api.py:
@dataclass(frozen=True) class RequestHeaderParams: server_address: str model: ModelDefinition | EmbeddingDefinition | None = None model_request: ModelRequest | None = None embed_request: EmbedRequest | None = None RequestHeaderFunction = Callable[ [RequestHeaderParams], dict[str, str] | None | Awaitable[dict[str, str] | None], ] RequestHeaders = dict[str, str] | RequestHeaderFunctionRequestHeaderParams与 JS 插件的RequestHeaderFunction参数对齐:回调可以拿到服务器地址、目标模型/嵌入器定义以及完整请求对象,从而签发"每个请求专用"的短时 token。
# 静态头:一次应用到缓存的客户端 Ollama(request_headers={'Authorization': 'Bearer <token>'}) # 异步解析头:每次请求重新求值,短时 token 自动续期 from genkit_ollama import RequestHeaderParams async def auth_headers(params: RequestHeaderParams) -> dict[str, str]: return {'Authorization': f'Bearer {await mint_token(params.server_address)}'} Ollama(request_headers=auth_headers, timeout=60.0)底层机制(plugin_api.py)值得展开:
- 静态 dict:直接烤进按事件循环缓存的共享客户端(
loop_local_client),跨请求复用、常开不关闭; - 可调用对象:
_client_for_request每次请求都调用一次(inspect.isawaitable支持同步与异步两种形态),因为 Ollama SDK 在构造时就把 header 烤进客户端、没有按请求注入 header 的钩子,所以每次都要新建一个临时客户端,并在退出时关闭其内部 httpx 连接池(通过_client.aclose(),幂等),防止长驻进程累积连接池。
plugin_api_test.py 中的test_sync_callable_headers_resolved_per_request、test_async_callable_headers_resolved_per_request、test_model_action_passes_request_context_to_header_callable与test_embedder_action_passes_request_context_to_header_callable分别验证了:可调用头在init()时不会被急切求值、每次请求重新解析、模型/嵌入器请求上下文正确传递、以及每次请求的连接池确实被关闭。
五、timeout:直达底层 httpx 客户端
timeout构造参数会原样转发给ollama.AsyncClient(其内部即 httpx),实现位于 plugin_api.py:
kwargs = {'host': self.server_address, 'headers': ...} if self.timeout is not None: kwargs['timeout'] = self.timeout return ollama_api.AsyncClient(**kwargs)注意None时完全省略该参数(使用 SDK 默认值),只有显式给出数值(秒)才透传。测试test_make_client_forwards_host_headers_and_timeout与test_make_client_omits_timeout_when_none(plugin_api_test.py)分别锁定了这两种行为。
六、OllamaConnectionError:让"连不上"变成可操作提示
新增的OllamaConnectionError(errors.py 中定义,继承内建ConnectionError)由wrap_connection_errors(server_address)上下文管理器统一产生,覆盖两类失败:
- Ollama SDK 把
httpx.ConnectError转成内建ConnectionError再抛出的情况; - SDK 未拦截的超时(
httpx.ReadTimeout/PoolTimeout等httpx.TransportError)。
真正的服务端 HTTP 状态错误(SDK 转成ollama.ResponseError,或裸HTTPStatusError)不会被误包装。错误消息会带上尝试连接的地址,例如"Cannot reach the Ollama server at http://127.0.0.1:11434. Start it with ollama serve (or set server_address to a reachable host).",超时则有独立的"timed out"文案。一个值得注意的边界:图片 URL 的抓取发生在wrap_connection_errors之外(models.py),因此图片宿主不可达不会被打扮成"Ollama 服务器宕机",测试test_model_action_does_not_wrap_media_fetch_error专门验证了这一点。
排查建议:先确认ollama serve是否在运行,再确认server_address指向可达主机;默认地址为http://127.0.0.1:11434(constants.py)。
七、EmbeddingDefinition根导出与嵌入能力
CHANGELOG 提到EmbeddingDefinition现在可以从包根genkit_ollama直接导入(此前只能从genkit_ollama.embedders子模块导入)。查看init.py,包根现在统一导出EmbeddingDefinition、ModelDefinition、Ollama、OllamaConfig、OllamaConnectionError、OllamaSupports、RequestHeaderFunction、RequestHeaderParams、RequestHeaders、ollama_name与package_name。
EmbeddingDefinition(embedders.py)包含name与可选的dimensions(用于信息展示或未来截断支持)。嵌入流程由OllamaEmbedder.embed实现:把 Genkit 的EmbedRequest文档内容展平为字符串列表,调用client.embed(model=..., input=...),再包装为EmbedResponse。基础用法:
from genkit import Genkit from genkit_ollama import EmbeddingDefinition, ModelDefinition, Ollama ai = Genkit( plugins=[ Ollama( models=[ModelDefinition(name='llama3.2')], embedders=[EmbeddingDefinition(name='nomic-embed-text')], ) ], model='ollama/llama3.2', ) embeddings = await ai.embed(embedder='ollama/nomic-embed-text', content='local inference') print(len(embeddings[0].embedding))八、可运行示例:chat / 流式 / 工具调用 / 嵌入一网打尽
CHANGELOG 记录了示例程序位于py/samples/ollama-sample/,覆盖聊天、流式、工具调用与嵌入四种场景,配合ai.run_main(...)作为完整入口(注意 README 提醒:await必须放在async def中,模块顶层直接写会抛SyntaxError)。完整可复现的安装与启动步骤如下(见 README.md):
# 1. 安装依赖 uv add genkit genkit-ollama # 2. 启动本地 Ollama 服务(默认 http://127.0.0.1:11434) ollama serve # 3. 提前拉取要用到的模型 ollama pull llama3.2 ollama pull nomic-embed-text流式生成与工具调用的代码形态:
# 流式 stream_response = ai.generate_stream(prompt='Stream a haiku about Ollama.') async for chunk in stream_response.stream: print(chunk.text, end='', flush=True) final = await stream_response.response # 工具调用:Ollama 工具输入必须是 object schema,基本类型请用 Pydantic 包一层 from pydantic import BaseModel, Field class WeatherInput(BaseModel): city: str = Field(description='City to look up') @ai.tool() async def current_weather(input: WeatherInput) -> str: return f'{input.city} is 18°C and partly cloudy.' response = await ai.generate(prompt='What is the weather in London?', tools=['current_weather'])Schema 约束输出(JSON)同样开箱即用:
from pydantic import BaseModel class Haiku(BaseModel): line_one: str line_two: str line_three: str response = await ai.generate(prompt='Write a haiku about local models.', output_schema=Haiku) print(response.output)九、元数据修正:按 API 类型声明能力
CHANGELOG 指出插件元数据改为反映每种 API 类型的能力:generateAPI 不再声明multiturn/tools。这由ollama_model_info(plugin_api.py)实现——只有CHAT类型模型才声明multiturn、tools,而media还需supports.media=True双重门控。测试锁定了三种形态(plugin_api_test.py):
- CHAT + media 模型:
multiturn/tools/media全为True; - GENERATE 模型:三者全为
False(systemRole仍为True); - 动态解析(未预配置)的模型:广告完整泛用能力集(
tools=True, media=True),与 JS 的GENERIC_MODEL_INFO、Go 的defaultOllamaSupports对齐——因为动态发现的模型无法做能力探测。
十、两处行为修复:top_p与工具 schema 推断
10.1top_p映射修复
此前ModelConfig中的top_p会被原样发送为topP(驼峰)而被 Ollama 忽略;现在build_request_options统一把配置键做to_snake归一化(models.py),topP正确落为top_p进入ollama.Options,并先经Options模型做类型强制(Genkit 把top_k等类型为 float,而 Ollama 需要 int),再把Options未收录的新参数(如min_p)合并回去,保证新旧采样参数都能到达服务端。
10.2 工具输入 schema 推断
_convert_parameters(models.py)规定:Ollama 只支持 object 类型的工具输入(与 JS 的isValidOllamaTool对齐),非 object 直接抛ValueError。此前"声明了properties却省略type"的 schema 会被丢弃,现在会推断为 object schema;属性类型解析(_property_type)还兼容Optional[str]这类anyOf/oneOf联合 schema,把它们映射为 OllamaProperty.type接受的列表形式,避免required指向不存在的属性。README 中"用 Pydantic 包一层基本类型"的建议正是对这一约束的呼应。
十一、小结:从变更日志到可用插件
回到 CHANGELOG 本身,pyproject.toml 显示当前包版本为0.11.0(Beta 阶段,支持 Python 3.10–3.14),依赖genkit、ollama>=0.5.3,<1.0与structlog>=25.2.0。把 CHANGELOG 的每一条改动与源码、测试对照后可以看到,这次"转正"的实质是一次完整的工程化收口:配置参数从"能传"到"传得对"(snake_case 归一化与顶层 kwargs 分流)、能力声明从"拍脑袋"到"按 API 类型如实上报"、网络行为从"存而不用"到"按请求解析 + 连接池管理"、错误从"晦涩的传输异常"到"带地址与修复提示的OllamaConnectionError"。对于需要在本地硬件上运行 LLM、追求数据不出机器的开发者而言,这份插件已经覆盖了从单轮问答、流式输出、工具调用、视觉输入到向量嵌入的完整使用链路。
【免费下载链接】genkitOpen-source framework for building agentic apps in JavaScript, Go, Dart, and Python, built and used in production by Google项目地址: https://gitcode.com/GitHub_Trending/ge/genkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考