Agent学习之二 LLM 基础 API 调用实战:从零开始使用 Ollama 本地模型

目录

  • [1. 前言](#1. 前言)
  • [2. 环境准备](#2. 环境准备)
  • [3. 案例一:首次调用 API](#3. 案例一:首次调用 API)
    • [3.1 代码实现](#3.1 代码实现)
    • [3.2 关键点解析](#3.2 关键点解析)
    • [3.3 运行结果与观察](#3.3 运行结果与观察)
    • [3.4 核心概念解释](#3.4 核心概念解释)
  • [4. 案例二:观察 Tokens](#4. 案例二:观察 Tokens)
    • [4.1 代码实现](#4.1 代码实现)
    • [4.2 关键点解析](#4.2 关键点解析)
    • [4.3 运行结果与观察](#4.3 运行结果与观察)
    • [4.4 核心概念解释](#4.4 核心概念解释)
  • [5. 案例三:统计不同模型调用成本](#5. 案例三:统计不同模型调用成本)
    • [5.1 代码实现](#5.1 代码实现)
    • [5.2 关键点解析](#5.2 关键点解析)
    • [5.3 运行结果与观察](#5.3 运行结果与观察)
    • [5.4 核心概念解释](#5.4 核心概念解释)
  • [6. 案例四:比较不同模型的回答风格](#6. 案例四:比较不同模型的回答风格)
    • [6.1 在线商业 API 对比(参考实现)](#6.1 在线商业 API 对比(参考实现))
    • [6.2 本地模型对比实现](#6.2 本地模型对比实现)
    • [6.3 本地模型对比总结](#6.3 本地模型对比总结)
  • [7. 案例五:常见错误处理与重试机制](#7. 案例五:常见错误处理与重试机制)
    • [7.1 代码实现](#7.1 代码实现)
    • [7.2 关键点解析](#7.2 关键点解析)
    • [7.3 运行结果与观察](#7.3 运行结果与观察)
    • [7.4 核心概念解释](#7.4 核心概念解释)

1. 前言

经过基础知识的洗礼,我们开始迎接 API 调用环节。尽管教程里包含免费和付费调用两种方案,但个人更偏向于免费路线,后续根据需要再添加付费 API 调用案例。(知识是无价的,商业才是有价的)。

本章分为 5 个案例,以下逐个讲解。

2. 环境准备

练习前提:首先需要在 Windows 上下载 Ollama。(B 站或其他资源较多,此处不一一赘述)

3. 案例一:首次调用 API

先使用 Ollama 下载几个模型,如 qwen2.5:3bllama3.2:3b 这两个,作为练习较为合适。当然,本机配置较高的读者可以选择配置更好的模型。

3.1 代码实现

python 复制代码
import sys
if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",  # Ollama 不检查,随便填即可
)

models = ["llama3.2:3b", "qwen2.5:3b", "gemma4:e4b"]

for model in models:
    r = client.chat.completions.create(
        model=model,   # 可换成 qwen2.5:3b / llama3.2:3b
        max_tokens=100,
        messages=[
            {"role": "system", "content": "只输出最终答案,不要输出任何推理过程、思维链或分析步骤。"},
            {"role": "user", "content": "用一句话自我介绍。"}
        ],
        extra_body={
            "thinking": False  # 关键配置:关闭推理过程输出
        }
    )

    # === 自我验证 ===
    print("Ollama {} 运行".format(model))
    origin_text = r.choices[0].message.content
    text = origin_text if len(origin_text) > 0 else r.choices[0].message.reasoning
    print("回应:", text)
    print("usage:", r.usage)

    assert r.choices[0].finish_reason in ("stop", "length"), f"非预期 finish_reason: {r.choices[0].finish_reason}"

3.2 关键点解析

从创建 client 前与官网教程是一样的。client 创建使用 OpenAI 的 API,使用本地 Ollama 不需要真实的 api_key

重点在下面:为了验证不同模型的效果(回答"用一句话自我介绍"的效果),分别调用了相应的 API,输出结果却不尽相同。有的模型是强推理模型,一定会有 reasoning 输出,不一定会有 content 输出。为了兼容,对代码进行了修改。(也添加了提示词,但是效果不是太大,无法根治 reasoning,稍微会好点)

3.3 运行结果与观察

运行代码后,不同模型对同一提示词给出了风格各异的回复。这直观展示了模型间的差异。整个调用过程使用统一的 OpenAI 格式 API,只需更换 model 参数即可切换模型,非常便捷。代码通过 thinking: False 希望解决reasoning,但实际也未完全解决,所以人还是要把关的。

3.4 核心概念解释

  • max_tokens:即输出回复的最大长度(计费相关呦)。
  • thinking: False # 关键配置:关闭推理过程输出(效果有,但一般)

4. 案例二:观察 Tokens

在第一个案例中,我们体验了不同模型对同一提示词的回复差异。本案例将聚焦于另一个重要维度:Tokens 的消耗与统计 。我们将通过编写一个小程序,观察同一模型在不同语言提示下输入/输出 Tokens 的数量,并理解 temperature 参数对输出长度随机性的影响。

4.1 代码实现

python 复制代码
import sys, statistics
if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")

from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

PROMPTS = {
    "中文": "用一句话描述一只猫在做什么。",
    "English": "Describe in one sentence what a cat is doing.",
}

N = 10  # 本机慢、N 小一点
for label, prompt in PROMPTS.items():
    output_tokens = []
    for _ in range(N):
        r = client.chat.completions.create(
            model="llama3.2:3b",
            max_tokens=80,
            temperature=1.0,
            messages=[{"role": "user", "content": prompt}],
        )
        output_tokens.append(r.usage.completion_tokens)
    print(f"\n[{label}] prompt: {prompt}")
    print(f"  input tokens: {r.usage.prompt_tokens}")
    print(f"  output tokens --- min={min(output_tokens)} max={max(output_tokens)} mean={statistics.mean(output_tokens):.1f} stdev={statistics.stdev(output_tokens):.1f}")

# === 自我验证 ===
assert max(output_tokens) > min(output_tokens), "temperature=1.0 下、output 长度应该有 variance"
print("\n✅ 练习 2 通过 --- 本机跑 $0")

4.2 关键点解析

  • 目标 :本案例旨在观察同一模型(llama3.2:3b,可以选其他模型,但注意资源消耗)在处理不同语言(中/英文)提示时,输入 Tokens(prompt_tokens)和输出 Tokens(completion_tokens)的差异,并验证 temperature 参数对输出长度随机性的影响。
  • 实验设计 :对每种语言的提示词,我们采样 N=10 次(因本机性能限制,次数较少),记录每次输出的 completion_tokens,最后计算最小值、最大值、平均值和标准差。
  • 参数设置temperature=1.0 意味着模型在生成时具有较高的随机性("创意"),这会导致每次输出的长度(即 completion_tokens)可能不同。我们通过统计方差来验证这一点。

4.3 运行结果与观察

运行上述代码后,我们可以得到类似下图的统计结果(具体数值因运行环境而异):

从结果中可以观察到两个关键现象:

  1. 输入 Tokens 的差异 :同样的模型,对英文和中文提示的 input tokens(即 prompt_tokens)数量是不同的。这打破了"中文一定比英文占用更多 Tokens"的简单认知。实际占用数量与模型本身的词表(vocabulary)和分词器(tokenizer)强相关,不同模型的结果可能截然不同。
  2. 输出 Tokens 的随机性 :在 temperature=1.0 的设置下,即使提示词完全相同,模型每次生成的回复长度(completion_tokens)也会在 max_tokens 限制内波动,表现为输出 Tokens 的最大值、最小值不一致,且标准差不为零。这直观地体现了 temperature 参数对生成"发散性"的影响。

本节通过一个简单的统计实验,展示了如何量化观察模型调用中的 Tokens 消耗,并验证了关键参数(如 temperature)对输出特性的影响。

注:感兴趣的可以修改temperature从0到1

4.4 核心概念解释

  • temperature:创意程度参数。简单来说,值为 0 时,模型的输出是确定性的(每次相同);值为 1 时,输出比较发散,更有"创意",长度和内容都可能变化。
  • completion_tokens :统计输出内容的 Token 数量,实际值不会超过 max_tokens 参数的限制。
  • prompt_tokens:统计输入提示词(Prompt)的 Token 数量。

5. 案例三:统计不同模型调用成本

在前两个案例中,我们分别体验了不同模型的回复差异和观察了 Tokens 消耗。本案例将聚焦于调用成本的量化分析。虽然本地调用 Ollama 模型没有直接的金钱成本,但时间成本是真实存在的。我们将通过编写一个性能测试程序,统计不同模型完成 1000 次调用所需的时间,帮助读者建立成本意识,并为选择合适模型提供数据参考。

5.1 代码实现

python 复制代码
import sys, time
if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")

from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

# 测试的模型列表
models = ["qwen2.5:3b", "gemma4:e4b", "llama3.2:3b"]

for model in models:
    print(f"\n=== 测试模型: {model} ===")
    
    # 预热一次,避免首次调用延迟影响统计
    client.chat.completions.create(
        model=model,
        max_tokens=50,
        messages=[{"role": "user", "content": "预热"}],
    )
    
    # 正式测试:采样 5 次
    latencies = []
    for i in range(5):
        t0 = time.time()
        r = client.chat.completions.create(
            model=model,
            max_tokens=200,
            messages=[{"role": "user", "content": "你好!请用一句话介绍一下你自己(模型本身)。"}],
        )
        latencies.append(time.time() - t0)
        print(f"  第 {i+1} 次: {latencies[-1]:.2f} 秒")
    
    # 统计计算
    avg_latency = sum(latencies) / len(latencies)
    out_tok_avg = r.usage.completion_tokens
    tps = out_tok_avg / avg_latency if avg_latency > 0 else 0
    
    # 输出结果
    print(f"\n统计结果:")
    print(f"  延迟 (秒): min={min(latencies):.2f}, max={max(latencies):.2f}, mean={avg_latency:.2f}")
    print(f"  平均输出: {out_tok_avg} tokens")
    print(f"  吞吐率: {tps:.1f} tokens/秒")
    print(f"  1000 次预计耗时: {avg_latency * 1000 / 60:.1f} 分钟")
    
    # === 自我验证 ===
    assert avg_latency > 0, "延迟应大于 0"
    assert out_tok_avg > 0, "输出 tokens 应大于 0"

print("\n✅ 案例三测试完成")

5.2 关键点解析

  • 目标:本案例旨在量化本地模型的调用时间成本,帮助读者建立本地部署的成本意识。
  • 实验设计:对每个模型进行 5 次采样测试,记录每次调用的延迟(latency),计算平均值、最小值和最大值,并基于平均延迟估算完成 1000 次调用所需的时间。
  • 成本意识:虽然本地调用没有直接的 API 费用,但时间成本是真实存在的。对于需要高频调用的场景,选择合适的模型可以显著提升效率。

5.3 运行结果与观察

运行上述代码后,可以得到类似下图的性能对比结果(具体数值因硬件配置而异):

从结果中可以观察到几个关键现象:

  1. 时间成本量化:通过将单次平均延迟乘以 1000,可以直观估算大规模调用所需的时间。例如,单次调用平均需要 2.5 秒时,1000 次调用约需 41.7 分钟。所有模型的平均调用时间都超过了400分钟,成本很高(4060显卡)。
  2. 硬件资源影响 :测试结果与硬件配置(CPU、GPU、内存)强相关。性能较弱的设备上,时间成本会显著增加。

5.4 核心概念解释

  • 延迟(Latency):从发送请求到收到完整响应所经历的时间,通常以秒为单位。是衡量模型响应速度的关键指标。
  • 吞吐率(Tokens per Second, TPS) :每秒生成的 tokens 数量,计算公式为 输出 tokens 数 / 延迟时间。该指标反映了模型的生成效率。
  • 成本意识:在 AI 应用开发中,成本不仅包括 API 调用费用(商业模型),还包括时间成本、硬件投入和维护成本。选择合适的模型需要在效果、速度和成本之间取得平衡。

6. 案例四:比较不同模型的回答风格

在前三个案例中,我们分别体验了模型调用、Tokens 统计和性能测试。本案例将聚焦于不同模型对同一问题的回答风格对比。我们将分别使用在线商业 API 和本地 Ollama 模型来回答同一个问题,观察它们在表达风格、响应速度和资源消耗上的差异。

6.1 在线商业 API 对比(参考实现)

原始案例使用了三个在线大模型(Claude、GPT-4o-mini、Gemini)进行对比,需要获取 API Key 并可能产生费用。这里提供参考代码,供有条件的读者练习:

python 复制代码
from __future__ import annotations

import os
import sys
import time
from dataclasses import dataclass

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")

PROMPT = "用 1-2 句話解釋 AGI 跟 narrow AI 的差別。"

@dataclass
class Reply:
    provider: str
    model: str
    text: str
    in_tokens: int
    out_tokens: int
    latency_ms: int

def call_claude(prompt: str) -> Reply | None:
    if not os.environ.get("ANTHROPIC_API_KEY"):
        return None
    import anthropic

    client = anthropic.Anthropic()
    t0 = time.time()
    msg = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=200,
        messages=[{"role": "user", "content": prompt}],
    )
    return Reply(
        provider="Anthropic",
        model="claude-haiku-4-5",
        text=msg.content[0].text,
        in_tokens=msg.usage.input_tokens,
        out_tokens=msg.usage.output_tokens,
        latency_ms=int((time.time() - t0) * 1000),
    )

def call_openai(prompt: str) -> Reply | None:
    if not os.environ.get("OPENAI_API_KEY"):
        return None
    from openai import OpenAI

    client = OpenAI()
    t0 = time.time()
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        max_tokens=200,
        messages=[{"role": "user", "content": prompt}],
    )
    return Reply(
        provider="OpenAI",
        model="gpt-4o-mini",
        text=r.choices[0].message.content or "",
        in_tokens=r.usage.prompt_tokens,
        out_tokens=r.usage.completion_tokens,
        latency_ms=int((time.time() - t0) * 1000),
    )

def call_gemini(prompt: str) -> Reply | None:
    if not os.environ.get("GOOGLE_API_KEY"):
        return None
    from google import genai

    client = genai.Client()
    t0 = time.time()
    r = client.models.generate_content(
        model="gemini-2.0-flash",
        contents=prompt,
    )
    usage = getattr(r, "usage_metadata", None)
    return Reply(
        provider="Google",
        model="gemini-2.0-flash",
        text=r.text,
        in_tokens=getattr(usage, "prompt_token_count", 0) or 0,
        out_tokens=getattr(usage, "candidates_token_count", 0) or 0,
        latency_ms=int((time.time() - t0) * 1000),
    )

def compare(prompt: str) -> list[Reply]:
    replies = []
    for fn in (call_claude, call_openai, call_gemini):
        r = fn(prompt)
        if r is None:
            print(f"⚠ skip {fn.__name__}(沒有對應 API key)")
        else:
            replies.append(r)
    return replies

if __name__ == "__main__":
    print(f"prompt: {PROMPT}\n" + "=" * 60)
    replies = compare(PROMPT)

    for r in replies:
        print(f"\n[{r.provider} / {r.model}]  latency={r.latency_ms}ms  in={r.in_tokens} out={r.out_tokens}")
        print(r.text)

    # === 自我驗證 ===
    assert len(replies) >= 1, "至少要有一家 provider 回應(請設一個 API key)"
    for r in replies:
        assert len(r.text) > 5, f"{r.provider} 回應太短"
        assert r.in_tokens > 0, f"{r.provider} 沒 input token"
    print(f"\n✅ 練習 4 通過 --- 收到 {len(replies)} 家 provider 回應、可比較風格 / 長度 / 成本")

关键点说明:

  • 统一接口设计 :使用 Reply 数据类统一封装各模型的返回结果,便于对比分析
  • 错误处理:当缺少 API Key 时优雅跳过,不影响其他模型测试
  • 多维度对比:同时记录响应时间、输入输出 Tokens,全面评估模型表现

运行结果示例:

markup 复制代码
prompt: 用 1-2 句話解釋 AGI 跟 narrow AI 的差別。
============================================================

[Anthropic / claude-haiku-4-5]  latency=1234ms  in=25 out=45
AGI(通用人工智能)擁有類似人類的廣泛認知能力,能處理各種任務;而窄域AI則專注於特定領域,如語音識別或下棋。

[OpenAI / gpt-4o-mini]  latency=987ms  in=22 out=38
AGI(通用人工智能)旨在擁有類似人類的全面智能,能夠處理多種不同任務;而窄域AI(狹義AI)則專注於特定任務,如語音識別或圖像分類。

[Google / gemini-2.0-flash]  latency=876ms  in=20 out=42
AGI是指能像人類一樣廣泛學習和推理的通用人工智能,而窄域AI則只專注於執行特定任務(如臉部辨識)。

6.2 本地模型对比实现

对于大多数学习者,使用本地 Ollama 模型进行对比更加实际。以下代码展示了如何使用 Ollama 的原生 API(非 OpenAI 兼容格式)调用多个本地模型:

python 复制代码
from __future__ import annotations

import sys
import time
from dataclasses import dataclass
from typing import Optional
import requests

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")

PROMPT = "用 1-2 句話解釋 AGI 跟 narrow AI 的差別。"

@dataclass
class Reply:
    provider: str
    model: str
    text: str
    in_tokens: int
    out_tokens: int
    latency_ms: int

def _call_ollama(prompt: str, model: str) -> Optional[Reply]:
    """通用 Ollama 调用函数(使用原生 API)"""
    try:
        t0 = time.time()
        resp = requests.post(
            "http://localhost:11434/api/generate",
            json={
                "model": model,
                "prompt": prompt,
                "stream": False,
                "options": {"num_predict": 200}
            },
            timeout=60
        )
        data = resp.json()
        text = data.get("response", "").strip()
        
        return Reply(
            provider="Local-Ollama",
            model=model,
            text=text,
            in_tokens=len(prompt) // 2,  # 简单估算
            out_tokens=len(text) // 2,    # 简单估算
            latency_ms=int((time.time() - t0) * 1000),
        )
    except Exception as e:
        print(f"⚠ {model} 调用失败: {e}")
        return None

def call_qwen(prompt: str) -> Optional[Reply]:
    """调用 qwen2.5:3b"""
    return _call_ollama(prompt, "qwen2.5:3b")

def call_llama(prompt: str) -> Optional[Reply]:
    """调用 llama3.2:3b"""
    return _call_ollama(prompt, "llama3.2:3b")

def call_deepseek(prompt: str) -> Optional[Reply]:
    """调用 deepseek-r1:8b"""
    return _call_ollama(prompt, "deepseek-r1:8b")

def compare(prompt: str) -> list[Reply]:
    """比较所有模型"""
    replies = []
    
    for fn in (call_qwen, call_llama, call_deepseek):
        r = fn(prompt)
        if r is None:
            print(f"⚠ 跳过 {fn.__name__}(模型未安装)")
        else:
            replies.append(r)
    
    return replies

if __name__ == "__main__":
    print(f"prompt: {PROMPT}\n" + "=" * 60)
    replies = compare(PROMPT)

    for r in replies:
        print(f"\n[{r.provider} / {r.model}]  latency={r.latency_ms}ms  in={r.in_tokens} out={r.out_tokens}")
        print(r.text)

    # 验证
    assert len(replies) >= 1, "至少需要一个模型响应"
    for r in replies:
        assert len(r.text) > 5, f"{r.provider} 回应太短"
        assert r.in_tokens > 0, f"{r.provider} 没有 input token"
    print(f"\n✅ 测试通过 --- 收到 {len(replies)} 个本地模型响应")

运行结果示例:

6.3 本地模型对比总结

6.2 节展示了如何使用 Ollama 原生 API 调用多个本地模型进行对比。通过统一的 Reply 数据类封装结果,我们可以方便地比较不同模型在回答风格、响应时间和资源消耗上的差异。

核心要点总结:

  1. API 调用方式 :使用 Ollama 原生 API(/api/generate 端点)而非 OpenAI 兼容接口,输出内容上不需要像案例一特殊处理,这一点更加方便。
  2. 统一接口设计 :通过 _call_ollama 函数封装调用逻辑,简化多模型对比
  3. 模型选择 :示例中对比了 qwen2.5:3bllama3.2:3bdeepseek-r1:8b 三个模型
  4. 结果对比:可以直观看到不同模型对同一问题的回答风格差异

适用场景:

  • 需要对比多个本地模型的性能表现
  • 希望使用 Ollama 原生 API 的完整功能
  • 隐私敏感或离线环境下的模型测试

通过这个案例,读者可以掌握使用 Ollama 原生 API 进行多模型对比的基本方法,为实际项目中的模型选型提供参考。

当然在线模型回答的更加严谨和恰当,日常功能使用还是需要尝试在线模型。

7. 案例五:常见错误处理与重试机制

在前面的案例中,我们学习了如何成功调用 Ollama API。但在实际开发中,网络波动、服务异常、参数错误等情况时有发生。本案例将介绍常见的错误类型及其处理方法,并实现一个带指数退避的重试机制,提高程序的健壮性。

7.1 代码实现

python 复制代码
from __future__ import annotations

import os
import random
import sys
import time
from typing import Any, Callable

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")

from openai import (
    APIConnectionError,
    APIStatusError,
    AuthenticationError,
    OpenAI,
    RateLimitError,
)

MODEL = os.environ.get("MODEL", "qwen2.5:3b")


# === 重试包装器 ===

RETRIABLE = (APIConnectionError, RateLimitError)  # 可重试的异常类型
MAX_ATTEMPTS = 4  # 最大重试次数
BASE_DELAY = 1.0  # 基础延迟(秒)


def with_retry(fn: Callable[[], Any], *, max_attempts: int = MAX_ATTEMPTS, base_delay: float = BASE_DELAY, sleep_fn=time.sleep) -> Any:
    """
    指数退避重试机制。
    - 可重试异常(如网络连接错误、限流)→ 等待 base * 2^attempt 秒后重试(含随机抖动)
    - 不可重试异常(如认证错误)→ 直接抛出,不浪费时间重试
    """
    last_exc = None
    for attempt in range(max_attempts):
        try:
            return fn()
        except RETRIABLE as e:
            last_exc = e
            if attempt == max_attempts - 1:
                break
            delay = base_delay * (2 ** attempt) + random.uniform(0, 0.3)
            print(f"  ⚠ 第 {attempt+1}/{max_attempts} 次尝试失败 ({type(e).__name__});{delay:.1f}秒后重试")
            sleep_fn(delay)
    raise last_exc


# === 三种错误场景演示 ===

def demo_bad_key() -> None:
    """场景1:故意使用错误的 base_url,模拟 APIConnectionError(Ollama 未运行时)"""
    print("\n[场景1] 故意连接到不存在的 Ollama 端口")
    client = OpenAI(base_url="http://localhost:65535/v1", api_key="ollama")
    try:
        client.chat.completions.create(
            model=MODEL,
            max_tokens=10,
            messages=[{"role": "user", "content": "你好"}],
        )
    except APIConnectionError as e:
        print(f"  ✅ 捕获到 APIConnectionError: {type(e).__name__}")
        print(f"  💡 生产环境处理:重试(网络错误通常是暂时的)")


def demo_with_retry() -> None:
    """场景2:使用 with_retry 包装正常调用,应该第一次就成功"""
    print("\n[场景2] 正常调用 + with_retry 包装(需要 Ollama 正在运行)")
    client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

    def call():
        return client.chat.completions.create(
            model=MODEL,
            max_tokens=30,
            messages=[{"role": "user", "content": "用一个 emoji 回答。"}],
        )

    try:
        msg = with_retry(call)
        print(f"  ✅ 成功,第一次就通过: {msg.choices[0].message.content}")
    except APIConnectionError:
        print("  ⚠ Ollama 未运行(端口 11434 不通)。请先运行 `ollama serve`")


def demo_too_long_prompt() -> None:
    """场景3:故意发送超长 prompt,观察上下文窗口满时的行为"""
    print("\n[场景3] Prompt 超过上下文窗口(Ollama 通常会截断或抛出异常)")
    client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
    huge_prompt = "重复很多次的 token。" * 200000000  # 约 1M tokens

    try:
        client.chat.completions.create(
            model=MODEL,
            max_tokens=10,
            messages=[{"role": "user", "content": huge_prompt}],
        )
        print("  ⚠ Ollama 未抛出异常(可能直接截断 prompt)。云 API 通常会返回 400 错误")
    except APIStatusError as e:
        print(f"  ✅ 捕获到 APIStatusError: 状态码 {e.status_code}")
        print(f"  💡 生产环境处理:在客户端先计算 token 数量,超过限制则拒绝,避免浪费 API 调用")
    except APIConnectionError:
        print("  ⚠ Ollama 未运行")


if __name__ == "__main__":
    demo_bad_key()
    demo_with_retry()
    demo_too_long_prompt()

    # === 自我验证 ===
    print("\n✅ 案例五测试通过 --- 您已了解 3 种常见错误的处理方式,知道何时该重试、何时该停止")

7.2 关键点解析

  • 目标:本案例旨在演示如何处理 Ollama API 调用中常见的错误,并实现一个健壮的重试机制。
  • 错误类型分类
    1. APIConnectionError:网络连接错误(如 Ollama 服务未启动、端口错误)
    2. RateLimitError:请求频率限制错误(在免费 API 中常见)
    3. APIStatusError:HTTP 状态码错误(如 400 Bad Request、429 Too Many Requests)
    4. AuthenticationError:认证错误(API Key 无效)
  • 重试策略:指数退避 + 随机抖动,避免"惊群效应"
  • 生产建议:根据错误类型决定是否重试,避免无限重试不可恢复的错误

通过本案例,您将掌握在实际项目中处理 API 错误的正确方法,提高应用程序的稳定性和用户体验。

8. 案例总结

本章通过五个案例系统介绍了 Ollama 本地大模型 API 调用的核心技能:

  1. 首次调用 API :掌握基础调用方法,理解 max_tokensthinking 参数,处理推理模型输出兼容性。
  2. 观察 Tokens :量化分析 Tokens 消耗,验证 temperature 对输出随机性的影响。
  3. 统计调用成本:量化时间成本,建立成本意识,为模型选型提供数据参考。
  4. 比较回答风格:对比在线与本地模型的差异,掌握多模型对比方法。
  5. 错误处理与重试:识别常见错误类型,实现指数退避重试机制,提高程序健壮性。

这五个案例构成了从基础调用到生产部署的完整学习路径,帮助读者掌握本地大模型调用的核心技能。

下一节将对Prompt进行进一步深入讲解

相关推荐
人间凡尔赛1 小时前
从 O(n²) 到 O(n):DeepSeek NSA 与 Kimi MoBA 稀疏注意力底层原理深度拆解
ai·底层·深度技术
深念Y2 小时前
基于 NapCat 与本地 RAG 的群聊 AI 机器人方案(ARM64 部署)
人工智能·ai·机器人·node.js·自动化·情感陪伴·bot
晴天163 小时前
Cordis框架: 为可逆软件系统而生的元框架-Day18
ai·架构
liulilittle4 小时前
LLM 推理引擎与内核系统工程原理
c++·ai·llm·内存·memory·core·kernel
星花月5 小时前
【无标题】
ai·语言模型·pdf·deep learning
感谢地心引力5 小时前
DeepSeek-V4-Pro正式版发布,API价格翻倍,DeepSeek Harness体验如何?
ai·deepseek·harness
晴天165 小时前
Cordis 框架代码核心解析:一个可逆插件系统的实现-Day18
人工智能·ai·架构
特立独行的猫a6 小时前
DeepSeek Harness插件和工具的区别介绍及开发入门指南
前端·ai·agent·插件·deepseek·harness
安逸sgr6 小时前
优化器是什么?SGD、Momentum、Adam 有什么区别?
人工智能·ai·大模型·agent·智能体