Chapter 06

Python 深度集成

使用 Ollama 官方 Python 库,解锁同步/异步调用、实时流式输出、结构化 JSON 响应、多模态图片理解等高级能力,最终构建一个完整的命令行聊天机器人。

安装与基础用法

Ollama 官方提供了 Python 和 JavaScript 两个 SDK,Python 库的设计风格与 OpenAI SDK 相似,学习成本极低:

# 安装官方 Python 库
pip install ollama

# 或使用 uv(更快的包管理器)
uv add ollama

# 验证安装
python3 -c "import ollama; print(ollama.__version__)"
import ollama

# ── 最简单的用法:generate ──────────────────────────────
response = ollama.generate(
    model="llama3.2",
    prompt="用一句话解释深度学习"
)
print(response["response"])

# ── chat 多轮对话 ────────────────────────────────────────
response = ollama.chat(
    model="llama3.2",
    messages=[
        {"role": "system", "content": "你是一个 Python 专家"},
        {"role": "user", "content": "什么是装饰器?"},
    ]
)
print(response["message"]["content"])

# ── 传入生成参数 ──────────────────────────────────────
response = ollama.generate(
    model="qwen2.5:7b",
    prompt="写一段快速排序的 Python 代码",
    options={
        "temperature": 0.1,
        "num_ctx": 4096,
        "seed": 42  # 固定种子,结果可复现
    }
)
print(response["response"])

# ── 列出已安装模型 ────────────────────────────────────
models = ollama.list()
for model in models["models"]:
    print(f"{model['name']:30s} {model['size'] / 1e9:.1f} GB")

流式输出(stream=True)

流式输出让 AI 回复像打字机一样逐字出现,大幅提升用户体验:

import ollama
import sys

# ── 流式 generate ────────────────────────────────────
print("AI: ", end="", flush=True)
for chunk in ollama.generate(
    model="llama3.2",
    prompt="用100字介绍 Python 的历史",
    stream=True
):
    print(chunk["response"], end="", flush=True)
    # chunk["done"] == True 时为最后一个 chunk
    if chunk["done"]:
        # 最后一个 chunk 包含性能统计
        total_ms = chunk["total_duration"] / 1_000_000
        tokens = chunk["eval_count"]
        speed = tokens / (chunk["eval_duration"] / 1e9)
        print(f"\n\n[统计] {tokens} tokens | {speed:.1f} tok/s | {total_ms:.0f}ms")

# ── 流式 chat ────────────────────────────────────────
messages = [{"role": "user", "content": "写一首关于编程的七言绝句"}]

print("AI: ", end="", flush=True)
for chunk in ollama.chat(
    model="qwen2.5:7b",
    messages=messages,
    stream=True
):
    content = chunk["message"]["content"]
    print(content, end="", flush=True)
print()

结构化输出(JSON 模式)

通过 format="json" 强制模型以 JSON 格式输出,便于程序解析:

import ollama
import json
from typing import TypedDict

# ── 基础 JSON 模式 ──────────────────────────────────────
response = ollama.generate(
    model="qwen2.5:7b",
    prompt="""
    分析以下代码的质量,以 JSON 格式返回:
    {
      "score": 1-10的评分,
      "issues": ["问题1", "问题2"],
      "suggestions": ["建议1", "建议2"],
      "summary": "一句话总结"
    }

    代码:
    def f(x):
        if x > 0:
            return x * 2
        return 0
    """,
    format="json",
    options={"temperature": 0.1}
)

result = json.loads(response["response"])
print(f"评分:{result['score']}/10")
print(f"问题:{result['issues']}")
print(f"建议:{result['suggestions']}")

# ── 批量信息提取示例 ────────────────────────────────────
def extract_entities(text: str) -> dict:
    """从文本中提取实体信息(姓名、日期、地点等)。"""
    response = ollama.generate(
        model="qwen2.5:7b",
        prompt=f"""从以下文本提取实体,以 JSON 返回:
{{
  "persons": ["人名列表"],
  "dates": ["日期列表"],
  "locations": ["地点列表"],
  "organizations": ["组织列表"]
}}

文本:{text}""",
        format="json",
        options={"temperature": 0}
    )
    return json.loads(response["response"])

sample_text = "2025年3月,张伟在北京参加了阿里巴巴举办的技术峰会"
entities = extract_entities(sample_text)
print(json.dumps(entities, ensure_ascii=False, indent=2))

异步客户端(async/await)

在 Web 服务(FastAPI/aiohttp)等异步场景中,使用异步客户端避免阻塞事件循环:

import asyncio
import ollama

async def generate_async(prompt: str) -> str:
    """异步生成文本。"""
    client = ollama.AsyncClient()
    response = await client.generate(
        model="llama3.2",
        prompt=prompt
    )
    return response["response"]

async def stream_async(prompt: str):
    """异步流式生成。"""
    client = ollama.AsyncClient()
    async for chunk in await client.generate(
        model="llama3.2",
        prompt=prompt,
        stream=True
    ):
        print(chunk["response"], end="", flush=True)
    print()

# 并发请求:同时处理多个问题
async def batch_generate(prompts: list[str]) -> list[str]:
    """并发生成多个响应。"""
    client = ollama.AsyncClient()
    tasks = [
        client.generate(model="llama3.2", prompt=p)
        for p in prompts
    ]
    results = await asyncio.gather(*tasks)
    return [r["response"] for r in results]

# 主程序
async def main():
    questions = [
        "什么是 Python GIL?",
        "解释事件循环(Event Loop)",
        "async/await 的原理是什么?"
    ]
    answers = await batch_generate(questions)
    for q, a in zip(questions, answers):
        print(f"\nQ: {q}\nA: {a[:100]}...\n")

asyncio.run(main())

多模态:图片理解

支持多模态的模型(LLaVA、Gemma 3、Llama 3.2-Vision)可以分析图片:

import ollama
import base64
from pathlib import Path

# 首先确保已安装多模态模型
# ollama pull llava:13b      ← 通用视觉模型
# ollama pull llama3.2-vision  ← Meta 视觉版
# ollama pull gemma3:12b     ← Gemma 3 支持多模态

def analyze_image(image_path: str, question: str) -> str:
    """
    分析图片并回答问题。
    image_path: 本地图片文件路径
    question: 关于图片的问题
    """
    # 读取并转换为 base64
    image_data = Path(image_path).read_bytes()
    image_b64 = base64.b64encode(image_data).decode()

    response = ollama.chat(
        model="llava:13b",
        messages=[{
            "role": "user",
            "content": question,
            "images": [image_b64]  # base64 字符串列表
        }]
    )
    return response["message"]["content"]

# 使用示例
answer = analyze_image("./screenshot.png", "描述图中的内容,如果有代码请解释")
print(answer)

# 也可以直接传图片文件路径(更简洁)
response = ollama.chat(
    model="llava:13b",
    messages=[{
        "role": "user",
        "content": "这张图里有什么?",
        "images": ["./photo.jpg"]  # 文件路径(ollama 库自动读取)
    }]
)

完整实战:命令行聊天机器人

将本章所有知识整合,构建一个功能完整的命令行聊天机器人:

#!/usr/bin/env python3
"""
local_chat.py — 基于 Ollama 的命令行聊天机器人
功能:流式输出、历史记录、多模型切换、会话导出
"""
import ollama
import json
import sys
from datetime import datetime
from pathlib import Path

class LocalChat:
    def __init__(self, model: str = "qwen2.5:7b", system: str = ""):
        self.model = model
        self.messages = []
        if system:
            self.messages.append({"role": "system", "content": system})

    def chat(self, user_input: str) -> str:
        """发送消息,流式打印响应,返回完整文本。"""
        self.messages.append({"role": "user", "content": user_input})
        print(f"\n\033[92m{self.model}\033[0m: ", end="", flush=True)

        full_response = ""
        for chunk in ollama.chat(
            model=self.model,
            messages=self.messages,
            stream=True
        ):
            token = chunk["message"]["content"]
            print(token, end="", flush=True)
            full_response += token

        print("\n")
        self.messages.append({"role": "assistant", "content": full_response})
        return full_response

    def clear_history(self):
        """清除对话历史,保留 system 消息。"""
        system_msgs = [m for m in self.messages if m["role"] == "system"]
        self.messages = system_msgs
        print("[历史已清除]")

    def export_history(self, path: str = None):
        """导出对话历史到 JSON 文件。"""
        if not path:
            path = f"chat_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        Path(path).write_text(
            json.dumps(self.messages, ensure_ascii=False, indent=2),
            encoding="utf-8"
        )
        print(f"[已导出到 {path}]")

    def run(self):
        """启动交互式会话。"""
        print(f"=== 本地 AI 对话 [{self.model}] ===")
        print("命令:/clear 清除历史 | /switch 切换模型 | /export 导出 | /quit 退出\n")

        while True:
            try:
                user_input = input("\033[96m你\033[0m: ").strip()
            except (EOFError, KeyboardInterrupt):
                print("\n再见!")
                break

            if not user_input:
                continue
            elif user_input == "/quit":
                print("再见!")
                break
            elif user_input == "/clear":
                self.clear_history()
            elif user_input == "/export":
                self.export_history()
            elif user_input.startswith("/switch "):
                self.model = user_input.split()[1]
                print(f"[已切换到 {self.model}]")
            else:
                try:
                    self.chat(user_input)
                except Exception as e:
                    print(f"[错误] {e}")

if __name__ == "__main__":
    model = sys.argv[1] if len(sys.argv) > 1 else "qwen2.5:7b"
    bot = LocalChat(model=model, system="你是一个有帮助的中文助手")
    bot.run()
# 使用方式
python3 local_chat.py                   # 默认使用 qwen2.5:7b
python3 local_chat.py llama3.2          # 指定模型
python3 local_chat.py deepseek-r1:14b  # 推理模型
生产集成建议 在 FastAPI 中集成 Ollama 时,使用 AsyncClient 配合 StreamingResponse 将流式 token 直接推送到前端。示例:return StreamingResponse(stream_generator(), media_type="text/event-stream")。避免在同步 FastAPI 路由中调用同步 ollama 函数,这会阻塞事件循环。
本章小结 Python 集成要点:安装 ollama 库后,ollama.generate() 单轮生成,ollama.chat() 多轮对话;stream=True 实现实时输出;format="json" 实现结构化输出;AsyncClient 用于异步场景;多模态传 images 参数。下一章将这些能力组合,构建完整的本地 RAG 系统。