Разработка с Claude API

Запросы, потоковые ответы, tool use, структурированный вывод и стоимость.

92 уроков, первые 3 бесплатно. Полный доступ: 1490 руб.

Уроки курса

  1. Welcome to the course
  2. Overview of Claude models
  3. Accessing the API
  4. Getting an API key
  5. Making a request
  6. Multi-Turn conversations
  7. Chat exercise
  8. System prompts
  9. System prompts exercise
  10. Temperature
  11. Course satisfaction survey
  12. Response streaming
  13. Structured data
  14. Structured data exercise
  15. Quiz on accessing Claude with the API
  16. Prompt evaluation
  17. A typical eval workflow
  18. Generating test datasets
  19. Running the eval
  20. Model based grading
  21. Code based grading
  22. Exercise on prompt evals
  23. Quiz on prompt evaluation
  24. Prompt engineering
  25. Being clear and direct
  26. Being specific
  27. Structure with XML tags
  28. Providing examples
  29. Exercise on prompting
  30. Quiz on prompt engineering techniques
  31. Introducing tool use
  32. Project overview
  33. Tool functions
  34. Tool schemas
  35. Handling message blocks
  36. Sending tool results
  37. Multi-turn conversations with tools
  38. Implementing multiple turns
  39. Using multiple tools
  40. Fine grained tool calling
  41. The text edit tool
  42. The web search tool
  43. Quiz on tool use with Claude
  44. Introducing Retrieval Augmented Generation
  45. Text chunking strategies
  46. Text embeddings
  47. The full RAG flow
  48. Implementing the RAG flow
  49. BM25 lexical search
  50. A Multi-Index RAG pipeline
  51. Extended thinking
  52. Image support
  53. PDF support
  54. Citations
  55. Prompt caching
  56. Rules of prompt caching
  57. Prompt caching in action
  58. Code execution and the Files API
  59. Quiz on features of Claude
  60. Introducing MCP
  61. MCP clients
  62. Project setup
  63. Defining tools with MCP
  64. The server inspector
  65. Implementing a client
  66. Defining resources
  67. Accessing resources
  68. Defining prompts
  69. Prompts in the client
  70. MCP review
  71. Quiz on Model Context Protocol
  72. Anthropic apps
  73. Claude Code setup
  74. Claude Code in action
  75. Enhancements with MCP servers
  76. Agents and workflows
  77. Parallelization workflows
  78. Chaining workflows
  79. Routing workflows
  80. Agents and tools
  81. Environment inspection
  82. Workflows vs agents
  83. Quiz on Agents and Workflows
  84. Final Assessment
  85. Course Wrap Up
  86. What You'll Learn
  87. What You'll Learn
  88. What You'll Learn
  89. What You'll Learn
  90. What You'll Learn
  91. What You'll Learn
  92. What You'll Learn

Quiz on tool use with Claude

Введение в генерацию с дополненным извлечением (RAG)

Генерация с дополненным извлечением (RAG) — это техника, которая помогает вам работать с большими документами, слишком объемными для одного промпта. Вместо того чтобы втискивать все в один огромный промпт, RAG разбивает документы на чанки и включает только наиболее релевантные части при ответе на вопросы.

Проблема с большими документами

Представьте, что у вас есть 800-страничный финансовый документ, и вы хотите задать Claude конкретные вопросы о нем, например: "Какие факторы риска есть у этой компании?" Вам нужно каким-то образом передать соответствующую информацию из документа Claude, но существуют ограничения на объем текста, который можно включить в промпт.

Вариант 1: Включить все в промпт

Первый подход прост — извлеките весь текст из документа и поместите его в свой промпт вместе с вопросом пользователя. Этот подход имеет серьезные ограничения:

Вариант 2: Разбиение документов на чанки

RAG использует более умный подход. Сначала вы разбиваете документ на более мелкие чанки на этапе предварительной обработки. Затем, когда пользователь задает вопрос, вы находите чанки, наиболее релевантные его вопросу, и включаете в промпт только их.

Преимущества этого подхода:

Проблемы с RAG

RAG обменивает простоту на масштабируемость и эффективность. Хотя для правильной реализации требуется больше предварительной работы, он позволяет работать с коллекциями документов, которые было бы невозможно обработать с помощью простого "запихивания" в промпт.

Стратегии разбиения текста на чанки

Разбиение текста на чанки является одним из наиболее важных шагов при построении конвейера RAG. То, как вы разбиваете свои документы, напрямую влияет на качество всей вашей системы. Плохая стратегия разбиения на чанки может привести к вставке нерелевантного контекста в ваши промпты.

Разбиение на чанки по размеру

Разбиение на чанки по размеру — это самый простой подход: вы делите текст на строки одинаковой длины.

Недостатки:

Чтобы решить эти проблемы, вы можете добавить перекрытие между чанками. Это означает, что каждый чанк включает часть символов из соседних чанков, обеспечивая лучший контекст и гарантируя полноту слов и предложений.

def chunk_by_char(text, chunk_size=150, chunk_overlap=20):
    chunks = []
    start_idx = 0
    while start_idx < len(text):
        end_idx = min(start_idx + chunk_size, len(text))
        chunk_text = text[start_idx:end_idx]
        chunks.append(chunk_text)
        start_idx = (
            end_idx - chunk_overlap if end_idx < len(text) else len(text)
        )
    return chunks

Разбиение на чанки по структуре

Разбиение на чанки по структуре делит текст на основе естественной структуры документа — заголовков, абзацев и разделов. Это отлично работает, когда у вас есть хорошо отформатированные документы, такие как файлы Markdown.

Для документа Markdown вы можете разделить его по маркерам заголовков. Этот подход дает наиболее чистые и осмысленные чанки, потому что каждый из них представляет собой полный раздел. Однако он работает только тогда, когда у вас есть гарантии относительно структуры вашего документа.

Разбиение на чанки по семантике

Разбиение на чанки по семантике — это самый сложный подход. Вы делите текст на предложения, затем используете обработку естественного языка для определения степени связанности последовательных предложений. Чанки строятся из групп связанных предложений.

Этот метод вычислительно затратен, но производит наиболее релевантные чанки.

Разбиение на чанки по предложениям

Практическим компромиссом является разбиение на чанки по предложениям. Вы делите текст на отдельные предложения, затем группируете их в чанки с необязательным перекрытием:

def chunk_by_sentence(text, max_sentences_per_chunk=5, overlap_sentences=1):
    sentences = re.split(r"(?<=[.!?])\s+", text)
    chunks = []
    start_idx = 0
    while start_idx < len(sentences):
        end_idx = min(start_idx + max_sentences_per_chunk, len(sentences))
        current_chunk = sentences[start_idx:end_idx]
        chunks.append(" ".join(current_chunk))
        start_idx += max_sentences_per_chunk - overlap_sentences
        if start_idx < 0:
            start_idx = 0
    return chunks

Выбор стратегии

Разбиение на чанки по размеру с перекрытием часто является предпочтительным выбором в производстве, потому что оно простое, надежное и работает с любым типом документов.

Векторные представления текста

После разбиения документа на чанки, следующим шагом в конвейере RAG является поиск наиболее релевантных чанков для вопроса пользователя.

Семантический поиск

Наиболее распространенный подход для поиска релевантных чанков — семантический поиск. В отличие от поиска по ключевым словам, который ищет точные совпадения слов, семантический поиск использует векторные представления текста (text embeddings) для понимания значения и контекста как вопроса пользователя, так и каждого текстового чанка.

Что такое векторное представление текста?

Векторное представление текста (text embedding) — это числовое представление смысла, содержащегося в некотором тексте. Представьте это как преобразование слов и предложений в формат, с которым компьютеры могут работать математически.

Вот как работает процесс:

Понимание чисел

Каждое число в эмбеддинге по сути является "оценкой" для некоторого качества входного текста. Однако важное предостережение: мы не знаем точно, что представляет каждое число. Фактическое значение каждого измерения изучается моделью во время обучения и не может быть непосредственно интерпретировано человеком.

Использование VoyageAI для эмбеддингов

Поскольку Anthropic в настоящее время не предоставляет генерацию эмбеддингов, рекомендуемым провайдером является VoyageAI. Вам потребуется:

Сначала установите библиотеку VoyageAI:

%pip install voyageai

Затем настройте клиент и создайте функцию для генерации эмбеддингов:

from dotenv import load_dotenv
import voyageai

load_dotenv()
client = voyageai.Client()

def generate_embedding(text, model="voyage-3-large", input_type="query"):
    result = client.embed([text], model=model, input_type=input_type)
    return result.embeddings[0]

Когда вы запустите эту функцию на текстовом чанке, вы получите список чисел с плавающей запятой, представляющих эмбеддинг.

Полный рабочий процесс RAG

Давайте пошагово пройдемся по полному конвейеру RAG, где все части работают вместе для извлечения релевантной информации и генерации ответов.

Шаг 1: Разбейте исходный текст на чанки

Сначала мы берем наш исходный документ и разбиваем его на управляемые чанки.

Шаг 2: Сгенерируйте эмбеддинги

Далее мы преобразуем каждый текстовый чанк в числовые эмбеддинги с помощью модели эмбеддингов. API эмбеддингов обычно выполняет шаг нормализации, который масштабирует каждый вектор до величины 1.0.

Шаг 3: Хранение в векторной базе данных

Мы храним эти эмбеддинги в векторной базе данных — специализированной базе данных, оптимизированной для хранения, сравнения и поиска по длинным спискам чисел, таких как наши эмбеддинги.

Шаг 4: Обработка запроса пользователя

Когда пользователь задает вопрос, мы пропускаем его запрос через ту же модель эмбеддингов.

Шаг 5: Поиск похожих эмбеддингов

Мы отправляем эмбеддинг запроса пользователя в нашу векторную базу данных и просим ее найти наиболее похожие сохраненные эмбеддинги.

Косинусное сходство

Векторная база данных использует косинусное сходство для определения наиболее похожих эмбеддингов. Оно измеряет косинус угла между двумя векторами.

Ключевые моменты о косинусном сходстве:

Часто встречается "косинусное расстояние", которое рассчитывается как (1 - cosine similarity). При косинусном расстоянии:

Шаг 6: Создание итогового промпта

Наконец, мы берем вопрос пользователя и наиболее релевантный текстовый чанк, который мы нашли, объединяем их в промпт и отправляем его Claude для получения ответа.

И это полный конвейер RAG! Система успешно извлекла наиболее релевантную информацию на основе семантического сходства и предоставила ее в качестве контекста для генерации точного ответа.

Реализация потока RAG

Теперь давайте реализуем поток RAG шаг за шагом. Вот полный пример, который демонстрирует, как разбивать текст на чанки, генерировать эмбеддинги, хранить их в векторной базе данных и выполнять поиск по сходству.

Пятишаговая реализация RAG

  1. Разбить текст на чанки по разделам
  2. Сгенерировать эмбеддинги для каждого чанка
  3. Создать векторное хранилище и добавить в него каждый эмбеддинг
  4. Сгенерировать эмбеддинг для вопроса пользователя
  5. Выполнить поиск в хранилище, чтобы найти наиболее релевантные чанки

Шаг 1: Разбиение текста на чанки

Сначала мы загружаем наш документ и разбиваем его на управляемые разделы:

with open("./report.md", "r") as f:
    text = f.read()

chunks = chunk_by_section(text)

Шаг 2: Генерация эмбеддингов

Далее мы создаем эмбеддинги для всех наших чанков одновременно:

embeddings = generate_embedding(chunks)

Шаг 3: Хранение в векторной базе данных

Теперь мы создаем наше векторное хранилище и заполняем его эмбеддингами и связанным с ними текстом:

store = VectorIndex()
for embedding, chunk in zip(embeddings, chunks):
    store.add_vector(embedding, {"content": chunk})

Важно: мы храним как эмбеддинг, так и исходное текстовое содержимое. Это крайне важно, потому что при последующем поиске нам нужно будет вернуть фактический текст, а не только числовые значения эмбеддинга.

Шаг 4: Обработка пользовательских запросов

Когда пользователь задает вопрос, мы генерируем эмбеддинг для его запроса:

user_embedding = generate_embedding("What did the software engineering dept do last year?")

Шаг 5: Поиск релевантного контента

Наконец, мы ищем в нашем векторном хранилище наиболее похожие чанки:

results = store.search(user_embedding, 2)
for doc, distance in results:
    print(distance, "\n", doc["content"][0:200], "\n")

Этот поиск возвращает два наиболее релевантных чанка вместе с их оценками сходства (косинусными расстояниями).

Понимание результатов

Меньшие значения расстояния указывают на более высокое сходство. RAG по своей сути заключается в преобразовании текста в числа (эмбеддинги), эффективном хранении этих чисел, а затем использовании математического сходства для поиска релевантного контента, когда пользователи задают вопросы.

Лексический поиск BM25

При создании конвейеров RAG вы быстро обнаружите, что один только семантический поиск не всегда дает наилучшие результаты. Иногда вам нужны точные совпадения терминов, которые семантический поиск может пропустить.

Проблема только с семантическим поиском

Предположим, вы ищете конкретный идентификатор инцидента, например "INC-2023-Q4-011", в документе. Хотя семантический поиск отлично справляется с пониманием контекста и значения, он может вернуть разделы, которые семантически связаны, но на самом деле не содержат точного термина, который вы ищете.

Гибридная стратегия поиска

Решение состоит в том, чтобы выполнять как семантический, так и лексический поиск параллельно, а затем объединять результаты. Это дает вам лучшее из обоих миров:

Как работает BM25

BM25 (Best Match 25) — популярный алгоритм для лексического поиска в системах RAG. Вот как он обрабатывает поисковый запрос:

Шаг 1: Токенизация запроса

Разбейте вопрос пользователя на отдельные термины. Например, "What is INC-2023-Q4-011?" становится ["What", "is", "INC-2023-Q4-011"].

Шаг 2: Подсчет частоты терминов

Посмотрите, как часто каждый термин встречается во всех ваших документах. Общие слова, такие как "is", могут встречаться часто, в то время как специфические термины, такие как "INC-2023-Q4-011", могут встречаться только один раз.

Шаг 3: Взвешивание терминов по важности

Термины, которые встречаются реже, получают более высокие оценки важности. Слово "is" получает низкую важность, потому что оно распространено, в то время как "INC-2023-Q4-011" получает высокую важность, потому что оно редко.

Шаг 4: Поиск наилучших совпадений

Возвращает документы, которые содержат больше вхождений терминов с более высоким весом.

Реализация поиска BM25

Вот как настроить базовую систему поиска BM25:

# 1. Chunk your text by sections
chunks = chunk_by_section(text)

# 2. Create a BM25 store and add documents
store = BM25Index()
for chunk in chunks:
    store.add_document({"content": chunk})

# 3. Search the store
results = store.search("What happened with INC-2023-Q4-011?", 3)

# Print results
for doc, distance in results:
    print(distance, "\n", doc["content"][:200], "\n----\n")

Почему это работает лучше

BM25 превосходно находит точные совпадения, потому что он:

Ключевая идея заключается в том, что оба метода поиска имеют взаимодополняющие сильные стороны. Семантический поиск понимает контекст и значение, в то время как лексический поиск гарантирует, что вы не пропустите точные совпадения терминов. Объединяя их, вы создаете более надежную систему поиска, которая эффективно обрабатывает как концептуальные запросы, так и специфические поиски.