Разработка с 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

Потоковая передача ответов

При создании чат-приложений с Claude возникает серьёзная проблема с пользовательским опытом: генерация ответов может занимать 10-30 секунд, оставляя пользователей смотреть на индикатор загрузки. Решение — потоковая передача ответов, которая позволяет пользователям видеть текст, появляющийся по частям по мере его генерации Claude, создавая гораздо более отзывчивое ощущение.

Проблема со стандартными ответами

В типичной настройке чата ваш сервер отправляет сообщение пользователя Claude и ждёт полного ответа, прежде чем отправить что-либо обратно клиенту. Это создаёт неудобную задержку, при которой пользователи не получают обратной связи о том, что что-то происходит.

Как работает потоковая передача

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

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

Понимание событий потока

Когда вы включаете потоковую передачу, Claude отправляет несколько типов событий:

События content_block_delta содержат фактически сгенерированный текст, который вы захотите отобразить пользователям.

Базовая реализация потоковой передачи

Чтобы включить потоковую передачу, добавьте stream=True в ваш вызов messages.create:

messages = []
add_user_message(messages, "Write a 1 sentence description of a fake database")
stream = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    stream=True
)
for event in stream:
    print(event)

Упрощённая потоковая передача текста

Вместо ручного анализа событий вы можете использовать упрощённый интерфейс потоковой передачи SDK, который извлекает только текстовое содержимое:

with client.messages.stream(
    model=model,
    max_tokens=1000,
    messages=messages
) as stream:
    for text in stream.text_stream:
        print(text, end="")

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

Получение полного сообщения

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

with client.messages.stream(
    model=model,
    max_tokens=1000,
    messages=messages
) as stream:
    for text in stream.text_stream:
        # Send each chunk to your client
        pass
    # Get the complete message for database storage
    final_message = stream.get_final_message()

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

Структурированные данные

Когда вам нужно, чтобы Claude генерировал структурированные данные, такие как JSON, код Python или маркированные списки, вы часто сталкиваетесь с распространённой проблемой: Claude стремится быть полезным и добавлять пояснительный текст вокруг вашего контента. Хотя это обычно хорошо, иногда вам нужны только необработанные данные без чего-либо ещё.

Рассмотрим создание веб-приложения, которое генерирует правила AWS EventBridge. Пользователи вводят описание, нажимают «сгенерировать» и ожидают увидеть чистый JSON, который они могут немедленно скопировать и использовать. Если Claude возвращает JSON, обёрнутый в блоки кода markdown с пояснительным текстом, пользователи не могут просто скопировать весь ответ — им приходится вручную выбирать только часть JSON.

Проблема с ответами по умолчанию

По умолчанию, когда вы просите Claude сгенерировать JSON, вы можете получить что-то вроде этого:

```json
{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": {
    "state": ["running"]
  }
}
```

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

JSON корректен, но он обёрнут в форматирование markdown и включает пояснительный текст. Для веб-приложения, где пользователям нужно скопировать необработанный JSON, это создаёт затруднения в пользовательском опыте.

Решение: Предзаполнение сообщения ассистента + Стоп-последовательности

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

messages = []
add_user_message(messages, "Generate a very short event bridge rule as json")
add_assistant_message(messages, "```json")
text = chat(messages, stop_sequences=["```"])

Эта техника работает следующим образом:

Результатом является чистый JSON без дополнительного форматирования:

{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": {
    "state": ["running"]
  }
}

Обработка ответа

Вы можете заметить несколько лишних символов новой строки в ответе. Их легко обработать:

text.strip()

Эта техника не ограничивается генерацией JSON. Используйте её всякий раз, когда вам нужны структурированные данные без комментариев:

Ключ в том, чтобы определить, во что Claude естественным образом хочет обернуть ваш контент, а затем использовать это в качестве предзаполнения и стоп-последовательности. Для кода это обычно блоки кода markdown. Для списков это могут быть другие маркеры форматирования.

Этот подход даёт вам точный контроль над форматом вывода Claude, значительно упрощая интеграцию сгенерированного ИИ контента в приложения, где чистые, структурированные данные имеют решающее значение.

Оценка промптов

При работе с Claude написание хорошего промпта — это только начало. Для создания надёжных AI-приложений необходимо понимать две критически важные концепции: промпт-инжиниринг и оценка промптов. Промпт-инжиниринг предоставляет методы для написания лучших промптов, в то время как оценка промптов помогает измерить, насколько хорошо эти промпты на самом деле работают.

Промпт-инжиниринг против оценки промптов

Промпт-инжиниринг — это ваш набор инструментов для создания эффективных промптов. Он включает такие методы, как:

Эти методы помогают Claude точно понять, что вы запрашиваете и как вы хотите, чтобы он ответил.

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

Три пути после написания промпта

После того как вы составили промпт, обычно перед вами стоят три варианта дальнейших действий:

Почему большинство инженеров попадают в ловушки тестирования

Варианты 1 и 2 — это распространённые ловушки, в которые попадают все инженеры, включая меня. Естественно написать промпт для серьёзного приложения и не протестировать его достаточно тщательно. Мы склонны недооценивать, сколько крайних случаев встретят реальные пользователи.

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

Подход "сначала оценка"

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

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

Типичный рабочий процесс оценки

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

Шаг 1: Составьте промпт

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

prompt = f"""
Please answer the user's question:
{question}
"""

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

Шаг 2: Создайте набор данных для оценки

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

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

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

Шаг 3: Пропустите через Claude

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

Например, первый вопрос становится:

Пожалуйста, ответьте на вопрос пользователя:

What's 2+2?

Claude может ответить "2 + 2 = 4" на математический вопрос, предоставить инструкции по приготовлению овсянки на второй вопрос и указать расстояние до Луны на третий.

Шаг 4: Пропустите через оценщика

Оценщик оценивает качество ответов Claude, анализируя как исходный вопрос, так и ответ Claude. Этот шаг обеспечивает объективную оценку, обычно по шкале от 1 до 10, где 10 представляет собой идеальный ответ, а более низкие баллы указывают на возможность улучшения.

В нашем примере оценщик может присвоить:

Средний балл по всем вопросам даёт вам объективное измерение: (10 + 4 + 9) ÷ 3 = 7.66

Шаг 5: Измените промпт и повторите

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

Например, вы можете добавить больше указаний в свой промпт:

prompt = f"""
Please answer the user's question:
{question}
Answer the question with ample detail
"""

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

Ключевое преимущество этого рабочего процесса — получение объективных измерений производительности промптов. Вы можете:

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

Генерация тестовых наборов данных

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

Постановка цели

Наш промпт должен помогать пользователям в написании трех конкретных типов вывода для сценариев использования AWS:

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

Вот наш начальный промпт (версия 1):

prompt = f"""
Please provide a solution to the following task:
{task}
"""

Создание набора данных для оценки

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

Наш набор данных будет представлять собой массив объектов JSON, где каждый объект содержит свойство "task", описывающее, что мы хотим, чтобы Claude выполнил. Мы можем создать этот набор данных вручную или сгенерировать его автоматически с помощью Claude.

Поскольку мы генерируем тестовые данные, это прекрасная возможность использовать более быструю модель, такую как Haiku, вместо полной модели Claude.

Генерация тестовых данных с помощью кода

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

def add_user_message(messages, text):
    user_message = {"role": "user", "content": text}
    messages.append(user_message)

def add_assistant_message(messages, text):
    assistant_message = {"role": "assistant", "content": text}
    messages.append(assistant_message)

def chat(messages, system=None, temperature=1.0, stop_sequences=[]):
    params = {
        "model": model,
        "max_tokens": 1000,
        "messages": messages,
        "temperature": temperature
    }
    if system:
        params["system"] = system
    if stop_sequences:
        params["stop_sequences"] = stop_sequences
    response = client.messages.create(**params)
    return response.content[0].text

Теперь мы создадим нашу функцию генерации набора данных:

def generate_dataset():
    prompt = """
Generate an evaluation dataset for a prompt evaluation. The dataset will be used to evaluate prompts that generate Python, JSON, or Regex specifically for AWS-related tasks. Generate an array of JSON objects, each representing task that requires Python, JSON, or a Regex to complete.

Example output:
```json
[
  {
    "task": "Описание задачи",
  },
  ...additional
]
```

* Focus on tasks that can be solved by writing a single Python function, a single JSON object, or a single regex
* Focus on tasks that do not require writing much code
Please generate 3 objects.
    """

Чтобы правильно разобрать ответ JSON, мы будем использовать предварительное заполнение и стоп-последовательности:

    messages = []
    add_user_message(messages, prompt)
    add_assistant_message(messages, "```json")
    text = chat(messages, stop_sequences=["```"])
    return json.loads(text)

Тестирование генерации набора данных

Давайте запустим нашу функцию и посмотрим, какие тестовые случаи мы получим:

dataset = generate_dataset()
print(dataset)

Это должно вернуть три различных тестовых случая, охватывающих наши целевые выводы — функции Python, конфигурации JSON и регулярные выражения для задач, специфичных для AWS.

Сохранение набора данных

Как только у нас будет наш набор данных, мы сохраним его в файл, чтобы мы могли легко загрузить его позже во время оценки:

with open('dataset.json', 'w') as f:
    json.dump(dataset, f, indent=2)

Файл в той же директории, что и ваш ноутбук, содержащий ваш список задач, готовых к оценке промптов.

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

Запуск оценки

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

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

Создание основных функций

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

Функция run_prompt

Эта функция принимает тестовый случай и объединяет его с нашим шаблоном промпта:

def run_prompt(test_case):
    """Объединяет промпт и входные данные тестового случая, затем возвращает результат"""
    prompt = f"""
Please solve the following task:
{test_case["task"]}
"""
    messages = []
    add_user_message(messages, prompt)
    output = chat(messages)
    return output

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

Функция run_test_case

Эта функция организует выполнение одного тестового случая и оценку результата:

def run_test_case(test_case):
    """Вызывает run_prompt, затем оценивает результат"""
    output = run_prompt(test_case)
    # TODO - Grading
    score = 10
    return {
        "output": output,
        "test_case": test_case,
        "score": score
    }

Пока мы используем жестко заданную оценку 10. Логика оценки — это то, чему мы уделим значительное время в следующих разделах, но этот заполнитель позволяет нам протестировать общий конвейер.

Функция run_eval

Эта функция координирует весь процесс оценки:

def run_eval(dataset):
    """Загружает набор данных и вызывает run_test_case для каждого случая"""
    results = []
    for test_case in dataset:
        result = run_test_case(test_case)
        results.append(result)
    return results

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

Запуск оценки

Чтобы выполнить наш конвейер оценки, мы загружаем наш набор данных и пропускаем его через наши функции:

with open("dataset.json", "r") as f:
    dataset = json.load(f)
results = run_eval(dataset)

При первом запуске ожидайте, что это займет некоторое время — даже с Claude Haiku, обработка полного набора данных может занять около 30 секунд. Методы оптимизации мы рассмотрим позже.

Изучение результатов

Оценка возвращает структурированный массив JSON, где каждый объект представляет результат одного тестового случая:

print(json.dumps(results, indent=2))

Каждый результат содержит три ключевых элемента информации:

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

Что мы достигли

На данный момент мы успешно построили основной конвейер оценки. Мы можем взять наш набор данных, обработать его через Claude и собрать структурированные результаты. Основной недостающий элемент — это система оценки — эта жестко заданная оценка 10 должна быть заменена фактической логикой оценки.

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

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