Глава 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— инструкции для AIuser— ваш вопрос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-4 | 128k | Универсальный, креативный |
| GPT-3.5-turbo | 16k | Быстрый, дешёвый |
| Claude 3.5 Sonnet | 200k | Длинные тексты, анализ кода |
| Claude 3 Opus | 200k | Самый мощный 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)
Как это работает:
- Модель генерирует токен за токеном
- Каждый токен отправляется сразу (не ждёт всего ответа)
- Вы выводите каждый токен по мере получения
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
- Разбить документы на chunks
- Создать embeddings для каждого chunk
- Сохранить в векторную БД
- При запросе:
- Найти релевантные 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-ассистента
Задача
Создать консольного ассистента, который:
- Поддерживает диалог
- Может вызывать функции (калькулятор, поиск)
- Сохраняет историю
Реализация
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)