用Ollama开发聊天程序实战

与Ollama服务通信支持以下三种方式:

1. cURL (命令行工具)

详细说明:cURL是一个利用URL语法在命令行下工作的文件传输工具。它是与Ollama交互最原始、最直接的方式,相当于手动构造并发送HTTP请求。你需要在终端(命令提示符)中逐条输入命令,并观察返回的原始JSON数据。

主要特点

  • 无需安装:大多数操作系统(Windows 10/11、macOS、Linux)都预装了cURL。

  • 完全控制:你可以精确控制请求头(Header)和请求体(Body)的每一个字段。

  • 即时反馈:适合快速测试API接口是否工作正常,查看原始响应结构。

  • 缺点:手动处理复杂的JSON和流式响应比较繁琐,不适合构建大型应用。

详细使用示例

场景1:测试服务是否可用(列出已安装的模型)

curl http://localhost:11434/api/tags

  • 返回示例:你会看到一个JSON数组,列出了所有已下载的模型名称、大小、修改时间等信息。

  • 用途 :确认Ollama服务已启动,并查看可用的模型名称(需要完整名称,如qwen3:4b)。

场景2:使用/api/generate端点进行单次文本生成

这个端点适合简单的问答或文本补全任务。

curl http://localhost:11434/api/generate -d '{ "model": "qwen3:4b", "prompt": "请用中文简单介绍一下人工智能。", "stream": false }'

  • 参数解释

    • -d:指定要发送的JSON数据。

    • "model":必须使用ollama list中显示的完整模型名称。

    • "prompt":你的提问内容。

    • "stream": false:设置为false表示等待完整响应后一次性返回;若为true,则会以Server-Sent Events(SSE)流式返回数据块。

  • 返回示例 :一个包含modelcreated_atresponse(生成的文本)、done(是否完成)等字段的JSON对象。

场景3:使用/api/chat端点进行对话(支持多轮消息)

/api/chat端点的结构更清晰,适合构建聊天机器人。

curl http://localhost:11434/api/chat -d '{ "model": "gemma3:4b", "messages": {"role": "user", "content": "你好,请介绍一下你自己。"} , "stream": false }'

  • 参数解释messages是一个消息数组,可以包含system(系统设定)、user(用户)和assistant(助手)角色的消息,实现上下文对话。

  • 返回示例 :响应中的message字段包含了助手的回复内容。

2. Python 库 (ollama)

详细说明:这是Ollama官方提供的Python SDK,它将底层的HTTP请求封装成了直观的Python函数。你不需要关心URL、JSON序列化或状态码,库会帮你处理这些细节。

主要特点

  • 高级封装:代码简洁,符合Python风格,易于学习和使用。

  • 自动管理:自动处理JSON的序列化与反序列化,异常会以Python异常的形式抛出。

  • 流式支持 :通过简单的参数控制(如stream=True)即可实现流式响应,并迭代处理每个数据块。

  • 生态集成:可以无缝集成到现有的Python AI项目(如数据分析、机器学习流水线)中。

详细使用示例

第一步:安装库

pip install ollama

第二步:编写Python代码

场景1:最简单的同步调用(使用/api/chat

复制代码
import ollama

# 调用chat接口,发送一条用户消息
response = ollama.chat(
    model='qwen3:4b',  # 指定模型
    messages=[{'role': 'user', 'content': '为什么天空是蓝色的?'}]
)

# 直接打印助手的回复内容
print(response['message']['content'])

场景2:实现流式响应(逐字输出)

这对于提升用户体验非常重要,可以让你像在使用ChatGPT一样看到文字逐个生成。

import ollama # 启用流式输出 stream = ollama.chat( model='gemma3:4b', messages={'role': 'user', 'content': '写一首关于夏天的短诗。'}, stream=True # 关键参数 ) # 迭代处理每一个响应块 for chunk in stream: # 每个chunk包含一个'message'字段,其中'content'是本次新生成的部分文本 print(chunk'message''content', end='', flush=True) # 输出完成后换行 print()

场景3:维护多轮对话上下文

通过不断向messages列表中添加历史记录,模型就能"记住"之前的对话。

import ollama # 初始化对话历史 messages = {'role': 'system', 'content': '你是一位知识渊博的历史老师。'} while True: user_input = input("你: ") if user_input.lower() == 'exit': break # 将用户输入添加到历史中 messages.append({'role': 'user', 'content': user_input}) # 发送完整历史,获取回复 response = ollama.chat(model='qwen3:4b', messages=messages) assistant_reply = response'message''content' # 将助手的回复也添加到历史中,以便下一轮使用 messages.append({'role': 'assistant', 'content': assistant_reply}) print(f"助手: {assistant_reply}")

3. JavaScript/Node.js 库 (ollama)

详细说明 :这是Ollama官方的JavaScript SDK,支持在浏览器和Node.js环境中使用。它基于现代的async/await异步编程模型,非常适合构建响应式的Web应用。

主要特点

  • 全栈支持:一套API既可以运行在前端浏览器中,也可以运行在后端Node.js服务器上。

  • Promise风格 :完美融入现代JavaScript异步流程,可以使用async/await.then()

  • 类型安全:如果使用TypeScript,可以获得完整的类型提示,减少错误。

  • 易于集成:可以很方便地与React、Vue、Next.js等前端框架结合。

详细使用示例

第一步:安装库

在项目目录下初始化npm并安装:

npm init -y npm install ollama

第二步:编写Node.js代码

场景1:基本调用(使用/api/chat

创建一个index.js文件:

jimport { Ollama } from 'ollama'; // 创建Ollama客户端实例,可指定主机地址 const ollama = new Ollama({ host: 'http://localhost:11434' }); async function basicChat() { try { // 发送聊天请求 const response = await ollama.chat({ model: 'gemma3:4b', messages: { role: 'user', content: 'Node.js和Python有什么区别?' } }); // 打印助手的回复 console.log(response.message.content); } catch (error) { console.error('调用失败:', error); } } basicChat();

使用命令运行:node index.js

场景2:实现流式响应(实时显示)

通过监听async迭代器,可以逐块接收并处理响应,例如在Web页面中动态更新UI。

javascript

复制

下载

复制代码
import { Ollama } from 'ollama';

const ollama = new Ollama({ host: 'http://localhost:11434' });

async function streamChat() {
    // 发起流式请求
    const streamResponse = await ollama.chat({
        model: 'qwen3:4b',
        messages: [{ role: 'user', content: '用JavaScript实现一个冒泡排序。' }],
        stream: true // 启用流式
    });

    // 遍历异步迭代器,获取每个数据块
    for await (const part of streamResponse) {
        // part.message.content 包含本次新生成的文本片段
        process.stdout.write(part.message.content); // 在控制台连续输出
    }
    console.log(); // 最终换行
}

streamChat();

场景3:在浏览器中使用

首先,通过<script type="importmap">或打包工具(如Vite)引入库。然后,在浏览器JavaScript中,用法与Node.js几乎完全相同,这使得前后端可以共享逻辑。

html

<!DOCTYPE html> <html> <head> <title>Ollama 浏览器聊天</title> </head> <body> <button id="chatBtn">问AI</button> <div id="output"></div> <script type="importmap"> { "imports": { "ollama": "https://cdn.jsdelivr.net/npm/ollama@0.5.0/+esm" } } </script> <script type="module"> import { Ollama } from 'ollama'; const ollama = new Ollama({ host: 'http://localhost:11434' }); document.getElementById('chatBtn').onclick = async () => { const output = document.getElementById('output'); output.innerText = '思考中...'; const response = await ollama.chat({ model: 'gemma3:4b', messages: { role: 'user', content: '推荐一本适合初学者的编程书。' } }); output.innerText = response.message.content; }; </script> </body> </html>

总结与选择建议

方式 核心操作 代码量 适合场景 优点 缺点
cURL 手动拼接JSON,在终端执行 最少(单条命令) 测试API、调试、快速验证 直接、无需环境配置 难以处理复杂逻辑,不可用于生产
Python库 调用ollama.chat()函数 很少 后端服务、数据分析、脚本 高级封装、生态丰富、易于调试 仅限Python环境
JavaScript库 调用ollama.chat()异步方法 很少 Web前端、Node.js后端、全栈应用 异步高性能、前后端通用 需要对JavaScript异步编程有基本了解

下面用C#实现聊天程序

下面提供一个功能完整的C#控制台程序。它基于你提供的文章代码进行了重构和增强,支持从多个本地模型中自由选择,并优化了响应处理。

主要改进点:

  • 可用的模型列表 :动态获取并让你选择qwen3:4bgemma3:4b(如果本地已拉取)。

  • 正确的API调用 :使用/api/chat端点(更适合多轮对话),并正确处理流式(stream: false)响应。

  • 清晰的实体类 :定义了与API响应匹配的ChatResponse类,方便解析。

  • 友好的交互界面 :支持连续对话(上下文未维护,仅作演示)和输入exit退出。

1. 创建实体类 (用于解析JSON响应)

在项目中新建一个ChatResponse.cs文件:

using System; using System.Text.Json.Serialization; namespace OllamaChat { // 对应 /api/chat 端点的响应格式 public class ChatResponse { JsonPropertyName("model") public string? Model { get; set; } JsonPropertyName("created_at") public DateTime CreatedAt { get; set; } JsonPropertyName("message") public Message? Message { get; set; } JsonPropertyName("done") public bool Done { get; set; } } public class Message { JsonPropertyName("role") public string? Role { get; set; } JsonPropertyName("content") public string? Content { get; set; } } }

2. 主程序代码 (Program.cs)

将以下代码替换到你的Program.cs中:

复制代码
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace OllamaChat
{
    class Program
    {
        // 定义可用的模型列表(请确保这些模型已通过 ollama pull 下载到本地)
        private static readonly List<string> AvailableModels = new List<string>
        {
            "qwen3:4b",
            "gemma3:4b"
            // 如果你有其他模型,可以在这里添加,例如 "llama3.2:3b"
        };

        static async Task Main(string[] args)
        {
            Console.OutputEncoding = Encoding.UTF8; // 支持中文显示

            // 1. 检查Ollama服务是否可用
            using var client = new HttpClient();
            client.Timeout = TimeSpan.FromSeconds(120); // 推理可能需要较长时间
            try
            {
                var healthCheck = await client.GetAsync("http://localhost:11434/api/tags");
                if (!healthCheck.IsSuccessStatusCode)
                {
                    Console.WriteLine("错误:无法连接到Ollama服务。请确保Ollama正在运行。");
                    Console.WriteLine("请启动Ollama应用程序或运行 'ollama serve' 命令。");
                    return;
                }
            }
            catch
            {
                Console.WriteLine("错误:无法连接到Ollama服务。请确保Ollama正在运行。");
                return;
            }

            // 2. 选择模型
            Console.WriteLine("=== Ollama C# 聊天程序 ===");
            Console.WriteLine("检测到的本地模型:");
            for (int i = 0; i < AvailableModels.Count; i++)
            {
                Console.WriteLine($"{i + 1}. {AvailableModels[i]}");
            }
            Console.Write("请选择模型的序号 (默认为1): ");
            string? inputChoice = Console.ReadLine();
            if (!int.TryParse(inputChoice, out int choice) || choice < 1 || choice > AvailableModels.Count)
            {
                choice = 1; // 默认选择第一个
            }
            string selectedModel = AvailableModels[choice - 1];
            Console.WriteLine($"你选择了模型: {selectedModel}\n");

            // 3. 开始聊天循环
            Console.WriteLine("输入你的问题,输入 'exit' 退出程序。\n");
            while (true)
            {
                Console.Write("你: ");
                string? userInput = Console.ReadLine();
                if (string.IsNullOrEmpty(userInput)) continue;
                if (userInput.Trim().ToLower() == "exit") break;

                // 调用Ollama API
                await SendChatRequestAsync(client, selectedModel, userInput);
                Console.WriteLine(); // 输出后换行
            }
        }

        private static async Task SendChatRequestAsync(HttpClient client, string model, string prompt)
        {
            try
            {
                // 构造请求体,使用 /api/chat 端点
                var requestBody = new
                {
                    model = model,
                    messages = new[]
                    {
                        new { role = "user", content = prompt }
                    },
                    stream = false // 设置为false以一次性获取完整响应
                };

                string json = JsonSerializer.Serialize(requestBody);
                var content = new StringContent(json, Encoding.UTF8, "application/json");

                // 发送POST请求
                HttpResponseMessage response = await client.PostAsync(
                    "http://localhost:11434/api/chat",
                    content
                );

                if (response.IsSuccessStatusCode)
                {
                    // 解析JSON响应
                    string responseJson = await response.Content.ReadAsStringAsync();
                    var chatResponse = JsonSerializer.Deserialize<ChatResponse>(responseJson);

                    if (chatResponse?.Message?.Content != null)
                    {
                        Console.WriteLine($"\n{model}: {chatResponse.Message.Content}");
                    }
                    else
                    {
                        Console.WriteLine("\n助手: (收到空响应)");
                    }
                }
                else
                {
                    string errorContent = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"\n请求失败 (HTTP {response.StatusCode}): {errorContent}");
                }
            }
            catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
            {
                Console.WriteLine("\n错误: 请求超时,模型推理时间过长。");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"\n发生异常: {ex.Message}");
            }
        }
    }
}
3. 运行与使用步骤
  1. 确保环境

    • 已在本地安装Ollama。

    • 已通过命令行拉取了模型,例如:

      复制代码
      ollama pull qwen3:4b
      ollama pull gemma3:4b
    • Ollama服务正在运行(可以在系统托盘中看到羊驼图标或通过ollama serve启动)。

  2. 创建项目:在Visual Studio或VS Code中新建一个控制台应用(.NET Core/.NET 5+)。

  3. 添加代码:将上述两个代码文件添加到项目中。

  4. 运行 :按F5dotnet run启动程序。根据提示选择模型,然后开始聊天。

这个程序为你提供了一个稳定、可直接运行的起点。你可以基于它进一步扩展,比如添加多轮对话的上下文记忆功能、实现流式输出等。希望这能帮助你顺利开始Ollama的C#开发!如果有其他问题,随时可以提出。

C#程序改进版,增加了流式应当,不用一次性输出,更加人性化。

cs 复制代码
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace OllamaChat
{
    class Program
    {
        private static readonly List<string> AvailableModels = new List<string>
        {
            "qwen3:4b",
            "gemma3:4b"
        };

        private static bool _showThinking = true;

        static async Task Main(string[] args)
        {
            Console.OutputEncoding = Encoding.UTF8;

            // 检查服务
            using var client = new HttpClient();
            client.Timeout = TimeSpan.FromSeconds(180);
            try
            {
                var healthCheck = await client.GetAsync("http://localhost:11434/api/tags");
                if (!healthCheck.IsSuccessStatusCode)
                {
                    Console.WriteLine("错误:无法连接到Ollama服务。");
                    return;
                }
                Console.WriteLine("✅ Ollama 服务连接正常\n");
            }
            catch
            {
                Console.WriteLine("错误:无法连接到Ollama服务。");
                return;
            }

            // 选择模型
            Console.WriteLine("=== Ollama C# 流式聊天程序 ===");
            for (int i = 0; i < AvailableModels.Count; i++)
            {
                Console.WriteLine($"{i + 1}. {AvailableModels[i]}");
            }
            Console.Write("请选择模型的序号 (默认为1): ");
            string? inputChoice = Console.ReadLine();
            if (!int.TryParse(inputChoice, out int choice) || choice < 1 || choice > AvailableModels.Count)
            {
                choice = 1;
            }
            string selectedModel = AvailableModels[choice - 1];
            Console.WriteLine($"你选择了模型: {selectedModel}\n");

            Console.Write("是否显示思考过程?(y/n, 默认为y): ");
            string? showInput = Console.ReadLine();
            _showThinking = string.IsNullOrEmpty(showInput) || showInput.ToLower() != "n";

            Console.WriteLine("\n输入你的问题,输入 'exit' 退出程序。\n");
            while (true)
            {
                Console.Write("你: ");
                string? userInput = Console.ReadLine();
                if (string.IsNullOrEmpty(userInput)) continue;
                if (userInput.Trim().ToLower() == "exit") break;

                await StreamChatAsync(client, selectedModel, userInput);
                Console.WriteLine("\n");
            }
        }

        private static async Task StreamChatAsync(HttpClient client, string model, string prompt)
        {
            try
            {
                var requestBody = new
                {
                    model = model,
                    prompt = prompt,
                    stream = true
                };

                string json = JsonSerializer.Serialize(requestBody);
                var content = new StringContent(json, Encoding.UTF8, "application/json");

                using var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:11434/api/generate")
                {
                    Content = content
                };

                using var response = await client.SendAsync(
                    request,
                    HttpCompletionOption.ResponseHeadersRead
                );

                if (!response.IsSuccessStatusCode)
                {
                    string errorContent = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"\n请求失败 (HTTP {response.StatusCode}): {errorContent}");
                    return;
                }

                using var stream = await response.Content.ReadAsStreamAsync();
                using var reader = new System.IO.StreamReader(stream, Encoding.UTF8);

                string? line;
                bool hasContent = false;
                int chunkCount = 0;
                int tokenCount = 0;

                // ✅ 状态管理
                bool isThinkingPhase = true;      // 当前是否在思考阶段
                bool thinkingStarted = false;
                bool responseStarted = false;

                // ✅ 先显示模型名称
                Console.Write($"{model}: ");

                while ((line = await reader.ReadLineAsync()) != null)
                {
                    if (string.IsNullOrEmpty(line)) continue;

                    try
                    {
                        using var doc = JsonDocument.Parse(line);
                        var root = doc.RootElement;

                        // ✅ 处理思考过程(先显示)
                        if (root.TryGetProperty("thinking", out var thinkingElement))
                        {
                            string thinkingChunk = thinkingElement.GetString() ?? "";
                            if (!string.IsNullOrEmpty(thinkingChunk) && _showThinking)
                            {
                                // 第一次出现思考时,显示 "思考中...\n"
                                if (!thinkingStarted)
                                {
                                    Console.ForegroundColor = ConsoleColor.DarkGray;
                                    Console.Write("思考中...\n");
                                    thinkingStarted = true;
                                }
                                // ✅ 逐字输出思考内容
                                Console.Write(thinkingChunk);
                            }
                        }

                        // ✅ 处理回答内容(在思考之后)
                        if (root.TryGetProperty("response", out var responseElement))
                        {
                            string chunk = responseElement.GetString() ?? "";
                            if (!string.IsNullOrEmpty(chunk))
                            {
                                // 如果还在思考阶段,切换到回答阶段
                                if (isThinkingPhase && thinkingStarted && _showThinking)
                                {
                                    Console.ResetColor();
                                    Console.Write("\n\n"); // 思考完成后换行
                                    isThinkingPhase = false;
                                }
                                else if (isThinkingPhase && !thinkingStarted)
                                {
                                    // 没有思考过程,直接输出回答
                                    isThinkingPhase = false;
                                }

                                // ✅ 逐字输出回答
                                Console.Write(chunk);
                                hasContent = true;
                                chunkCount++;
                            }
                        }

                        // ✅ 检查是否完成
                        if (root.TryGetProperty("done", out var doneElement) && doneElement.GetBoolean())
                        {
                            if (root.TryGetProperty("eval_count", out var evalCount))
                            {
                                tokenCount = evalCount.GetInt32();
                            }

                            // ✅ 如果思考过程结束时没有换行,补充换行
                            if (thinkingStarted && _showThinking && !isThinkingPhase)
                            {
                                // 已经换行了
                            }
                            else if (thinkingStarted && _showThinking && isThinkingPhase)
                            {
                                // 思考结束但没有回答(异常情况)
                                Console.ResetColor();
                            }

                            Console.WriteLine($"\n\n[生成完成,共 {tokenCount} 个 token,{chunkCount} 个数据块]");
                            break;
                        }
                    }
                    catch (JsonException)
                    {
                        // 忽略无法解析的行
                    }
                }

                if (!hasContent)
                {
                    Console.WriteLine("(收到空响应)");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"\n发生异常: {ex.Message}");
            }
        }
    }
}
相关推荐
青 春 记 忆14 小时前
Dify Docker Compose 通用无损升级指南:从备份、双版本预演到切换与回滚
运维·人工智能·python·docker·容器
GlueNa2SiO314 小时前
03-Flask模板引擎Jinja2详解
笔记·python·flask
临沂GEO16 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
大模型码小白16 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
荷蒲18 小时前
【小白量化Qbuddy】利用AI学习中文Python
人工智能·python·学习
数据狐(Datafox)19 小时前
京东商品列表API技术解析与落地应用(含标准 JSON 示例)
java·大数据·前端·人工智能·python·数据分析·json
量化吞吐机19 小时前
2026年下半年手工交易规则走向量化表达,产品该先接住哪一步
人工智能·python
Metaphor69220 小时前
使用 Python 读取和删除 Excel 文件属性
python·excel
迷迭香yy21 小时前
Python构建转融券多空识别系统从接口到因子的全流程 IG50免费开源股票数据API接口
开发语言·python·开源
月光船幽幽21 小时前
真实即粗糙,光滑是伪造
人工智能·python