Hướng Dẫn Sử Dụng Ollama: Streaming Phản Hồi và Gọi Công Cụ

Mark Ponomarev

Mark Ponomarev

29 tháng 5 2025

Hướng Dẫn Sử Dụng Ollama: Streaming Phản Hồi và Gọi Công Cụ

Hướng dẫn này sẽ chỉ cho bạn cách sử dụng một trong những tính năng mới mạnh mẽ của Ollama: khả năng truyền (stream) phản hồi và gọi công cụ (như hàm hoặc API) theo thời gian thực. Đây là một bước đột phá cho việc xây dựng các ứng dụng trò chuyện (chat) mang lại cảm giác sống động và có thể tương tác với thế giới xung quanh.

Những gì bạn sẽ học trong hướng dẫn này:

💡
Bạn muốn một công cụ Kiểm thử API tuyệt vời có thể tạo Tài liệu API đẹp mắt?

Bạn muốn một nền tảng tích hợp, Tất cả trong Một cho Đội ngũ Phát triển của bạn làm việc cùng nhau với năng suất tối đa?

Apidog đáp ứng mọi nhu cầu của bạn và thay thế Postman với mức giá phải chăng hơn nhiều!

button

Bắt đầu: Những gì bạn cần

Để làm theo, bạn sẽ cần một vài thứ:

Hiểu các Khái niệm chính: Truyền (Streaming) và Gọi công cụ (Tool Calls)

Hãy cùng phân tích ý nghĩa của "truyền phản hồi" (streaming responses) và "gọi công cụ" (tool calling).

Truyền phản hồi (Response Streaming) là gì?

Hãy tưởng tượng bạn đang trò chuyện với một AI. Thay vì chờ đợi nó suy nghĩ và gõ ra toàn bộ câu trả lời trước khi bạn thấy bất cứ điều gì, truyền phản hồi (streaming) có nghĩa là AI gửi phản hồi của nó cho bạn từng phần, từng từ, khi nó tạo ra. Điều này làm cho tương tác cảm thấy nhanh hơn và tự nhiên hơn nhiều, giống như một cuộc trò chuyện thực sự.

Với Ollama, khi bạn bật tính năng truyền phản hồi ("stream": true), bạn sẽ nhận được các cập nhật tăng dần này.

Gọi công cụ (Tool Calling) hoạt động như thế nào?

Gọi công cụ cho phép các mô hình AI của bạn làm được nhiều hơn là chỉ tạo văn bản. Bạn có thể định nghĩa "công cụ" – về cơ bản là các hàm (functions) hoặc API bên ngoài – mà AI có thể quyết định sử dụng để lấy thông tin hoặc thực hiện các hành động.

Ví dụ, một công cụ có thể là:

Bạn mô tả các công cụ này cho Ollama, và khi AI xác định rằng việc sử dụng một công cụ sẽ giúp trả lời truy vấn của người dùng, nó sẽ báo hiệu ý định gọi công cụ đó với các đối số cụ thể. Ứng dụng của bạn sau đó thực thi công cụ và có thể gửi kết quả trở lại cho AI để tiếp tục cuộc trò chuyện.

Tại sao lại kết hợp Truyền phản hồi (Streaming) với Gọi công cụ (Tool Calling)?

Nâng cấp lớn của Ollama là giờ đây nó có thể xử lý việc gọi công cụ trong khi truyền phản hồi. Điều này có nghĩa là ứng dụng của bạn có thể:

  1. Nhận văn bản ban đầu từ mô hình (được truyền).
  2. Đột nhiên, luồng dữ liệu có thể báo hiệu cần gọi một công cụ.
  3. Ứng dụng của bạn xử lý lệnh gọi công cụ.
  4. Trong lúc đó, mô hình thậm chí có thể truyền thêm văn bản (ví dụ: "Okay, tôi sẽ lấy thông tin thời tiết cho bạn...").
  5. Khi ứng dụng của bạn nhận được kết quả từ công cụ, bạn có thể gửi nó trở lại cho AI để tiếp tục cuộc trò chuyện, lúc này đã có thông tin từ kết quả của công cụ.

Điều này tạo ra các ứng dụng AI có khả năng và phản hồi cao.

Những mô hình nào hỗ trợ các tính năng này?

Ollama đã bật tính năng này cho một số mô hình phổ biến, bao gồm:

Cách thực hiện lệnh Gọi công cụ (Tool Call) truyền dữ liệu (Streaming) đầu tiên của bạn với cURL

cURL là một cách tuyệt vời để kiểm tra nhanh API của Ollama. Hãy thử hỏi về thời tiết ở Toronto.

Bước 1: Hình dung Công cụ của bạn

Công cụ của chúng ta sẽ là get_current_weather. Nó cần:

Bước 2: Xây dựng lệnh cURL

Mở terminal của bạn và chuẩn bị lệnh sau. Chúng ta sẽ phân tích nó:

curl <http://localhost:11434/api/chat> -d '{
  "model": "qwen3",
  "messages": [
    {
      "role": "user",
      "content": "What is the weather today in Toronto?"
    }
  ],
  "stream": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_current_weather",
        "description": "Get the current weather for a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "The location to get the weather for, e.g. San Francisco, CA"
            },
            "format": {
              "type": "string",
              "description": "The format to return the weather in, e.g. \\\\\\\\'celsius\\\\\\\\' or \\\\\\\\'fahrenheit\\\\\\\\'",
              "enum": ["celsius", "fahrenheit"]
            }
          },
          "required": ["location", "format"]
        }
      }
    }
  ]
}'

Phân tích:

Bước 3: Thực thi và Quan sát Kết quả

Nhấn Enter. Bạn sẽ thấy một loạt các đối tượng JSON xuất hiện lần lượt. Đây chính là luồng dữ liệu!

Các đoạn trích ví dụ từ luồng dữ liệu:

{
  "model": "qwen3", "created_at": "...",
  "message": { "role": "assistant", "content": "Okay, " }, "done": false
}
{
  "model": "qwen3", "created_at": "...",
  "message": { "role": "assistant", "content": "I will " }, "done": false
}
{
  "model": "qwen3", "created_at": "...",
  "message": { "role": "assistant", "content": "try to get that for you." }, "done": false
}

(Mô hình có thể xuất ra một số token "suy nghĩ" như <think>...celsius...</think> tùy thuộc vào quy trình nội bộ của nó, những token này cũng là một phần của luồng dữ liệu)

Sau đó, điều quan trọng là bạn có thể thấy điều gì đó như thế này:

{
  "model": "qwen3",
  "created_at": "2025-05-27T22:54:58.100509Z",
  "message": {
    "role": "assistant",
    "content": "", // Content might be empty when a tool call is made
    "tool_calls": [
      {
        "function": {
          "name": "get_current_weather",
          "arguments": { // The arguments the model decided on!
            "format": "celsius",
            "location": "Toronto"
          }
        }
      }
    ]
  },
  "done": false // Still not done, awaiting tool result
}

Những điều cần lưu ý:

Trong một ứng dụng thực tế, khi bạn thấy một đoạn tool_calls, mã của bạn sẽ:

  1. Tạm dừng xử lý luồng dữ liệu (hoặc xử lý không đồng bộ).
  2. Thực thi hàm/API get_current_weather thực tế với "Toronto" và "celsius".
  3. Lấy kết quả (ví dụ: "20 độ C").
  4. Gửi kết quả này trở lại cho Ollama trong một tin nhắn mới với role: "tool".
  5. Mô hình sau đó sẽ sử dụng thông tin này để tiếp tục tạo phản hồi của nó, cũng được truyền dữ liệu.

Cách truyền lệnh Gọi công cụ (Tool Calls) bằng Python

Hãy triển khai ý tưởng tương tự trong Python sử dụng thư viện chính thức của Ollama.

Bước 1: Cài đặt thư viện Ollama cho Python

Nếu bạn chưa có, hãy cài đặt hoặc nâng cấp thư viện:

pip install -U ollama

Bước 2: Định nghĩa Công cụ của bạn và lập trình bằng Python

SDK Ollama cho Python cho phép bạn truyền trực tiếp các hàm Python dưới dạng công cụ một cách thông minh. Nó kiểm tra chữ ký hàm và docstring để tạo schema cho AI.

Hãy tạo một ví dụ công cụ toán học đơn giản (đầu vào sử dụng add_two_numbers nhưng ví dụ đầu ra hiển thị subtract_two_numbers được gọi bởi mô hình. Chúng ta sẽ bám sát định nghĩa add_two_numbers được cung cấp và để mô hình quyết định làm gì dựa trên lời nhắc.)

import ollama

# Define the python function that can be used as a tool
def add_two_numbers(a: int, b: int) -> int:
  """
  Add two numbers.

  Args:
    a (int): The first number as an int.
    b (int): The second number as an int.

  Returns:
    int: The sum of the two numbers.
  """
  print(f"--- Tool 'add_two_numbers' called with a={a}, b={b} ---")
  return a + b

# --- Main conversation logic ---
messages = [{'role': 'user', 'content': 'What is three plus one?'}]
# Or, for the subtraction example in the original output:
# messages = [{'role': 'user', 'content': 'what is three minus one?'}]

print(f"User: {messages[0]['content']}")

# Make the chat request with streaming and the tool
# Note: ChatResponse type hint might be ollama.ChatResponse or similar depending on library version
response_stream = ollama.chat(
  model='qwen3', # Or another capable model
  messages=messages,
  tools=[
      { # You can also define the tool explicitly if needed, or pass the function directly
          'type': 'function',
          'function': {
              'name': 'add_two_numbers', # Must match the Python function name if you want it to be called directly by your code later
              'description': 'Add two integer numbers together.',
              'parameters': {
                  'type': 'object',
                  'properties': {
                      'a': {'type': 'integer', 'description': 'The first number'},
                      'b': {'type': 'integer', 'description': 'The second number'}
                  },
                  'required': ['a', 'b']
              }
          }
      }
      # Simpler way for Python: pass the function directly if the library supports easy schema generation from it
      # tools=[add_two_numbers] # The SDK can often create the schema from this
  ],
  stream=True
)

print("Assistant (streaming):")
full_response_content = ""
tool_call_info = None

for chunk in response_stream:
  # Print the streamed content part
  if chunk['message']['content']:
    print(chunk['message']['content'], end='', flush=True)
    full_response_content += chunk['message']['content']

  # Check for tool calls in the chunk
  if 'tool_calls' in chunk['message'] and chunk['message']['tool_calls']:
    tool_call_info = chunk['message']['tool_calls'][0] # Assuming one tool call for simplicity
    print(f"\\\\n--- Detected Tool Call: {tool_call_info['function']['name']} ---")
    break # Stop processing stream for now, handle tool call

  if chunk.get('done'):
      print("\\\\n--- Stream finished ---")
      if not tool_call_info:
          print("No tool call was made.")

# --- If a tool call was detected, handle it ---
if tool_call_info:
  tool_name = tool_call_info['function']['name']
  tool_args = tool_call_info['function']['arguments']

  print(f"Arguments for the tool: {tool_args}")

  # Here, you'd actually call your Python tool function
  if tool_name == "add_two_numbers":
    # For safety, ensure arguments are of correct type if necessary
    try:
        arg_a = int(tool_args.get('a'))
        arg_b = int(tool_args.get('b'))
        tool_result = add_two_numbers(a=arg_a, b=arg_b)
        print(f"--- Tool execution result: {tool_result} ---")

        # Now, send this result back to Ollama to continue the conversation
        messages.append({'role': 'assistant', 'content': full_response_content, 'tool_calls': [tool_call_info]})
        messages.append({
            'role': 'tool',
            'content': str(tool_result), # Result must be a string
            'tool_call_id': tool_call_info.get('id', '') # If your library/model provides a tool_call_id
        })

        print("\\\\n--- Sending tool result back to model ---")

        follow_up_response_stream = ollama.chat(
            model='qwen3',
            messages=messages,
            stream=True
            # No tools needed here unless you expect another tool call
        )

        print("Assistant (after tool call):")
        for follow_up_chunk in follow_up_response_stream:
            if follow_up_chunk['message']['content']:
                print(follow_up_chunk['message']['content'], end='', flush=True)
            if follow_up_chunk.get('done'):
                print("\\\\n--- Follow-up stream finished ---")
                break
    except ValueError:
        print("Error: Could not parse tool arguments as integers.")
    except Exception as e:
        print(f"An error occurred during tool execution or follow-up: {e}")
  else:
    print(f"Error: Unknown tool '{tool_name}' requested by the model.")

Giải thích mã Python:

  1. Import ollama.
  2. Hàm add_two_numbers: Đây là công cụ của chúng ta. Docstring và gợi ý kiểu (type hints) giúp Ollama hiểu mục đích và các tham số của nó.
  3. messages: Chúng ta bắt đầu cuộc trò chuyện với truy vấn của người dùng.
  4. ollama.chat(...):
  1. Lặp qua response_stream:
  1. Xử lý lệnh Gọi công cụ:

Luồng kết quả dự kiến:Bạn sẽ thấy câu hỏi ban đầu của người dùng, sau đó là phản hồi của trợ lý được truyền dữ liệu. Nếu nó quyết định gọi add_two_numbers (hoặc subtract_two_numbers như trong ví dụ đầu ra của tài liệu gốc nếu lời nhắc là cho phép trừ), bạn sẽ thấy thông báo "Detected Tool Call", các đối số, kết quả của hàm Python của bạn, và sau đó trợ lý tiếp tục phản hồi của nó sử dụng kết quả đó.

(Ví dụ đầu ra gốc hiển thị:

<think>
Okay, the user is asking ...
</think>

[ToolCall(function=Function(name='subtract_two_numbers', arguments={'a': 3, 'b': 1}))]

Điều này cho thấy quá trình "suy nghĩ" nội bộ của AI và sau đó là đối tượng gọi công cụ có cấu trúc mà SDK Python cung cấp.)

Cách truyền lệnh Gọi công cụ (Tool Calls) bằng JavaScript (Node.js)

Bây giờ, hãy làm tương tự với JavaScript, thường là cho backend Node.js hoặc ứng dụng web.

Bước 1: Cài đặt thư viện Ollama cho JavaScript

Trong thư mục dự án của bạn, chạy:

npm i ollama

Bước 2: Định nghĩa Schema Công cụ và lập trình bằng JavaScript

Trong JavaScript, bạn thường định nghĩa schema công cụ dưới dạng một đối tượng JSON.

import ollama from 'ollama';

// Describe the tool schema (e.g., for adding two numbers)
const addTool = {
    type: 'function',
    function: {
        name: 'addTwoNumbers',
        description: 'Add two numbers together',
        parameters: {
            type: 'object',
            required: ['a', 'b'],
            properties: {
                a: { type: 'number', description: 'The first number' },
                b: { type: 'number', description: 'The second number' }
            }
        }
    }
};

// Your actual JavaScript function that implements the tool
function executeAddTwoNumbers(a, b) {
    console.log(`--- Tool 'addTwoNumbers' called with a=${a}, b=${b} ---`);
    return a + b;
}

async function main() {
    const messages = [{ role: 'user', content: 'What is 2 plus 3?' }];
    console.log('User:', messages[0].content);

    console.log('Assistant (streaming):');
    let assistantResponseContent = "";
    let toolToCallInfo = null;

    try {
        const responseStream = await ollama.chat({
            model: 'qwen3', // Or another capable model
            messages: messages,
            tools: [addTool],
            stream: true
        });

        for await (const chunk of responseStream) {
            if (chunk.message.content) {
                process.stdout.write(chunk.message.content);
                assistantResponseContent += chunk.message.content;
            }
            if (chunk.message.tool_calls && chunk.message.tool_calls.length > 0) {
                toolToCallInfo = chunk.message.tool_calls[0]; // Assuming one tool call
                process.stdout.write(`\\\\n--- Detected Tool Call: ${toolToCallInfo.function.name} ---\\\\n`);
                break; // Stop processing stream to handle tool call
            }
            if (chunk.done) {
                process.stdout.write('\\\\n--- Stream finished ---\\\\n');
                if (!toolToCallInfo) {
                    console.log("No tool call was made.");
                }
                break;
            }
        }

        // --- If a tool call was detected, handle it ---
        if (toolToCallInfo) {
            const toolName = toolToCallInfo.function.name;
            const toolArgs = toolToCallInfo.function.arguments;

            console.log(`Arguments for the tool:`, toolArgs);

            let toolResult;
            if (toolName === 'addTwoNumbers') {
                toolResult = executeAddTwoNumbers(toolArgs.a, toolArgs.b);
                console.log(`--- Tool execution result: ${toolResult} ---`);

                // Append assistant's partial message and the tool message
                messages.push({
                    role: 'assistant',
                    content: assistantResponseContent, // Include content leading up to tool call
                    tool_calls: [toolToCallInfo]
                });
                messages.push({
                    role: 'tool',
                    content: toolResult.toString(), // Result must be a string
                    // tool_call_id: toolToCallInfo.id // If available and needed
                });

                console.log("\\\\n--- Sending tool result back to model ---");
                const followUpStream = await ollama.chat({
                    model: 'qwen3',
                    messages: messages,
                    stream: true
                });

                console.log("Assistant (after tool call):");
                for await (const followUpChunk of followUpStream) {
                    if (followUpChunk.message.content) {
                        process.stdout.write(followUpChunk.message.content);
                    }
                     if (followUpChunk.done) {
                        process.stdout.write('\\\\n--- Follow-up stream finished ---\\\\n');
                        break;
                    }
                }
            } else {
                console.error(`Error: Unknown tool '${toolName}' requested.`);
            }
        }

    } catch (error) {
        console.error('Error during Ollama chat:', error);
    }
}

main().catch(console.error);

Giải thích mã JavaScript:

  1. Import ollama.
  2. Đối tượng addTool: Đây là schema JSON mô tả công cụ của chúng ta cho Ollama.
  3. Hàm executeAddTwoNumbers: Hàm JavaScript thực tế của chúng ta cho công cụ.
  4. Hàm async main:
  1. Xử lý lệnh Gọi công cụ: Tương tự như Python, nếu toolToCallInfo được thiết lập:

Luồng kết quả dự kiến (tương tự như ví dụ cURL và Python):Bạn sẽ thấy câu hỏi của người dùng, sau đó là phản hồi của trợ lý được truyền dữ liệu. Khi nó quyết định gọi addTwoNumbers, nó sẽ in thông tin gọi công cụ, kết quả từ hàm JavaScript của bạn, và sau đó tiếp tục truyền câu trả lời của AI dựa trên kết quả đó.

Ví dụ đầu ra gốc cho JS trông như:

Question: What is 2 plus 3?
<think>
Okay, the user is asking...
</think>
Tool call: {
  function: {
    name: "addTwoNumbers",
    arguments: { a: 2, b: 3 },
  },
}

Cách Ollama xử lý phân tích cú pháp Công cụ trong khi Truyền dữ liệu

Bạn có thể tự hỏi làm thế nào Ollama quản lý việc truyền văn bản và xác định các lệnh gọi công cụ một cách mượt mà như vậy. Nó sử dụng một bộ phân tích cú pháp tăng dần mới rất thông minh.

Tại sao cách này tốt hơn?

Mẹo: Cải thiện hiệu suất với Cửa sổ ngữ cảnh (Context Window)

Đối với các tương tác phức tạp hơn, đặc biệt là với việc gọi công cụ, kích thước của "cửa sổ ngữ cảnh" (context window)

Thực hành thiết kế API trong Apidog

Khám phá cách dễ dàng hơn để xây dựng và sử dụng API