ЧАСТЬ III: НОВАЯ ПАРАДИГМА

Работа с LLM

Глава 16. Работа с LLM

"LLM — это не просто инструмент. Это новый вид вычислительного ресурса."


16.1. LLM как вычислительный примитив

От CPU к LLM

Эволюция вычислительных примитивов:

1940s: CPU — арифметические операции
1990s: GPU — параллельные вычисления
2000s: Cloud — удалённые вычисления
2020s: LLM — языковые вычисления

LLM — это новый примитив:

  • Вход: текст (промпт)
  • Выход: текст (completion)
  • Операция: понимание + генерация

Пример операции:

# CPU операция
result = 2 + 2  # → 4

# LLM операция
result = llm("What is 2 + 2?")  # → "The answer is 4."

Разница: LLM работает с смыслом, а не с битами.


16.2. OpenAI API — практика

Установка и настройка

pip install openai
import openai
import os

# API ключ (получить на platform.openai.com)
openai.api_key = os.getenv("OPENAI_API_KEY")

Безопасность:

  • ❌ Никогда не пишите API ключ в коде!
  • ✅ Используйте переменные окружения
# .env файл
OPENAI_API_KEY=sk-...your-key-here...

# .gitignore
.env

Базовый запрос

from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain quantum computing in simple terms."}
    ]
)

print(response.choices[0].message.content)

Структура:

  • model — какая модель (gpt-4, gpt-3.5-turbo, etc.)
  • messages — список сообщений
    • system — инструкции для AI
    • user — ваш вопрос
    • assistant — ответ AI (для продолжения диалога)

Диалоговый контекст

messages = [
    {"role": "system", "content": "You are a Python tutor."}
]

def chat(user_message):
    messages.append({"role": "user", "content": user_message})

    response = client.chat.completions.create(
        model="gpt-4",
        messages=messages
    )

    assistant_message = response.choices[0].message.content
    messages.append({"role": "assistant", "content": assistant_message})

    return assistant_message

# Использование
print(chat("What is a list in Python?"))
print(chat("How do I add an element to it?"))  # Контекст сохранён!
print(chat("Show me an example"))

Контекст сохраняется через массив messages.

Параметры генерации

response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Write a poem about code"}],
    temperature=0.7,    # Креативность (0-2)
    max_tokens=150,     # Максимум токенов ответа
    top_p=1.0,          # Nucleus sampling
    frequency_penalty=0.0,  # Штраф за повторения
    presence_penalty=0.0    # Штраф за повтор тем
)

Ключевые параметры:

temperature (0-2):

  • 0 — детерминированный, точный
  • 0.7 — сбалансированный (default)
  • 1.5+ — очень креативный, непредсказуемый
# Для кода: низкая температура
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Write a function to sort a list"}],
    temperature=0.2  # Предсказуемо
)

# Для креатива: высокая температура
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Write a creative story"}],
    temperature=1.2  # Креативно
)

max_tokens:

  • Ограничивает длину ответа
  • 1 token ≈ 0.75 слова (для английского)
  • 1 token ≈ 0.5-1 слово (для русского)

16.3. Anthropic Claude API

Claude — альтернатива от Anthropic (создатели этой модели).

Установка

pip install anthropic

Использование

import anthropic
import os

client = anthropic.Anthropic(
    api_key=os.getenv("ANTHROPIC_API_KEY")
)

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Explain recursion with an example"}
    ]
)

print(response.content[0].text)

Отличия от OpenAI:

  • Синтаксис немного отличается
  • Claude лучше для длинных контекстов (200k tokens)
  • Claude более "осторожный" в ответах

Сравнение моделей

МодельКонтекстСильные стороны
GPT-4128kУниверсальный, креативный
GPT-3.5-turbo16kБыстрый, дешёвый
Claude 3.5 Sonnet200kДлинные тексты, анализ кода
Claude 3 Opus200kСамый мощный Anthropic

Выбор модели:

  • Быстрый ответ, простая задача → GPT-3.5-turbo
  • Сложная задача, качество → GPT-4
  • Анализ большого кода → Claude 3.5 Sonnet

16.4. Streaming — потоковый вывод

Зачем нужен streaming

Без streaming:

response = client.chat.completions.create(...)
print(response.choices[0].message.content)  # Ждём весь ответ

Пользователь ждёт 5-10 секунд молчания, потом получает весь текст сразу.

Со streaming:

for chunk in client.chat.completions.create(stream=True, ...):
    print(chunk.choices[0].delta.content, end="")

Текст появляется постепенно, как печатает человек. Лучший UX!

Реализация

from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Write a short story"}],
    stream=True  # Включаем streaming
)

for chunk in response:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="", flush=True)

Как это работает:

  1. Модель генерирует токен за токеном
  2. Каждый токен отправляется сразу (не ждёт всего ответа)
  3. Вы выводите каждый токен по мере получения

Streaming в веб-приложении

from flask import Flask, Response, stream_with_context
from openai import OpenAI

app = Flask(__name__)
client = OpenAI()

@app.route('/chat', methods=['POST'])
def chat():
    user_message = request.json['message']

    def generate():
        response = client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": user_message}],
            stream=True
        )

        for chunk in response:
            if chunk.choices[0].delta.content:
                yield f"data: {chunk.choices[0].delta.content}\n\n"

    return Response(stream_with_context(generate()),
                    content_type='text/event-stream')

Frontend (JavaScript):

const eventSource = new EventSource('/chat');

eventSource.onmessage = (event) => {
    document.getElementById('output').innerText += event.data;
};

16.5. Function calling — вызов функций

Концепция

LLM может вызывать ваши функции для получения данных.

Пример: Пользователь спрашивает "Какая погода в Москве?"

Без function calling:

LLM: "Я не знаю текущую погоду, т.к. у меня нет доступа к интернету."

С function calling:

LLM → вызывает вашу функцию get_weather("Moscow") → получает данные → отвечает

Определение функций

functions = [
    {
        "name": "get_weather",
        "description": "Get the current weather in a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city, e.g. Moscow, London"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"]
                }
            },
            "required": ["location"]
        }
    }
]

Использование

import json

def get_weather(location, unit="celsius"):
    # Реальный API вызов (например, OpenWeatherMap)
    # Здесь для примера — mock
    return {
        "location": location,
        "temperature": 15,
        "unit": unit,
        "condition": "cloudy"
    }

# Запрос к LLM
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "What's the weather in Moscow?"}],
    functions=functions,
    function_call="auto"  # LLM сам решает, вызывать ли функцию
)

message = response.choices[0].message

# Если LLM хочет вызвать функцию
if message.function_call:
    function_name = message.function_call.name
    function_args = json.loads(message.function_call.arguments)

    # Вызываем нашу функцию
    if function_name == "get_weather":
        function_response = get_weather(**function_args)

    # Отправляем результат обратно LLM
    second_response = client.chat.completions.create(
        model="gpt-4",
        messages=[
            {"role": "user", "content": "What's the weather in Moscow?"},
            message,  # Ответ LLM с function_call
            {
                "role": "function",
                "name": function_name,
                "content": json.dumps(function_response)
            }
        ]
    )

    print(second_response.choices[0].message.content)
    # "The weather in Moscow is currently 15°C and cloudy."

Практический пример: Калькулятор

def calculator(operation, a, b):
    operations = {
        "add": lambda x, y: x + y,
        "subtract": lambda x, y: x - y,
        "multiply": lambda x, y: x * y,
        "divide": lambda x, y: x / y if y != 0 else "Error: division by zero"
    }
    return operations[operation](a, b)

functions = [{
    "name": "calculator",
    "description": "Perform arithmetic operations",
    "parameters": {
        "type": "object",
        "properties": {
            "operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
            "a": {"type": "number"},
            "b": {"type": "number"}
        },
        "required": ["operation", "a", "b"]
    }
}]

# Запрос
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "What is 123 multiplied by 456?"}],
    functions=functions,
    function_call="auto"
)

# LLM вызовет calculator("multiply", 123, 456)

16.6. Локальные модели (Ollama, llama.cpp)

Зачем локальные модели

Преимущества:

  • ✅ Бесплатно (нет API costs)
  • ✅ Приватность (данные не уходят в интернет)
  • ✅ Нет rate limits
  • ✅ Офлайн работа

Недостатки:

  • ❌ Нужны мощные GPU (минимум 8GB VRAM)
  • ❌ Качество ниже, чем GPT-4
  • ❌ Медленнее

Ollama — простейший способ

Установка:

# macOS / Linux
curl https://ollama.ai/install.sh | sh

# Или скачать с https://ollama.ai

Запуск модели:

ollama run llama2

Использование в коде:

import requests

def ollama_generate(prompt):
    response = requests.post('http://localhost:11434/api/generate', json={
        "model": "llama2",
        "prompt": prompt
    })

    full_response = ""
    for line in response.iter_lines():
        if line:
            data = json.loads(line)
            full_response += data['response']

    return full_response

# Использование
result = ollama_generate("Explain Python decorators")
print(result)

Доступные модели Ollama

# Llama 2 (Meta)
ollama pull llama2

# Code Llama (специально для кода)
ollama pull codellama

# Mistral (быстрая, качественная)
ollama pull mistral

# Phi-2 (Microsoft, маленькая)
ollama pull phi

llama.cpp для Python

pip install llama-cpp-python
from llama_cpp import Llama

# Загрузить модель
llm = Llama(model_path="./models/llama-2-7b.gguf")

# Генерация
output = llm("Explain recursion", max_tokens=200)
print(output['choices'][0]['text'])

16.7. Embeddings — векторные представления

Что такое embeddings

Embeddings — это числовой вектор, представляющий смысл текста.

text = "Hello, world!"
embedding = [0.023, -0.145, 0.389, ..., 0.012]  # 1536 чисел для OpenAI

Похожие тексты → похожие векторы:

embed("cat") ≈ embed("kitten")
embed("dog") ≈ embed("puppy")

embed("cat") ≠ embed("car")

Получение embeddings

from openai import OpenAI

client = OpenAI()

def get_embedding(text):
    response = client.embeddings.create(
        input=text,
        model="text-embedding-ada-002"
    )
    return response.data[0].embedding

# Использование
embedding = get_embedding("Python is a programming language")
print(len(embedding))  # 1536

Поиск похожих текстов

import numpy as np

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# База знаний
documents = [
    "Python is a programming language",
    "JavaScript runs in browsers",
    "Cats are cute animals",
    "Dogs are loyal pets"
]

# Получить embeddings для всех документов
doc_embeddings = [get_embedding(doc) for doc in documents]

# Запрос пользователя
query = "Tell me about pets"
query_embedding = get_embedding(query)

# Найти самый похожий документ
similarities = [cosine_similarity(query_embedding, doc_emb)
                for doc_emb in doc_embeddings]

best_match_idx = np.argmax(similarities)
print(documents[best_match_idx])
# "Dogs are loyal pets"

RAG (Retrieval-Augmented Generation)

Проблема: LLM не знает ваших данных (документы компании, личные заметки).

Решение: RAG

  1. Разбить документы на chunks
  2. Создать embeddings для каждого chunk
  3. Сохранить в векторную БД
  4. При запросе:
    • Найти релевантные chunks
    • Вставить в промпт LLM
    • LLM отвечает на основе ваших данных

Пример:

# 1. Индексация документов
documents = [
    "Компания основана в 2020 году",
    "У нас 50 сотрудников",
    "Офис находится в Москве, ул. Ленина 1"
]

doc_embeddings = [get_embedding(doc) for doc in documents]

# 2. Запрос
query = "Где находится офис?"
query_embedding = get_embedding(query)

# 3. Поиск релевантных документов
similarities = [cosine_similarity(query_embedding, doc_emb)
                for doc_emb in doc_embeddings]

top_docs = sorted(zip(documents, similarities),
                  key=lambda x: x[1], reverse=True)[:2]

context = "\n".join([doc for doc, _ in top_docs])

# 4. Промпт с контекстом
prompt = f"""
Контекст:
{context}

Вопрос: {query}

Ответь на основе контекста.
"""

response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": prompt}]
)

print(response.choices[0].message.content)
# "Офис находится в Москве, на улице Ленина, дом 1."

16.8. Практика: Создание AI-ассистента

Задача

Создать консольного ассистента, который:

  1. Поддерживает диалог
  2. Может вызывать функции (калькулятор, поиск)
  3. Сохраняет историю

Реализация

import openai
import json
import os

client = openai.OpenAI()

# История диалога
messages = [
    {"role": "system", "content": "You are a helpful assistant."}
]

# Функции
def calculator(operation, a, b):
    ops = {
        "add": a + b,
        "subtract": a - b,
        "multiply": a * b,
        "divide": a / b if b != 0 else "Error"
    }
    return ops.get(operation, "Unknown operation")

def web_search(query):
    # В реальности — вызов поискового API
    return f"Search results for: {query}"

functions = [
    {
        "name": "calculator",
        "description": "Perform arithmetic",
        "parameters": {
            "type": "object",
            "properties": {
                "operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
                "a": {"type": "number"},
                "b": {"type": "number"}
            },
            "required": ["operation", "a", "b"]
        }
    },
    {
        "name": "web_search",
        "description": "Search the web",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {"type": "string"}
            },
            "required": ["query"]
        }
    }
]

# Главный цикл
while True:
    user_input = input("You: ")
    if user_input.lower() in ["quit", "exit"]:
        break

    messages.append({"role": "user", "content": user_input})

    # Запрос к LLM
    response = client.chat.completions.create(
        model="gpt-4",
        messages=messages,
        functions=functions,
        function_call="auto"
    )

    message = response.choices[0].message

    # Если LLM вызывает функцию
    if message.function_call:
        func_name = message.function_call.name
        func_args = json.loads(message.function_call.arguments)

        # Вызываем функцию
        if func_name == "calculator":
            result = calculator(**func_args)
        elif func_name == "web_search":
            result = web_search(**func_args)

        messages.append(message)
        messages.append({
            "role": "function",
            "name": func_name,
            "content": json.dumps({"result": result})
        })

        # Повторный запрос с результатом функции
        second_response = client.chat.completions.create(
            model="gpt-4",
            messages=messages
        )

        final_message = second_response.choices[0].message.content
        messages.append({"role": "assistant", "content": final_message})
        print(f"Assistant: {final_message}")

    else:
        # Обычный ответ
        messages.append(message)
        print(f"Assistant: {message.content}")

Использование:

You: What is 25 * 17?
Assistant: The result of 25 multiplied by 17 is 425.

You: Search for Python tutorials
Assistant: Here are the search results for Python tutorials: [results]

Ключевые выводы главы

LLM как примитив: Новый вид вычислений (смысл, а не биты)

OpenAI API: Стандарт для работы с GPT моделями

Anthropic Claude: Альтернатива, сильна в длинных контекстах

Streaming: Улучшает UX, выводит токены постепенно

Function calling: LLM может вызывать ваши функции

Локальные модели: Ollama, llama.cpp — бесплатно, но слабее

Embeddings + RAG: Поиск по смыслу, LLM с вашими данными

Температура: 0-0.3 для кода, 0.7-1.2 для креатива


Следующая глава: AI-инструменты для разработки (Copilot, Cursor, Aider)