用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}");
            }
        }
    }
}
相关推荐
Ulyanov1 小时前
Python实现6-DOF刚体仿真器(下)——环境扰动与控制闭环
开发语言·python·算法·系统仿真·雷达电子对抗·导引头
小小的木头人1 小时前
Python 批量解析 Excel 经纬度,调用高德地图 API 获取中文地址
开发语言·python·excel
金銀銅鐵2 小时前
[Python] 为 Vole 机器语言实现图形化界面
python·程序员
小林ixn2 小时前
Python基础全梳理:从注释到函数,这些细节你都掌握了吗?
python·编程语言
今天AI了吗3 小时前
Hermes Agent 搭建全流程:从本机试跑到可持续运行的个人 AI Agent
java·人工智能·python·学习·embedding
为啥全要学4 小时前
自注意力中随着根号dk的增大,qk点积的方差为什么也会增大
python·深度学习
来两个炸鸡腿4 小时前
【Datawhale2607】llm-algo-leetcode task02 基础算子
人工智能·python·大模型
庵中十三居士5 小时前
【纯AI无人工修改】AI Agent从0到1实战:50行Python手写核心循环,一次看懂所有Agent框架的底层逻辑
开发语言·人工智能·python
有同事要进步5 小时前
python常见问题--2
开发语言·python