A shared index interface lets one Retriever combine search methods.VectorIndex and BM25Index both expose add_document() and search(). A Retriever class takes any number of indexes, adds each document to every index, sends each query to all of them, and merges the results.
Reciprocal rank fusion merges rankings that use different score scales. Each chunk scores Σ 1/(k + rank) across the indexes, with k usually 60 but set to 1 in the example. Vector ranks of Section 2, 7, 6 and BM25 ranks of 6, 2, 7 give Section 2 0.833, Section 6 0.75 and Section 7 0.583, so the chunk that ranked well in both comes out on top.
Hybrid retrieval fixed the vector-only misranking. For "what happened with INC-2023-Q4-011?", vector search alone put financial analysis (Section 3) second. The hybrid retriever returned Cybersecurity Incident Response (Section 10), then Software Engineering's Project Phoenix (Section 2), then Legal Developments (Section 5).
New search methods plug in without changing the Retriever. Any index that implements the same SearchIndex protocol, such as keyword, graph or domain-specific search, joins the fusion automatically. The lesson says this keeps each implementation focused and testable.
Building with the Claude API: 37-BM25 lexical search
Semantic search can miss exact-term lookups like IDs. Searching for incident "INC-2023-Q4-011" returned the cybersecurity section, which does contain it, but also a financial analysis section that never mentions it, because embeddings match on concepts rather than exact terms.
The fix is to run semantic and lexical search together, then merge. Embeddings find conceptually related text and classic text search finds exact matches. The lesson presents the two as complementary, so combining them handles both concept questions and specific lookups.
BM25 ranks documents by how rare the matched terms are. It tokenizes the query, counts how often each term appears across the documents, gives rare terms more weight (a common word like "a" gets little, the incident ID a lot), and returns the documents with the most high-weight terms.
On the ID query, BM25 put the right sections first. A BM25Index filled with section chunks ranked Software Engineering (about 0.27) and Cybersecurity Analysis (0.33) ahead of Methodology (0.94), and both of the top two contain the incident ID.
Building with the Claude API: 36-Implementing the RAG flow
The basic pipeline is five steps from chunking to search. You split report.md into sections with chunk_by_section, embed all chunks in one batch call, add each embedding to a VectorIndex, embed the user's question, and search the index for the nearest chunks.
Store each chunk's text next to its embedding. A search that returns only vectors is useless. Each vector is stored with {"content": chunk}, or at least a reference to the text, so results come back as readable text.
The test query found the right section, but only narrowly. "What did the software engineering dept do last year?" returned Section 2: Software Engineering at cosine distance 0.71, just ahead of Methodology at 0.72, where lower means closer. The lesson notes that this approach fails in some cases, which later lessons address.
Building with the Claude API: 35-The full RAG flow
Chunking, embedding and storage all happen before any query. In the lesson's toy model, every embedding has two dimensions: medical and software. A Medical Research chunk about "bug" XDR-47 embeds as [0.97, 0.34], and a Software Engineering chunk about "infection vectors" as [0.30, 0.97]. These are normalized to length 1.0 ([0.944, 0.331] and [0.295, 0.955]) and stored in a vector database, and the pipeline then waits for a user.
The query is embedded the same way, then matched by nearness. A question about what the software engineering department did embeds as [0.1, 0.89], which normalizes to [0.112, 0.993], and the database returns the software chunk as the closest.
Cosine similarity measures how close two vectors point. It runs from -1 (opposite) through 0 (unrelated) to 1 (same direction). The query scores 0.983 against the software chunk and 0.398 against the medical one. Vector databases often report cosine distance (1 − similarity) instead, where values near 0 mean similar.
The retrieved chunk and the question go into one prompt for Claude. The template wraps them in <user_question> and <report> tags under an instruction to answer the user's question about the document.
Finding relevant chunks is a search problem, usually solved by meaning. Semantic search compares embeddings of the question and of each chunk instead of matching keywords. The example question "How many bugs did engineers fix this year?" has to work around a medical chunk that uses "bug" for a pathogen.
An embedding is a list of numbers whose dimensions humans can't read. Each value runs from -1 to +1 and acts as a score for some feature of the text. Labels like "how happy" or "how formal" are only illustrations; the model learns what each dimension means during training.
Anthropic offers no embedding model, so the course uses VoyageAI. That means a separate account and API key (free to start) stored as VOYAGE_API_KEY in .env. A small generate_embedding helper calls voyageai.Client().embed with model voyage-3-large and input_type="query", and returns a list of floats.
Building with the Claude API: 33-Text chunking strategies
Bad chunking puts the wrong context in the prompt. In the lesson's example, "How many bugs did engineers fix this year?" can pull up a medical-research chunk that calls the pathogen XDR-47 a "bug" instead of the software engineering section, and the model then answers wrongly.
Size-based chunking is simple but cuts words and context. Equal-length slices (a 325-character text into three of about 108) work on any content but split words ("signific"/"ant") and separate headers from their text. Overlap between chunks reduces this; chunk_by_char defaults to 150 characters with 20 of overlap.
Splitting on document structure gives the cleanest chunks. Splitting Markdown on \n## turns each section into one complete chunk. It depends on guaranteed formatting, which plain text and PDFs often lack.
Semantic chunking produces the most relevant chunks but costs the most. It groups consecutive sentences by how related NLP judges them to be. Sentence-based chunking is the practical middle ground: split on sentence punctuation with a regex and group, for example, 5 sentences with 1 of overlap.
The right choice depends on what you can guarantee about the documents. Use structure-based when you control the format (such as internal reports), sentence-based for most prose, and size-based for anything, including code. The lesson says size-based with overlap is often the production default because it reliably gives reasonable chunks.
Building with the Claude API: 32-Introducing Retrieval Augmented Generation
Putting a whole document in the prompt breaks down at scale. For an 800-page financial filing, the full text may exceed the prompt limit. The lesson adds that Claude gets less effective on very long prompts, and that larger prompts cost more and run slower.
RAG sends only the chunks relevant to the question. The document is split into chunks ahead of time. At query time you find the relevant chunk, such as "Risk Factors" for a question about company risks, and include only that in the prompt. This scales to very large documents and to many documents, with cheaper, faster prompts.
RAG trades simplicity for scale and adds design work. It needs a preprocessing step, a way to search for "relevant" chunks, and a choice among many chunking methods, and the retrieved chunks may still lack context Claude needs. The lesson says to weigh whether this complexity is worth it for your application.
Building with the Claude API: 31-The web search tool
Web search runs entirely on Anthropic's side; you supply only a schema. You add {"type": "web_search_20250305", "name": "web_search", "max_uses": 5} to the tools list and Claude decides when to search. Your organization must first enable the tool in the console settings.
max_uses limits how many searches Claude may chain. One search returns several results, but Claude may run follow-up searches based on them, and the cap prevents excessive calls.
allowed_domains limits results to sources you trust. The lesson's example restricts medical and exercise questions to nih.gov (PubMed) to get evidence-based answers instead of random blog content.
The response's block types are meant to map onto UI elements. Text blocks, the ServerToolUseBlock with the exact query, a WebSearchToolResultBlock of individual results with titles and URLs, and citation blocks with quoted supporting text. The lesson suggests showing sources as a list at the top and citations inline with domain, title, URL and quote.
Building with the Claude API: 30-The text edit tool
The text editor tool gives Claude a full set of file operations. It can view files and directories, view line ranges, replace text, create files and insert at given lines. The lesson says this lets Claude act as a software engineer from the start.
Claude knows the schema, but you write the implementation. Claude only requests file operations; your code must actually perform the create, read, replace and so on. This is the reverse of custom tools, where you write both the schema and the function.
You send a short, version-specific schema stub that Claude expands. Claude 3 models use type text_editor_20250124 named str_replace_editor; later models use text_editor_20250728 named str_replace_based_edit_tool.
Its value is file editing inside your own applications. In the example, Claude views main.py, rewrites it with a function that computes pi to 5 digits, and creates test.py with unit tests. The lesson pitches the tool for programmatic editing, environments without a capable editor, and rebuilding AI-editor features in your own app.
Building with the Claude API: 29-Fine grained tool calling
Streamed tool arguments arrive as InputJsonEvents. Each event carries partial_json, the new fragment, and snapshot, the JSON accumulated so far.
By default the API holds chunks until each top-level key validates. It buffers, say, the whole abstract value, checks that key-value pair against the tool schema, then sends the buffered chunks together before moving on to meta. That is why tool streaming arrives as pauses followed by bursts.
Fine-grained tool calling turns off that validation for immediate chunks. Passing fine_grained=True sends chunks as soon as Claude generates them, so a nested value like word_count can appear before its parent object is finished.
Without validation, your code must handle invalid JSON. Claude may produce something like "word_count": undefined, so parsing the snapshot needs a JSONDecodeError handler. The lesson notes that with validation on, the API might instead wrap bad values in strings that may not match your schema.
Default buffering is fine for most apps. The lesson recommends fine-grained mode only for live progress display, early processing of partial arguments, or when the delays hurt the user experience, and only if you are prepared to write robust JSON error handling.
Building with the Claude API: 28-Using multiple tools
Once tool handling exists, a new tool takes four small steps. Write the function, define its schema, add the schema to the tools list in run_conversation, and add an elif case for it in the run_tool router.
The reminder example chains tools because Claude can't reliably do date arithmetic. The three tools are get current datetime, add duration to datetime (included because Claude is imperfect at date addition), and set a reminder. For "177 days after Jan 1st, 2050", Claude computed June 27, 2050 with the duration tool, then called set_reminder.
One assistant message can pair explanation with a tool call. The conversation history shows assistant messages containing both a TextBlock and a ToolUseBlock, followed by tool-result messages and further assistant turns.
Building with the Claude API: 27-Implementing multiple turns
Keep calling Claude until stop_reason is no longer "tool_use".run_conversation sends the messages with the tool schemas, appends the assistant reply, and stops if stop_reason != "tool_use". Otherwise it runs the requested tools, appends their results as a user message, and loops.
Every tool-use block needs a matching tool-result block. One response can contain several tool-use blocks (for example "10 + 10" and "30 + 30"). run_tools filters the content for tool_use blocks and builds one tool_result per block, tied to it by tool_use_id, with the output as a JSON string.
Failed tools still return a result, flagged as an error. Each call is wrapped in try/except, and on an exception the result block carries "Error: {e}" with is_error: True, so Claude always gets an answer for every request.
A name-to-function router keeps the loop unchanged as tools grow.run_tool dispatches on the tool name with **tool_input, so adding a tool means adding one branch rather than touching the conversation logic.
Building with the Claude API: 26-Multi-turn conversations with tools
One question can require several tool calls in a row. For "What day is 103 days from today?", Claude first asks for get_current_datetime, then, once it has that result, for add_duration_to_datetime, and only then answers. The application has to run this back-and-forth automatically.
The fix is a loop that runs until Claude stops asking for tools. Each turn appends Claude's response to the history, runs any requested tools, and sends the result blocks back as a user message.
The helpers must handle whole messages, not just text.add_user_message/add_assistant_message now accept a string, a list of blocks or a Message object (they use .content when given one). chat accepts a tools list and returns the full message so no blocks are lost, and a text_from_message helper joins the text blocks for display.
Building with the Claude API: 25-Sending tool results
Run the tool by unpacking Claude's input as keyword arguments. The tool-use block's .input is a dictionary, so the call is get_current_datetime(**response.content[1].input).
Results go back as tool_result blocks inside a user message. Each carries a tool_use_id matching the request's id, the output serialized as a string in content, and is_error.
Matching ids keep parallel tool calls straight. When "What's 10 + 10 and what's 30 + 30?" produces two tool-use blocks (ids ab3 and po9), results can come back in any order, here po9's 60 before ab3's 20, because the ids tell Claude which result answers which request.
The follow-up needs the full history and the tool schema again. The history is the original user message, the assistant tool-use message, and the user tool-result message. The schema must still be passed even if no further call is expected, because Claude needs it to understand the tool references in the history.
Building with the Claude API: 24-Handling message blocks
With tools enabled, Claude's reply can contain several blocks. Passing a tools list of JSON schemas can produce an assistant message with a text block explaining what Claude is doing and a ToolUse block. The ToolUse block has an id, the function name (e.g. get_current_datetime), a dictionary of inputs (e.g. date_format "%H:%M:%S") and type tool_use.
Store the entire content list in the history, not just the text. Claude keeps no history of its own, so you append {"role": "assistant", "content": response.content} to keep both blocks for the next call. Text-only versions of add_user_message/add_assistant_message need updating to handle this.
A tool call takes two round trips. You send the user message with the schema, receive text plus a tool-use block, run the function yourself, send the result back with the full history, and then get Claude's final answer.
A tool spec has a name, a description and an input_schema. The input_schema uses standard JSON Schema, a long-established data-validation spec that the AI field adopted, not something specific to Claude.
The description tells Claude when to use the tool. The lesson recommends about 3–4 sentences covering what the tool does, when to use it and what it returns, plus a detailed description for each argument.
Have Claude write the schema for you. Paste the function and Anthropic's tool-use documentation into Claude and ask for "a valid JSON schema spec for the purposes of tool calling" that follows the documented best practices.
Name each schema after its function and type it with ToolParam. The convention is get_current_datetime next to get_current_datetime_schema. Wrapping the dict in anthropic.types.ToolParam isn't required, but the lesson says it prevents type errors when the schema is passed to the API.
A tool function is a plain Python function Claude triggers. It runs automatically when Claude decides it needs more information or an action. The first one built is get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"), which returns datetime.now() formatted to whatever string Claude passes, such as "%H:%M".
Validate inputs and raise clear errors, because Claude reads them. Function and parameter names should be descriptive, and bad inputs should raise informative errors (the weather example's "Location cannot be empty"; here, an empty date_format raises ValueError). Claude sees the message and may retry with corrected arguments. The next step is a JSON schema describing the function to Claude.
Reminder-setting exposes three gaps that tools must fill. For a request like "Set a reminder for my doctor's appointment. It's a week from Thursday", the lesson says Claude may know the date but not the exact time, doesn't always add durations reliably far into the future, and has no way to set a reminder.
The project builds one tool per gap, simplest first. The tools are get current date time, add duration to date time, and set a reminder. The stated principle is to extend the model with tools where it falls short, rather than working around the limits in the prompt.
Building with the Claude API: 20-Introducing tool use
Tools give Claude live information its training data lacks. Without them, a question like the weather in San Francisco gets "I don't have access to up-to-date weather information"; tools connect Claude to current data, APIs, databases and other systems.
Tool use is a four-message round trip through your server. You send the question with instructions on how to get extra data; Claude decides it needs more and asks for specific data; your server fetches it (e.g. from a weather API); you send the result back and Claude answers using both the question and the fresh data.
Building with the Claude API: 19-Providing examples
Examples teach Claude corner cases instructions struggle to describe. In tweet sentiment classification, sarcasm ("the best movie I've seen since 'Plan 9 from Outer Space'") reads as positive but is negative. Adding a plain positive example and a sarcastic one ("Oh yeah, I really needed a flight delay tonight! Excellent!" → Negative), with a note on sarcasm, addresses it; one-shot sets a pattern and multi-shot covers several scenarios.
Use your eval's top-scoring outputs as examples. Input/output pairs that scored 10 (or the highest available) show what "perfect" looks like for your task, and examples should target your most common failure cases.
Wrap examples in tags and say why they're ideal. Use <sample_input> / <ideal_output> tags, label them explicitly ("Here is an example input with an ideal response"), and follow with a sentence explaining what makes the output good. That way Claude learns the reasoning as well as the format.
Building with the Claude API: 18-Structure with XML tags
XML tags mark where instructions end and interpolated data begins. With large pasted content, such as 20 pages of sales records, or mixed content like code alongside documentation, Claude can lose track of which text is which. Wrapping each part (<sales_records>, <my_code>, <docs>) gives clear boundaries.
Descriptive, made-up tag names work better than generic ones. No official tag set is needed: <sales_records> beats <data>, and <athlete_information> groups height, weight, goal and restrictions as one unit. The lesson concedes gains on simple prompts may be small and grow as prompts become longer and more varied.
Output guidelines give Claude a target; use them almost always. Listing qualities the output must have — length, structure, required elements, tone — narrows an open request like a short story about a hidden talent (200 or 2,000 words, one character or five). The lesson calls these a safety net for nearly every prompt.
Specific guidelines roughly doubled the meal-plan eval score. Six guidelines (accurate daily calories; protein, fat and carb amounts; meal timing; only foods fitting restrictions; portions in grams; budget-friendly if mentioned) moved the score from 3.92 to 7.86.
Process steps make Claude reason through complex problems systematically. For troubleshooting, decisions, critical thinking or multi-angle problems, list steps to follow first. The example walks a 30% sales drop through market metrics, industry changes, individual performance, organizational changes and customer feedback, rather than letting Claude fix on one cause; professional prompts often combine steps with output guidelines.
Building with the Claude API: 16-Being clear and direct
Open with a plain instruction led by an action verb. The lesson calls the first line the most important part of a prompt: use simple language, state the task outright, and give instructions rather than questions, starting with verbs like "Write", "Create" or "Generate". A rambling query about "those solar panel things" becomes "Write three paragraphs about how solar panels work."
Rewriting just the opening line raised the meal-plan eval score. Replacing "What should this person eat?" with "Generate a one-day meal plan for an athlete that meets their dietary restrictions" names the action, the deliverable and the constraints. The score rose from 2.32 to 3.92.
Building with the Claude API: 15-Prompt engineering
Prompt engineering is a measured loop, one change at a time. The steps are set a goal, write an initial prompt, evaluate, apply a technique and re-evaluate, repeating the last two. The lesson says each change should be evaluated on its own so you know which techniques actually help.
The running example is a one-day athlete meal plan. Inputs are height, weight, goal and dietary restrictions (the illustration: 180cm, 75kg, maintain weight and improve power, lactose intolerant, about 3,000 calories). The deliberately naive baseline asks only "What should this person eat?"
The provided PromptEvaluator handles dataset generation and grading. Concurrency is set by max_concurrent_tasks, and the lesson advises starting low (around 3) to avoid rate-limit errors. Development datasets should stay at 2–3 cases for fast iteration, and extra_criteria adds grading requirements (calorie total, macronutrient breakdown, exact foods, portions and timing).
A very low first score is normal and gives the baseline. The evaluator produces a score plus an HTML report with per-case reasoning; the first attempt scores 2.3 out of 10, which the lesson calls typical, and the report shows where the prompt fails.
Building with the Claude API: 14-Code based grading
Syntax is checked by trying to parse the output. Three validators return 10 if parsing succeeds and 0 if it fails: json.loads for JSON, ast.parse for Python and re.compile for regex. This covers the format and valid-syntax criteria, leaving task following to the model grader.
Each test case must name its expected format. A "format" field (e.g. "python") tells the code grader which validator to run, and the dataset-generation prompt can be updated to emit it automatically.
Tighter instructions and a generic code prefill improve compliance. The prompt adds "Respond only with Python, JSON, or a plain Regex" and "Do not add any comments or commentary or explanation", and prefills "```code" so Claude starts raw code without the language being fixed in advance.
Combine model and syntax scores, then improve on the baseline. The simple approach averages the two equally, with weights adjustable by use case. The lesson says the baseline is neither good nor bad in itself; what matters is whether prompt changes raise it.
Building with the Claude API: 13-Model based grading
Graders come in three kinds with different strengths. Code graders run programmatic checks (length, required or banned words, JSON/Python/regex syntax, readability); model graders use another API call to judge quality, instruction following, completeness, helpfulness and safety; human graders are the most flexible but slow and tedious. Each returns a usable signal, usually 1–10.
Define the criteria first, then assign each to a grader. For the code-generation prompt, the criteria are format (only Python, JSON or regex), valid syntax and task following. The first two suit a code grader; task following goes to a model grader because it needs flexible judgement.
Ask the model grader for reasoning, not just a score.grade_by_model asks for 1–3 strengths, 1–3 weaknesses, reasoning and a 1–10 score as JSON (via prefill and stop sequence). The lesson says that without the surrounding analysis, models default to middling scores around 6.
Average the per-case scores into one metric to track.run_test_case now records the grader's score and reasoning, and run_eval prints the mean. The lesson concedes model graders can be capricious but says they give a consistent baseline for measuring improvement.
Three small functions make up most of an eval pipeline.run_prompt merges a test case into the template and calls Claude, run_test_case runs it and attaches a score, and run_eval loops over the dataset collecting {output, test_case, score} results. The lesson says this is most of what any eval pipeline does; the complexity lies in better prompts, grading and performance.
Grading is the missing piece, stubbed for now. The score is hardcoded to 10 so the pipeline can be tested end to end, with real graders to follow. A full run takes around 30 seconds even on Claude Haiku, and outputs are verbose because the prompt has no formatting instructions yet.
Building with the Claude API: 11-Generating test datasets
The worked eval targets clean AWS code output with no explanation. The prompt under test must return Python, JSON or a regex for AWS tasks, with no extra explanation, headers or footers. Version 1 is deliberately bare: "Please provide a solution to the following task: {task}".
Have Claude generate the test dataset, using a fast model. The lesson recommends a faster model such as Haiku for this step. generate_dataset() asks for an array of {"task": ...} objects solvable with a single function, JSON object or regex, uses the "json" prefill and "" stop sequence to get parseable JSON, requests 3 cases, and saves them to dataset.json.
Building with the Claude API: 10-A typical eval workflow
An eval runs a prompt template over a dataset of realistic inputs. The five steps are draft a prompt, build a dataset, merge each input into the template and send it to Claude, grade the outputs, then change the prompt and repeat. The example dataset has three questions ("What's 2+2?", oatmeal, distance to the Moon); real ones run to tens, hundreds or thousands of records, built by hand or generated with Claude.
A grader turns outputs into an average score you can compare. The grader sees each question and answer and scores 1–10; the example scores 10, 4 and 9 average 7.66. Adding "Answer the question with ample detail" lifts the hypothetical rerun to 8.7, and the higher-scoring version is the one to keep.
Building with the Claude API: 09-Prompt evaluation
Prompt engineering writes better prompts; prompt evaluation measures them. Engineering is the toolkit (multishot prompting, XML-tag structure and other practices); evaluation is automated testing against expected answers, comparing prompt versions and reviewing outputs for errors.
Testing a prompt once or a few times is a trap. The lesson's three paths after drafting: test once and ship (high risk of breaking in production), test a few times and patch a corner case or two (users still supply inputs you never considered), or score it in an evaluation pipeline and iterate on objective metrics. The instructor says the first two catch all engineers, including themself.
An eval pipeline costs more upfront but catches failures before users do. Its stated payoff is finding weaknesses before production, comparing versions objectively and iterating on measured improvement.
Prefill plus a stop sequence yields raw output without commentary. By default Claude wraps generated JSON in a markdown code fence and adds an explanation, which breaks copy-and-use flows like the lesson's AWS EventBridge rule generator. Prefilling the assistant message with "json" makes Claude continue with only the JSON, and `stop_sequences=[""]ends generation when it tries to close the fence;json.loads(text.strip())` cleans up stray newlines.
Use whatever wrapper Claude naturally adds as prefill and stop. The lesson says the technique applies equally to Python snippets, bulleted lists, CSV and other formatted content: identify the wrapper (usually a code fence for code) and use it for both.
Building with the Claude API: 07-Response streaming
Streaming removes the dead wait before a response appears. A response can take 10–30 seconds to generate; with streaming, the server forwards text chunks to the client as they arrive, all within a single request to Claude.
A stream is a sequence of typed events; only deltas carry text. The order is MessageStart, ContentBlockStart, repeated ContentBlockDelta, ContentBlockStop, MessageDelta and MessageStop, enabled by stream=True on messages.create. ContentBlockDelta holds the generated text to display.
The SDK's stream helper yields just text, then the full message.client.messages.stream(...) exposes stream.text_stream for text-only iteration, and stream.get_final_message() afterward returns the assembled message for storage or further processing.
Temperature reshapes the next-token probabilities Claude samples from. It is a value from 0 to 1. In the lesson's example, "about" at 30% becomes 100% at temperature 0 (fully deterministic), while near 1 the seven candidates spread out to between 9% and 23% each, giving more varied output.
Match temperature to the task. The lesson's ranges: 0.0–0.3 for factual answers, coding, data extraction and moderation; 0.4–0.7 for summarization, educational content, problem-solving and constrained creative writing; 0.8–1.0 for brainstorming, creative writing, marketing and jokes.
Higher temperature makes variety more likely but doesn't guarantee it. In the movie-idea test, temperature 0.0 consistently returned the same time-traveling-archaeologist premise, while 1.0 varied themes and plots, though similar outputs can still recur. The chat helper gains a temperature=1.0 parameter passed through to the request.
A system prompt makes Claude respond as a given role would. Passed as a plain string in the system parameter, it shapes tone and approach and keeps Claude on task. In the math-tutor example ("How do I solve 5x + 2 = 3 for x?"), Claude without one gives the full worked solution; with "You are a patient math tutor. Do not directly answer..." it asks guiding questions about the first step instead.
Include the system parameter only when one is provided. The API does not accept system=None, so the reusable chat(messages, system=None) builds a params dictionary and adds system conditionally before calling client.messages.create(**params).
Building with the Claude API: 04-Multi-Turn conversations
The API is stateless; Claude remembers nothing between requests. A follow-up like "Write another sentence" sent alone gets a sentence on some random topic, because the earlier quantum-computing exchange was never stored by the API or Claude.
Conversation memory is your code's job: resend the full history. You keep a list of messages, append Claude's reply as an assistant message and the next question as a user message, then send the whole list each time. The lesson builds three helpers for this — add_user_message, add_assistant_message and chat — reused through the rest of the course.
Keep the API key in a .env file, never in code. The setup installs anthropic<1 and python-dotenv, stores ANTHROPIC_API_KEY in a .env file loaded with load_dotenv(), and adds .env to .gitignore so the key is not committed. The client is created with Anthropic() and the course uses model "claude-haiku-4-5".
max_tokens is a safety ceiling, not a length target.client.messages.create() takes model, max_tokens and messages; with max_tokens=1000, Claude writes what it judges appropriate and is cut off only if it reaches 1,000 tokens.
Messages are role-tagged dictionaries forming the conversation. Each has a role of "user" (human-written) or "assistant" (Claude-generated) and a content string. The response object carries metadata, and the generated text itself is at message.content[0].text.
Building with the Claude API: 02-Getting an API key
A new API key is displayed only once, so copy it immediately. At platform.claude.com, the path is Get API Keys, then Create Key, choosing the "Default" workspace and a name (the course uses "Anthropic Course"). The key appears in a single pop-up; if the window is closed before copying, the advice is to delete that key and generate a new one.
Building with the Claude API: 01-Accessing the API
Never call the API from client code; route through your server. Requests need a secret API key, and anything shipped to a web or mobile client can be extracted and used for unauthorized calls, so the app talks to your server, which holds the key. Official SDKs exist for Python, TypeScript, JavaScript, Go and Ruby, or plain HTTP works.
Every request carries four required fields. They are the API key, the model name (e.g. "claude-haiku-4-5"), a list of messages holding the user's input, and max tokens, a cap on how much Claude may generate.
Claude generates text one token at a time, sampling rather than maximizing. Input is tokenized, each token turned into an embedding (a list of numbers covering a word's possible meanings, such as the four senses of "quantum"), refined by surrounding context, then passed to an output layer that assigns next-word probabilities. Claude mixes probability with controlled randomness rather than always taking the top word (the example shows "Quantum" at 30%), appends the choice, and repeats.
Generation stops on one of three conditions, reported back to you. Claude checks after each token whether it has hit max tokens, emitted an end-of-sequence token, or hit a predefined stop sequence. The response returns the message text, input and output token usage, and the stop reason, which the lesson says your application logic should handle.