{
  "source_file": "0930",
  "source_platform": "Medium",
  "author": "Avishek Biswas",
  "title_listed": "Context Engineering — A Comprehensive Hands-On Tutorial with DSPy",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://towardsdatascience.com/context-engineering-a-comprehensive-hands-on-tutorial-with-dspy/",
  "publish_date": "2025-08-06",
  "corpus_stage": "",
  "notes": "",
  "id": "fcbb8e52d971",
  "collected_at": "2026-10-02T10:26:56",
  "title": "Context Engineering — A Comprehensive Hands-On Tutorial with DSPy | Towards Data Science",
  "body_markdown": "![Revisit consent button](../images/body-df29beacd7496220cc94.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](../images/body-2159bb67f3de8ad870eb.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\n# Context Engineering — A Comprehensive Hands-On Tutorial with DSPy\nLet's dissect the art and science of context engineering, one module at a time!\n[Avishek Biswas](https://towardsdatascience.com/author/neural.avb/)\nAugust 5, 202515 min read\n![](../images/000.png)\nYou may have heard about **Context Engineering** by now. This article will cover the key ideas behind creating LLM applications using Context Engineering principles, visually explain these workflows, and share code snippets that apply these concepts practically.\nDon't worry about copy-pasting the code from this article into your editor. **At the end of this article, I will share the GitHub link to the open-source code repository and a link to my 1-hour 20-minute YouTube course that explains the concepts presented here in greater detail.**\n_Unless otherwise mentioned, all images used in this article are produced by the author and are free to use._\nLet's begin!\n···\n## What is Context Engineering?\nThere is a significant gap between writing simple prompts and building production-ready applications. Context Engineering is an umbrella term that refers to the delicate art and science of fitting information into the context window of an LLM as it works on a task.\nThe exact scope of where the definition of Context Engineering begins and ends is debatable, but according to [this tweet from Andrej Karpathy](https://x.com/karpathy/status/1937902205765607626), we can identify the following key points:\n  * It is not _just_ atomic prompt engineering, where you ask one question to the LLM and get a response\n  * It is a holistic approach that breaks up a larger problem into multiple subproblems\n  * These subproblems can be solved by multiple LLMs (or agents) in isolation. Each agent is provided with the appropriate context to carry out its task\n  * Each agent can be of appropriate capability and size depending on the complexity of the task.\n  * Intermediate steps that each agent can take to complete the task - the context is _not just_ information we input - it also includes _intermediate tokens_ that the LLMs see during generation (eg. reasoning steps, tool results, etc)\n  * The agents are connected with control flows, and we orchestrate exactly how information flows through our system\n  * The information available to the agents can come from multiple sources - external databases with Retrieval-Augmented Generation (RAG), tool calls (like web search), memory systems, or classic few-shot examples.\n  * Agents can take actions while generating responses. Each action the agent can take should be well-defined so the LLM can interact with it through reasoning and acting.\n  * Additionally, systems need to be evaluated with metrics and maintained with observability. Monitoring token usage, latency, and cost to output quality is a key consideration.\n\n\n### Important: How this article is structured\n_Throughout this article, I will be referring to the points above while providing examples of how they are applied in building real applications. Whenever I do so, I will use a block quote like this:_\n> _It is a holistic approach that breaks up a larger problem into multiple subproblems_\n_When you see a quote in this format, the example that follows will apply the quoted concept programmatically_.\nBut before that, we must ask ourselves one question...\n### Why not pass _everything_ into the LLM?\nResearch has shown that cramming every piece of information into the context of an LLM is far from ideal. Even though many frontier models do claim to support \"long-context\" windows, they still suffer from issues like _context poisoning_ or [_context rot_](https://research.trychroma.com/context-rot).\n![](../images/001.png)\nA recent report from Chroma describes how increasing tokens can negatively impact LLM performance   \n(Source: Chroma)\nToo much unnecessary information in an LLM's context can pollute the model's understanding, lead to hallucinations, and result in poor performance.\nThis is why simply having a large context window isn't enough. We need systematic approaches to context engineering. \n### Why DSPY\n![](../images/body-b5044f45cb8bbfc59b27.png)\nJust a logo\nFor this tutorial, I have chosen the DSPy framework. I will explain the reasoning for this choice shortly, but let me assure you that the concepts presented here apply to almost any prompting framework, including writing prompts in pure English.\nDSPy is a declarative framework for building modular AI software. They have neatly separated the two key aspects of any LLM task —   \n(a) the input and output contracts passed into a module,   \nand (b) the logic that governs how information flows.\n### Let's see an example!\nImagine we want to use an LLM to write a joke. Specifically, we want it to generate a setup, a punchline, and the full delivery in a comedian's voice.\nOh, and we also want the output in JSON format so that we can post-process individual fields of the dictionary after generation. For example, perhaps we want to print the punchline on a T-shirt (assume someone has already written a convenient function for that).\npython\n\n```\nsystem_prompt = \"\"\"\nYou are a comedian who tells jokes, you are always funny. \nGenerate the setup, punchline, and full delivery in the comedian's voice.\n\nOutput in the following JSON format:\n{\n\"setup\": <str>,\n\"punchline\": <str>,\n\"delivery\": <str>\n}\n\nYour response should be parsable withou errors in Python using json.loads().\n\"\"\"\n\nclient = openai.Client()\nresponse = client.chat.completions.create( \n    model=\"gpt-4o-mini\", \n    temperature = 1,\n    messages=[\n      {\"role\": \"system\", \"content\": system_prompt,\n      {\"role\": \"user\", \"content\": \"Write a joke about AI\"}\n    ] \n)\n\njoke = json.loads(response.choices[0].message.content) # Hope for the best\n\nprint_on_a_tshirt(joke[\"punchline\"])\n```\n\nNotice how we post-process the LLM's response to extract the dictionary? What if something \"bad\" happened, like the LLM failing to generate the response in the desired format? Our entire code would fail and there will be no printing on any T-shirts!\nThe above code is also quite difficult to extend. For example, if we wanted the LLM to do chain of thought reasoning before generating the answer, we would need to write additional logic to parse that reasoning text correctly.\nFurthermore, it can be difficult to look at plain English prompts like these and understand what the inputs and outputs of these systems are. DSPy solves all of the above. Let's write the above example using DSPy.\npython\n\n```\nclass JokeGenerator(dspy.Signature): \n    \"\"\"You're a comedian who tells jokes. You're always funny.\"\"\" \n    query: str = dspy.InputField()\n\n    setup: str = dspy.OutputField()\n    punchline: str = dspy.OutputField() \n    delivery: str = dspy.OutputField()\n\njoke_gen = dspy.Predict(JokeGenerator) \njoke_gen.set_lm(lm=dspy.LM(\"openai/gpt-4.1-mini\", temperature=1))\n\nresult = joke_gen(query=\"Write a joke about AI\")\nprint(result)\nprint_on_a_tshirt(result.punchline)\n\n```\n\nThis approach gives you structured, predictable outputs that you can work with programmatically, eliminating the need for regex parsing or error-prone string manipulation.\nDspy Signatures explicitly makes you define what the inputs to the system are (\"query\" in the above example), and the outputs to the system (setup, punchline, and delivery) as well as their data-types. It also tells the LLM the order in which you want them to be generated.\n![](../images/body-5dfae2251dd20b0701d0.png)\nThe output of the previous code block (minus the t-shirt stuff)\nThe `dspy.Predict` thing is an example of a DSPy Module. With modules, you define how the LLM converts from inputs to outputs. `dspy.Predict` is the most basic one - you can pass the query to it, as in `joke_gen(query=\"Write a joke about AI\")` and it will create a basic prompt to send to the LLM. Internally, DSPy just creates a prompt as you can see below.\nOnce the LLM responds, DSPy will create Pydantic `BaseModel` objects that perform automatic schema validation and send back the output. If errors occur during this validation process, DSPy automatically attempts to fix them by re-prompting the LLM—thereby significantly reducing the risk of a program crash.\n![Dspy.Predict vs Dspy.ChainOfThought](../images/002.jpeg)\nIn chain of thought, we ask the LLM to generate reasoning text before generating the answer (Source: Author)\nAnother common theme in context engineering is Chain of Thought. Here, we want the LLM to generate reasoning text before providing its final answer. This allows the LLM's context to be populated with its self-generated reasoning before it generates the final output tokens.\nTo do that, you can simply replace `dspy.Predict` with `dspy.ChainOfThought` in the example above. The rest of the code remains the same. Now you can see that the LLM generates reasoning before the defined output fields.\n## Multi-Step Interactions and Agentic Workflows\nThe best part of DSPy's approach is how it decouples system dependencies (`Signatures`) from control flows (`Modules`), which makes writing code for multi-step interactions trivial (and fun!). In this section, let's see how we can build some simple agentic flows.\n### Sequential Processing\nLet's remind ourselves about one of the key components of Context Engineering.\n> _It is a holistic approach that breaks up a larger problem into multiple subproblems_\n  \nLet's continue with our joke generation example. We can easily separate out two subproblems from it. Generating the idea is one, creating a joke is another.\n![Sequential flows](../images/003.jpeg)\nSequential flows allow us to design LLM systems in a modular way where each agent can be of appropriate strength/size and is given context and tools that are appropriate for its task (Illustrated by author)\nLet's have two agents then — the first Agent generates a joke idea (setup and punchline) from a query. A second agent then generates the joke from this idea.\n> _Each agent can be of appropriate capability and size depending on the complexity of the task_\nWe are also running the first agent with `gpt-4.1-mini` and the second agent with the more powerful `gpt-4.1`.\nNotice how we wrote our own `dspy.Module` called `JokeGenerator`. Here we use two separate dspy modules - the `query_to_idea` and the `idea_to_joke` to convert our original query to a `JokeIdea` and subsequently into a joke (as pictured above).\npython\n\n```\nclass JokeIdea(BaseModel):\n    setup: str\n    contradiction: str\n    punchline: str\n\nclass QueryToIdea(dspy.Signature):\n    \"\"\"Generate a joke idea with setup, contradiction, and punchline.\"\"\"\n    query = dspy.InputField()\n    joke_idea: JokeIdea = dspy.OutputField()\n\nclass IdeaToJoke(dspy.Signature):\n    \"\"\"Convert a joke idea into a full comedian delivery.\"\"\"\n    joke_idea: JokeIdea = dspy.InputField()\n    joke = dspy.OutputField()\n\nclass JokeGenerator(dspy.Module):\n    def __init__(self):\n        self.query_to_idea = dspy.Predict(QueryToIdea)\n        self.idea_to_joke = dspy.Predict(IdeaToJoke)\n        \n        self.query_to_idea.set_lm(lm=dspy.LM(\"openai/gpt-4.1-mini\"))\n        self.idea_to_joke.set_lm(lm=dspy.LM(\"openai/gpt-4.1\"))\n\n    \n    def forward(self, query):\n        idea = self.query_to_idea(query=query)\n        joke = self.idea_to_joke(joke_idea=idea.joke_idea)\n        return joke\n```\n\n### Iterative Refinement\nYou can also implement iterative improvement where the LLM reflects on and refines its outputs. For example, we can write a refinement module whose context is the output of a previous LM, and it must act as a feedback provider. The first LM can input this feedback and iteratively improve its response.\n![Iterative refinement](../images/004.jpeg)\nAn illustration of Iterative refinement. The Idea LM produces a \"Setup\", \"Contradiction\", and \"Punchline\" for a joke. The Joke LM generates a joke out of it. The Refinement LM provides feedback to the Joke LM to   \niteratively improve the final joke. (Source: Author)\n### Conditional Branching and Multi-Output Systems\n> _The agents are connected with control flows, and we orchestrate exactly how information flows through our system_\nSometimes you want your agent to output multiple variations, and then select the best among them. Let's look at an example of that.\nHere we have first defined a joke judge - it inputs several joke ideas, and then picks the index of the best joke. This joke is then passed into the next section.\npython\n\n```\nnum_samples = 5\n\nclass JokeJudge(dspy.Signature):\n    \"\"\"Given a list of joke ideas, you must pick the best joke\"\"\"\n    joke_ideas: list[JokeIdeas] = dspy.InputField()\n    best_idx: int = dspy.OutputField(\n        le=num_samples,\n        ge=1,\n        description=\"The index of the funniest joke\")\n\nclass ConditionalJokeGenerator(dspy.Module):\n    def __init__(self):\n        self.query_to_idea = dspy.ChainOfThought(QueryToIdea)\n        self.judge = dspy.ChainOfThought(JokeJudge)\n        self.idea_to_joke = dspy.ChainOfThought(IdeaToJoke)\n    \n    async def forward(self, query):\n        # Generate multiple ideas in parallel\n        ideas = await asyncio.gather(*[\n            self.query_to_idea.acall(query=query) \n            for _ in range(num_samples)\n        ])\n        \n        # Judge and rank ideas\n        best_idx = (await self.judge.acall(joke_ideas=ideas)).best_idx\n        \n        # Select best idea and generate final joke\n        best_idea = ideas[best_idx]\n\n        # Convert from idea to joke\n        return await self.idea_to_joke.acall(joke_idea=best_idea)\n```\n\n## Tool Calling\nLLM applications often need to interact with external systems. This is where tool-calling steps in. You can imagine a tool to be any Python function. You just need two things to define a Python function as an LLM tool:\n  * A description of what the function does\n  * A list of inputs and their data types\n\n\n![Tool Calling](../images/005.jpeg)\nAn example of a tool: Web Search. Given a query, the LLM decides if a web search is necessary, generates a query for the web if so, and then incorporates its search results to generate the final answer (Illustration by Author)\nLet's see an example of fetching news. We first write a simple Python function, where we use [Tavily](https://tavily.com/). The function inputs a search query and fetches recent news articles from the last 7 days.\npython\n\n```\nclient = TavilyClient(api_key=os.getenv(\"TAVILY_API_KEY\"))\n\ndef fetch_recent_news(query: str) -> str:\n    \"\"\"Inputs a query string, searches for news and returns top results.\"\"\"\n    response = tavily_client.search(query, search_depth=\"advanced\", \n                                    topic=\"news\", days=7, max_results=3)\n    return [x[\"content\"] for x in response[\"results\"]]\n\n```\n\nNow let's use`dspy.ReAct` (or the REasoning and ACTing). The module automatically reasons about the user's query, decides when to call which tools, and incorporates the tool results into the final response. Doing this is pretty easy:\npython\n\n```\nclass HaikuGenerator(dspy.Signature):\n    \"\"\"\nGenerates a haiku about the latest news on the query.\nAlso create a simple file where you save the final summary.\n    \"\"\"\n    query = dspy.InputField()\n    summary = dspy.OutputField(desc=\"A summary of the latest news\")\n    haiku = dspy.OutputField()\n\nprogram = dspy.ReAct(signature=HaikuGenerator,\n                     tools=[fetch_recent_news],\n                     max_iters=2)\n\nprogram.set_lm(lm=dspy.LM(\"openai/gpt-4.1\", temperature=0.7))\npred = program(query=\"OpenAI\")\n\n```\n\nWhen the above code runs, the LLM first reasons about what the user wants and which tool to call (if any). Then it generates the name of the function and the arguments to call the function.\nWe call the news function with the generated args, execute the function to generate the news data. This information is passed back into the LLM. The LLM makes a decision whether to call more tools, or \"finish\". If the LLM reasons that it has enough information to answer the user's original request, it chooses to finish, and generate the answer.\n> _Agents can take actions while generating responses. Each action the agent can take should be well defined so the LLM can interact with it through reasoning and acting._\n#### Advanced Tool Usage — Scratchpad and File I/O\nAn evolving standard for modern applications is to allow LLMs access to the file system, allowing them to read and write files, move between directories (with appropriate restrictions), grep and search text within files, and even run terminal commands!\nThis pattern opens a ton of possibilities. It transforms the LLM from a passive text generator into an active agent capable of performing complex, multi-step tasks directly within a user's environment. For example, just displaying the list of tools available to Gemini CLI will reveal a short but incredibly powerful collection of tools.\n![Gemini CLI Tools](../images/006.png)\nA screenshot of the default tools available via Gemini CLI\n### A quick word on MCP Servers\nAnother new paradigm in the space of agentic systems are MCP servers. MCPs need their own dedicated article, so I won't go over them in detail in this one.\nThis has quickly become the industry-standard way to serve specialized tools to LLMs. It follows the classic Client-Server architecture where the LLM (a client) sends a request to the MCP server, and the MCP server carries out the requested action, and returns a result back to the LLM for downstream processing. MCPs are perfect for context engineering specific examples since you can declare system prompt formats, resources, restricted database access, etc, to your application.\n[This repository has a great list of MCP servers](https://github.com/punkpeye/awesome-mcp-servers) that you can study to make your LLM applications connect with a wide variety of applications.\n## Retrieval-Augmented Generation (RAG)\nRetrieval Augmented Generation has become a cornerstone of modern AI application development. It is an architectural approach that injects external, relevant, and up-to-date information into the Large Language Models (LLMs) that is contextually relevant to the user's query.\nRAG pipelines consist of a preprocessing and an inference-time phase. During pre-processing, we process the reference data corpus and save it in a queryable format. In the inference phase, we process the user query, retrieve relevant documents from our database, and pass them into the LLM to generate a response.\n> _The information available to the agents can come from multiple sources - external database with Retrieval-Augmented Generation (RAG), tool calls (like web search), memory systems, or classic few-shot examples._\nBuilding RAGs is complicated, and there has been a lot of great research and engineering optimizations that have made life easier. I made a 17-minute video that covers all the aspects of building a reliable RAG pipeline.\nPlease accept cookies to access this content\n### Some practical tips for Good RAG\n  * When preprocessing, generate additional metadata per chunk. This can be as simple as \"questions this chunk answers\". When saving the chunks to your database, also save the generated metadata!\n\n\npython\n\n```\nclass ChunkAnnotator(dspy.Signature):\n    chunk: str = dspy.InputField()\n    possible_questions: list[str] = dspy.OutputField(\n           description=\"list of questions that this chunk answers\"\n           )\n```\n\n  * **Query Rewriting:** Directly using the user's query to do RAG retrieval is often a bad idea. Users write pretty random things, which may not match the distribution of text in your corpus. Query rewriting does what it says - it \"rewrites\" the query, perhaps fixing grammar, spelling errors, contextualizes it with past conversation, or even adds additional keywords that make querying easier.\n\n\npython\n\n```\nclass QueryRewriting(dspy.Signature):\n    user_query: str = dspy.InputField()\n    conversation: str = dspy.InputField(\n           description=\"The conversation so far\")\n    modified_query: str = dspy.OutputField(\n           description=\"a query contextualizing the user query with the conversation's context and optimized for retrieval search\"\n           )\n```\n\n  * HYDE or **Hypothetical Document Embedding** is a type of Query Rewriting system. In HYDE, we generate an artificial (or hypothetical) answer from the LLM's internal knowledge. This response often contains important keywords that try to directly match with the answers database. Vanilla query rewriting is great for searching a database of questions, and HYDE is great for searching a database with answers.  \n\n\n\n![Direct retrieval vs Query rewriting vs HYDE](../images/007.jpeg)\nDirect Retrieval vs Query Rewriting vs HYDE (Source: Author)\n  * Hybrid search is almost always better than purely semantic or purely keyword-based search. For semantic search, I'd use cosine similarity nearest neighbor search with vector embeddings. And for semantic search, use BM25.\n  * **RRF:** You can choose multiple strategies to retrieve documents, and then use reciprocal rank fusion to combine them into one unified list!\n\n\n![Multi-Hop Retrieval](../images/008.jpeg)\nMulti-Hop Retrieval and Hybrid HyDE Search (Illustrated by Author)\n  * **Multi-Hop Search** is an option to consider as well if you can afford additional latency. Here, you pass the retrieved documents back into the LLM to generate new queries, which are used to conduct additional searches on the database.\n\n\npython\n\n```\nclass MultiHopHyDESearch(dspy.Module):\n    def __init__(self, retriever):\n        self.generate_queries = dspy.ChainOfThought(QueryGeneration)\n        self.retriever = retriever\n    \n    def forward(self, query, n_hops=3):\n        results = []\n        \n        for hop in range(n_hops): # Notice we loop multiple times\n\n            # Generate optimized search queries\n            search_queries = self.generate_queries(\n                query=query, \n                previous_jokes=retrieved_jokes\n            )\n            \n            # Retrieve using both semantic and keyword search\n            semantic_results = self.retriever.semantic_search(\n                search_queries.semantic_query\n            )\n            bm25_results = self.retriever.bm25_search(\n                search_queries.bm25_query\n            )\n            \n            # Fuse results\n            hop_results = reciprocal_rank_fusion([\n                semantic_results, bm25_results\n            ])\n            results.extend(hop_results)\n        \n        return results\n```\n\n  * **Citations** : When asking LLM to generate responses from the retrieved documents, we can also ask the LLM to cite references to the documents it found useful. This allows the LLM to first generate a plan of how it's going to use the retrieved content.\n  * **Memory:** If you are building a chatbot, it is important to figure out the question of memory. You can imagine Memory as a combination of Retrieval and Tool Calling. A well-known system is the Mem0 system. The LLM observes new data and calls tools to decide if it needs to add or modify its existing memories. During question-answering, it retrieves relevant memories using RAG to generate answers.\n\n\n![](../images/body-9c37cb386150d1555dc7.png)\nThe Mem0 architecture (Source: [The Mem0 paper](https://arxiv.org/pdf/2504.19413))\n## Best Practices and Production Considerations\nThis section is not directly about Context Engineering, but more about best practices to build LLM apps for production.\n> _Additionally, systems need to be evaluated with metrics and maintained with observability. Monitoring token usage, latency, and cost to output quality is a key consideration._\n### 1. Design Evaluation First\nBefore building features, decide how you'll measure success. This helps scope your application and guides optimization decisions.   \n\n![Hyperparameters impacting LLM outputs](../images/body-d2d7115f6f0a93b22ccd.jpeg)\nA lot of parameters impact the quality of LLM's outputs (Illustrated by the author)\n  * If you can design verifiable or objective rewards, that's the best. (example: classification tasks where you have a validation dataset)\n  * If not, can you define functions that heuristically evaluate LLM responses for your use case? (example: number of times a specific chunk is retrieved given a question)\n  * If not, can you get humans to annotate your LLM's responses?\n  * If nothing works, use an LLM as a judge to evaluate responses. In most cases, you want to set your evaluation task as a comparison study, where the Judge receives multiple responses produced using different hyperparameters/prompts, and the judge must rank which ones are the best.\n\n\n![Evaluation of LLM apps](../images/body-0c95d42eaa7a48114312.jpeg)\nA simple flowchart about evaluating LLM apps (Illustration by author)\n### 3. Use Structured Outputs Almost Everywhere\nAlways prefer structured outputs over free-form text. It makes your system more reliable and easier to debug. You can add validation and retries as well!\n### 4. Design for failure\nWhen designing prompts or dspy modules, make sure you always consider \"what happens if things go wrong?\"\nLike any good software, cutting down error states and failing with swagger is the ideal scenario.\n### 5. Monitor Everything\nDSpy integrates with MLflow to track:\n  * Individual prompts passed into the LLM and their responses\n  * Token usage and costs\n  * Latency per module\n  * Success/failure rates\n  * Model performance over time\n\n\nLangfuse, Logfire are equally great alternatives. \n## Outro\nContext engineering represents a paradigm shift from simple prompt engineering to building comprehensive and modular LLM applications. \nThe DSPy framework provides the tools and abstractions needed to implement these patterns systematically. As LLM capabilities continue to evolve, context engineering will become increasingly crucial for building applications that effectively leverage the power of large language models.\nTo watch the full video course on which this article is based, please visit this YouTube link.\nPlease accept cookies to access this content\nTo access the full GitHub repo, visit:\n<https://github.com/avbiswas/context-engineering-dspy>\n![](../images/body-4dc8bb1a97c2d6148954.png)\n[Visit the Context Engineering repo for code access! ](https://github.com/avbiswas/context-engineering-dspy)\n## References\n**Author's YouTube channel** : <https://www.youtube.com/@avb_fj>\n**Author's Patreon** : [www.patreon.com/NeuralBreakdownwithAVB](https://www.patreon.com/c/NeuralBreakdownwithAVB)\n**Author's Twitter (X) account** : <https://x.com/neural_avb>\n**Full Context Engineering video course:**<https://youtu.be/5Bym0ffALaU>\n**Github Link:**<https://github.com/avbiswas/context-engineering-dspy>\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](../images/body-849620ef9680d77fc51e.jpeg)\nWritten By\n## [Avishek Biswas](https://towardsdatascience.com/author/neural.avb/)\n[See all from Avishek Biswas](https://towardsdatascience.com/author/neural.avb/)\n[Deep Dives](https://towardsdatascience.com/tag/deep-dives/)[Context Engineering](https://towardsdatascience.com/tag/context-engineering/)[Dspy](https://towardsdatascience.com/tag/dspy/)[Llm Applications](https://towardsdatascience.com/tag/llm-applications/)[Machine Learning](https://towardsdatascience.com/tag/machine-learning/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F&title=Context%20Engineering%20%E2%80%94%20A%20Comprehensive%20Hands-On%20Tutorial%20with%20DSPy)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F&title=Context%20Engineering%20%E2%80%94%20A%20Comprehensive%20Hands-On%20Tutorial%20with%20DSPy)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F&text=Context%20Engineering%20%E2%80%94%20A%20Comprehensive%20Hands-On%20Tutorial%20with%20DSPy)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](../images/009.png)](https://towardsdatascience.com/pandas-indexes-and-headers-have-you-ever-been-confused-b5a11f143876/)\n## [Pandas Indexes And Headers, Have You Ever Been Confused?](https://towardsdatascience.com/pandas-indexes-and-headers-have-you-ever-been-confused-b5a11f143876/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nFrom single-level index and headers to multi-level, why and how?\n[Christopher Tao](https://towardsdatascience.com/author/christophertao/)\nJune 9, 20248 min read\n  * [![](../images/010.jpeg)](https://towardsdatascience.com/mathematical-optimization-heuristics-every-data-scientist-should-know-b26de0bd43e6/)\n## [Mathematical Optimization Heuristics Every Data Scientist Should Know](https://towardsdatascience.com/mathematical-optimization-heuristics-every-data-scientist-should-know-b26de0bd43e6/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nLocal search, genetic algorithms and more.\n[Hennie de Harder](https://towardsdatascience.com/author/hennie-de-harder/)\nDecember 9, 20229 min read\n  * [![](../images/011.jpeg)](https://towardsdatascience.com/increase-llama-2s-latency-and-throughput-performance-by-up-to-4x-23034d781b8c/)\n## [Increase Llama 2's Latency and Throughput Performance by Up to 4X](https://towardsdatascience.com/increase-llama-2s-latency-and-throughput-performance-by-up-to-4x-23034d781b8c/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nReal-world benchmarks for Llama-2 13B\n[Het Trivedi](https://towardsdatascience.com/author/het.trivedi05/)\nAugust 8, 20237 min read\n  * [![](../images/012.jpg)](https://towardsdatascience.com/step-by-step-approach-of-building-data-pipelines-as-a-data-scientist-or-a-machine-learning-engineer-4b3e8da3bcd1/)\n## [Step-by-step Approach of Building Data Pipelines as a Data Scientist or a Machine Learning Engineer](https://towardsdatascience.com/step-by-step-approach-of-building-data-pipelines-as-a-data-scientist-or-a-machine-learning-engineer-4b3e8da3bcd1/)\n[Deep Learning](https://towardsdatascience.com/category/artificial-intelligence/deep-learning/)\nLearn how to build interesting pipelines in the field of machine learning to develop a system that can perform AI capabilities\n[Suhas Maddali](https://towardsdatascience.com/author/suhas-maddali007/)\nNovember 2, 20228 min read\n  * [![](../images/013.png)](https://towardsdatascience.com/what-is-universality-in-llm-and-how-to-find-universal-neurons/)\n## [What is Universality in LLMs? How to Find Universal Neurons](https://towardsdatascience.com/what-is-universality-in-llm-and-how-to-find-universal-neurons/)\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\nHow independently trained transformers form same the neurons\n[Shuyang Xiang](https://towardsdatascience.com/author/vanillaxiangshuyang/)\nSeptember 2, 20255 min read\n  * [![](../images/014.jpeg)](https://towardsdatascience.com/fit-vs-predict-vs-fit-predict-in-python-scikit-learn-f15a34a8d39f/)\n## [fit() vs predict() vs fit_predict() in Python scikit-learn](https://towardsdatascience.com/fit-vs-predict-vs-fit-predict-in-python-scikit-learn-f15a34a8d39f/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nWhat's the difference between fit, predict and fit_predict methods in sklearn\n[Giorgos Myrianthous](https://towardsdatascience.com/author/gmyrianthous/)\nMarch 9, 20215 min read\n  * [![](../images/015.jpeg)](https://towardsdatascience.com/simulated-data-real-learnings-power-analysis-652045eeae22/)\n## [Simulated Data, Real Learnings : Power Analysis](https://towardsdatascience.com/simulated-data-real-learnings-power-analysis-652045eeae22/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nPart 2 - Experimental Power Analysis\n[Jarom Hulet](https://towardsdatascience.com/author/jarom.hulet/)\nMarch 26, 202411 min read\n  * [![](../images/016.jpg)](https://towardsdatascience.com/how-to-develop-powerf-interal-llm-benchmarks/)\n## [How to Develop Powerful Internal LLM Benchmarks](https://towardsdatascience.com/how-to-develop-powerf-interal-llm-benchmarks/)\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\nLearn how to compare LLMs using your own internal benchmark\n[Eivind Kjosbakken](https://towardsdatascience.com/author/oieivind/)\nAugust 26, 20256 min read\n  * [![](../images/017.jpg)](https://towardsdatascience.com/how-to-use-causal-inference-when-a-b-testing-is-not-possible-c87c1252724a/)\n## [How to use Causal Inference when A/B testing is not available](https://towardsdatascience.com/how-to-use-causal-inference-when-a-b-testing-is-not-possible-c87c1252724a/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nEvaluating ad targeting product using causal inference: propensity score matching!\n[Harry Lu](https://towardsdatascience.com/author/chinheng.h.lu/)\nJanuary 8, 20247 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n",
  "metadata": {
    "title": "Context Engineering — A Comprehensive Hands-On Tutorial with DSPy | Towards Data Science",
    "description": "Let's dissect the art and science of context engineering, one module at a time!",
    "keywords": null,
    "author": "Avishek Biswas",
    "og:locale": "en_US",
    "og:type": "article",
    "og:title": "Context Engineering — A Comprehensive Hands-On Tutorial with DSPy",
    "og:description": "Let's dissect the art and science of context engineering, one module at a time!",
    "og:url": "https://towardsdatascience.com/context-engineering-a-comprehensive-hands-on-tutorial-with-dspy/",
    "og:site_name": "Towards Data Science",
    "og:image": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/50.png",
    "og:image:width": "1280",
    "og:image:height": "720",
    "og:image:type": "image/png",
    "twitter:card": "summary_large_image",
    "twitter:site": "@TDataScience",
    "twitter:creator": "@TDataScience",
    "twitter:label1": "Written by",
    "twitter:data1": "Avishek Biswas",
    "twitter:label2": "Est. reading time",
    "twitter:data2": "15 min read",
    "article:published_time": "2025-08-05T22:05:38.000Z",
    "article:modified_time": "2025-08-05T22:05:38.000Z",
    "article:author": "https://towardsdatascience.com/author/neural.avb/"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 202,
    "error": ""
  },
  "code_blocks": [
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_000.txt",
      "chars": 696
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_001.txt",
      "chars": 465
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_002.txt",
      "chars": 908
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_003.txt",
      "chars": 1081
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_004.txt",
      "chars": 376
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_005.txt",
      "chars": 522
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_006.txt",
      "chars": 207
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_007.txt",
      "chars": 357
    },
    {
      "language": "txt",
      "file": "attachments/fcbb8e52d971/code/block_008.txt",
      "chars": 1045
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://cdn-cookieyes.com/assets/images/revisit.svg",
      "bytes": 2339,
      "reused": false,
      "content_type": "image/svg+xml",
      "local_path": "attachments/fcbb8e52d971/images/body-df29beacd7496220cc94.svg",
      "src": "https://cdn-cookieyes.com/assets/images/revisit.svg",
      "alt": "Revisit consent button",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://cdn-cookieyes.com/assets/images/close.svg",
      "bytes": 1345,
      "reused": false,
      "content_type": "image/svg+xml",
      "local_path": "attachments/fcbb8e52d971/images/body-2159bb67f3de8ad870eb.svg",
      "src": "https://cdn-cookieyes.com/assets/images/close.svg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/50.png",
      "bytes": 142347,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/000.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/50.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-31-1024x576.png",
      "bytes": 78194,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/001.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-31-1024x576.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-32.png",
      "bytes": 7414,
      "reused": false,
      "content_type": "image/png",
      "local_path": "attachments/fcbb8e52d971/images/body-b5044f45cb8bbfc59b27.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-32.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-17.02.35@2x-1024x253.png",
      "bytes": 24166,
      "reused": false,
      "content_type": "image/png",
      "local_path": "attachments/fcbb8e52d971/images/body-5dfae2251dd20b0701d0.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-17.02.35@2x-1024x253.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide1-1024x576.jpeg",
      "bytes": 27365,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/002.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide1-1024x576.jpeg",
      "alt": "Dspy.Predict vs Dspy.ChainOfThought",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide3-1024x576.jpeg",
      "bytes": 33781,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/003.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide3-1024x576.jpeg",
      "alt": "Sequential flows",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide4-1024x576.jpeg",
      "bytes": 21199,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/004.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide4-1024x576.jpeg",
      "alt": "Iterative refinement",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide11-1024x576.jpeg",
      "bytes": 39949,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/005.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide11-1024x576.jpeg",
      "alt": "Tool Calling",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-11.53.29@2x-1024x908.png",
      "bytes": 28833,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/006.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-11.53.29@2x-1024x908.png",
      "alt": "Gemini CLI Tools",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide33-1024x846.jpeg",
      "bytes": 102600,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/007.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide33-1024x846.jpeg",
      "alt": "Direct retrieval vs Query rewriting vs HYDE",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide14-1024x576.jpeg",
      "bytes": 47993,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/008.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide14-1024x576.jpeg",
      "alt": "Multi-Hop Retrieval",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-05-at-00.18.12@2x-1024x526.png",
      "bytes": 46290,
      "reused": false,
      "content_type": "image/png",
      "local_path": "attachments/fcbb8e52d971/images/body-9c37cb386150d1555dc7.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-05-at-00.18.12@2x-1024x526.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide8-1024x576.jpeg",
      "bytes": 48178,
      "reused": false,
      "content_type": "image/jpeg",
      "local_path": "attachments/fcbb8e52d971/images/body-d2d7115f6f0a93b22ccd.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide8-1024x576.jpeg",
      "alt": "Hyperparameters impacting LLM outputs",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide9-1024x576.jpeg",
      "bytes": 44114,
      "reused": false,
      "content_type": "image/jpeg",
      "local_path": "attachments/fcbb8e52d971/images/body-0c95d42eaa7a48114312.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide9-1024x576.jpeg",
      "alt": "Evaluation of LLM apps",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-16.22.34@2x-1024x671.png",
      "bytes": 53489,
      "reused": false,
      "content_type": "image/png",
      "local_path": "attachments/fcbb8e52d971/images/body-4dc8bb1a97c2d6148954.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-16.22.34@2x-1024x671.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/1787657599061_2dgnrr.jpeg",
      "bytes": 140311,
      "reused": false,
      "content_type": "image/jpeg",
      "local_path": "attachments/fcbb8e52d971/images/body-849620ef9680d77fc51e.jpeg",
      "src": "https://assets.insightmediagroup.io/media/1787657599061_2dgnrr.jpeg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2024/06/19GG0bJ2ZhSMjsYhQpMqlfw.png",
      "bytes": 1459101,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/009.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2024/06/19GG0bJ2ZhSMjsYhQpMqlfw.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2022/12/1Jvn7tyMX8OYGXVeSLn4U6Q.jpeg",
      "bytes": 93760,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/010.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2022/12/1Jvn7tyMX8OYGXVeSLn4U6Q.jpeg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2023/08/1EosxuvAZXq3vDKnsSTPScg-scaled.jpeg",
      "bytes": 309924,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/011.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2023/08/1EosxuvAZXq3vDKnsSTPScg-scaled.jpeg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2022/11/00PeGePMkelmI7RjP-scaled.jpg",
      "bytes": 746103,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/012.jpg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2022/11/00PeGePMkelmI7RjP-scaled.jpg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/09/download-4.png",
      "bytes": 29283,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/013.png",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/09/download-4.png",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2021/03/1M5pCg5RLzy1t6gEYVbnPRg-scaled.jpeg",
      "bytes": 140448,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/014.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2021/03/1M5pCg5RLzy1t6gEYVbnPRg-scaled.jpeg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2024/03/1Q8vye7yAsxeiGwlnDbpQJQ-scaled.jpeg",
      "bytes": 462180,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/015.jpeg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2024/03/1Q8vye7yAsxeiGwlnDbpQJQ-scaled.jpeg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-269.jpg",
      "bytes": 426701,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/016.jpg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-269.jpg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    },
    {
      "status": "ok",
      "url": "https://assets.insightmediagroup.io/media/wp-content/uploads/2024/01/0DRYPGuUHflPZ0a1R-scaled.jpg",
      "bytes": 402090,
      "reused": true,
      "local_path": "attachments/fcbb8e52d971/images/017.jpg",
      "src": "https://assets.insightmediagroup.io/media/wp-content/uploads/2024/01/0DRYPGuUHflPZ0a1R-scaled.jpg",
      "alt": "",
      "downloaded": true,
      "discovery": "saved_markdown"
    }
  ],
  "audio": [],
  "videos": [
    {
      "url": "https://www.youtube.com/@avb_fj",
      "kind": "channel_reference",
      "status": "reference_only"
    },
    {
      "url": "https://www.youtube.com/watch?v=5Bym0ffALaU",
      "status": "ok",
      "local_path": "attachments/fcbb8e52d971/videos/5Bym0ffALaU.mp4",
      "bytes": 348624563,
      "valid": true,
      "height": 1080,
      "width": 1920,
      "duration": "4944.401000"
    },
    {
      "url": "https://www.youtube.com/c/TowardsDataScience",
      "kind": "channel_reference",
      "status": "reference_only"
    }
  ],
  "links": [
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/latest/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/editors-pick/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/the-variable/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/shipai/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/submissions/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/artificial-intelligence/large-language-models/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/neural.avb/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/deep-dives/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/context-engineering/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/dspy/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/llm-applications/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/machine-learning/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/pandas-indexes-and-headers-have-you-ever-been-confused-b5a11f143876/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/artificial-intelligence/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/christophertao/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/mathematical-optimization-heuristics-every-data-scientist-should-know-b26de0bd43e6/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/artificial-intelligence/machine-learning/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/hennie-de-harder/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/increase-llama-2s-latency-and-throughput-performance-by-up-to-4x-23034d781b8c/"
    },
    {
      "type": "external",
      "href": "https://write.insightmediagroup.io"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/company/towards-data-science/"
    },
    {
      "type": "external",
      "href": "https://x.com/TDataScience"
    },
    {
      "type": "external",
      "href": "https://x.com/karpathy/status/1937902205765607626"
    },
    {
      "type": "external",
      "href": "https://research.trychroma.com/context-rot"
    },
    {
      "type": "external",
      "href": "https://tavily.com/"
    },
    {
      "type": "external",
      "href": "https://github.com/punkpeye/awesome-mcp-servers"
    },
    {
      "type": "external",
      "href": "https://arxiv.org/pdf/2504.19413"
    },
    {
      "type": "external",
      "href": "https://github.com/avbiswas/context-engineering-dspy"
    },
    {
      "type": "external",
      "href": "https://www.youtube.com/@avb_fj"
    },
    {
      "type": "external",
      "href": "https://www.patreon.com/c/NeuralBreakdownwithAVB"
    },
    {
      "type": "external",
      "href": "https://x.com/neural_avb"
    },
    {
      "type": "external",
      "href": "https://youtu.be/5Bym0ffALaU"
    },
    {
      "type": "external",
      "href": "https://www.facebook.com/sharer/sharer.php?title=Context+Engineering+%E2%80%94+A+Comprehensive+Hands-On+Tutorial+with+DSPy&u=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/shareArticle?mini=true&title=Context+Engineering+%E2%80%94+A+Comprehensive+Hands-On+Tutorial+with+DSPy&url=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F"
    },
    {
      "type": "external",
      "href": "https://x.com/share?text=Context+Engineering+%E2%80%94+A+Comprehensive+Hands-On+Tutorial+with+DSPy&url=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F"
    },
    {
      "type": "external",
      "href": "https://www.youtube.com/c/TowardsDataScience"
    },
    {
      "type": "external",
      "href": "https://www.threads.net/@towardsdatascience"
    },
    {
      "type": "external",
      "href": "https://bsky.app/profile/towardsdatascience.com"
    },
    {
      "type": "external",
      "href": "https://thenewstack.io"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-20T17:33:19Z",
  "source_url": "https://towardsdatascience.com/context-engineering-a-comprehensive-hands-on-tutorial-with-dspy/",
  "delivery_status": "unverified",
  "discovery_status": "http_403",
  "missing_reason": "http_403",
  "repair_error": "",
  "actual_video_files": [
    {
      "local_path": "attachments/fcbb8e52d971/videos/5Bym0ffALaU.mp4",
      "bytes": 348624563,
      "valid": true,
      "height": 1080,
      "width": 1920,
      "duration": "4944.401000",
      "status": "ok"
    }
  ],
  "expected_saved_body_images": 27,
  "image_discovery": "saved_markdown",
  "source_body_markdown": "![Revisit consent button](https://cdn-cookieyes.com/assets/images/revisit.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](https://cdn-cookieyes.com/assets/images/close.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\n# Context Engineering — A Comprehensive Hands-On Tutorial with DSPy\nLet's dissect the art and science of context engineering, one module at a time!\n[Avishek Biswas](https://towardsdatascience.com/author/neural.avb/)\nAugust 5, 202515 min read\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/50.png)\nYou may have heard about **Context Engineering** by now. This article will cover the key ideas behind creating LLM applications using Context Engineering principles, visually explain these workflows, and share code snippets that apply these concepts practically.\nDon't worry about copy-pasting the code from this article into your editor. **At the end of this article, I will share the GitHub link to the open-source code repository and a link to my 1-hour 20-minute YouTube course that explains the concepts presented here in greater detail.**\n_Unless otherwise mentioned, all images used in this article are produced by the author and are free to use._\nLet's begin!\n···\n## What is Context Engineering?\nThere is a significant gap between writing simple prompts and building production-ready applications. Context Engineering is an umbrella term that refers to the delicate art and science of fitting information into the context window of an LLM as it works on a task.\nThe exact scope of where the definition of Context Engineering begins and ends is debatable, but according to [this tweet from Andrej Karpathy](https://x.com/karpathy/status/1937902205765607626), we can identify the following key points:\n  * It is not _just_ atomic prompt engineering, where you ask one question to the LLM and get a response\n  * It is a holistic approach that breaks up a larger problem into multiple subproblems\n  * These subproblems can be solved by multiple LLMs (or agents) in isolation. Each agent is provided with the appropriate context to carry out its task\n  * Each agent can be of appropriate capability and size depending on the complexity of the task.\n  * Intermediate steps that each agent can take to complete the task - the context is _not just_ information we input - it also includes _intermediate tokens_ that the LLMs see during generation (eg. reasoning steps, tool results, etc)\n  * The agents are connected with control flows, and we orchestrate exactly how information flows through our system\n  * The information available to the agents can come from multiple sources - external databases with Retrieval-Augmented Generation (RAG), tool calls (like web search), memory systems, or classic few-shot examples.\n  * Agents can take actions while generating responses. Each action the agent can take should be well-defined so the LLM can interact with it through reasoning and acting.\n  * Additionally, systems need to be evaluated with metrics and maintained with observability. Monitoring token usage, latency, and cost to output quality is a key consideration.\n\n\n### Important: How this article is structured\n_Throughout this article, I will be referring to the points above while providing examples of how they are applied in building real applications. Whenever I do so, I will use a block quote like this:_\n> _It is a holistic approach that breaks up a larger problem into multiple subproblems_\n_When you see a quote in this format, the example that follows will apply the quoted concept programmatically_.\nBut before that, we must ask ourselves one question...\n### Why not pass _everything_ into the LLM?\nResearch has shown that cramming every piece of information into the context of an LLM is far from ideal. Even though many frontier models do claim to support \"long-context\" windows, they still suffer from issues like _context poisoning_ or [_context rot_](https://research.trychroma.com/context-rot).\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-31-1024x576.png)\nA recent report from Chroma describes how increasing tokens can negatively impact LLM performance   \n(Source: Chroma)\nToo much unnecessary information in an LLM's context can pollute the model's understanding, lead to hallucinations, and result in poor performance.\nThis is why simply having a large context window isn't enough. We need systematic approaches to context engineering. \n### Why DSPY\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-32.png)\nJust a logo\nFor this tutorial, I have chosen the DSPy framework. I will explain the reasoning for this choice shortly, but let me assure you that the concepts presented here apply to almost any prompting framework, including writing prompts in pure English.\nDSPy is a declarative framework for building modular AI software. They have neatly separated the two key aspects of any LLM task —   \n(a) the input and output contracts passed into a module,   \nand (b) the logic that governs how information flows.\n### Let's see an example!\nImagine we want to use an LLM to write a joke. Specifically, we want it to generate a setup, a punchline, and the full delivery in a comedian's voice.\nOh, and we also want the output in JSON format so that we can post-process individual fields of the dictionary after generation. For example, perhaps we want to print the punchline on a T-shirt (assume someone has already written a convenient function for that).\npython\n\n```\nsystem_prompt = \"\"\"\nYou are a comedian who tells jokes, you are always funny. \nGenerate the setup, punchline, and full delivery in the comedian's voice.\n\nOutput in the following JSON format:\n{\n\"setup\": <str>,\n\"punchline\": <str>,\n\"delivery\": <str>\n}\n\nYour response should be parsable withou errors in Python using json.loads().\n\"\"\"\n\nclient = openai.Client()\nresponse = client.chat.completions.create( \n    model=\"gpt-4o-mini\", \n    temperature = 1,\n    messages=[\n      {\"role\": \"system\", \"content\": system_prompt,\n      {\"role\": \"user\", \"content\": \"Write a joke about AI\"}\n    ] \n)\n\njoke = json.loads(response.choices[0].message.content) # Hope for the best\n\nprint_on_a_tshirt(joke[\"punchline\"])\n```\n\nNotice how we post-process the LLM's response to extract the dictionary? What if something \"bad\" happened, like the LLM failing to generate the response in the desired format? Our entire code would fail and there will be no printing on any T-shirts!\nThe above code is also quite difficult to extend. For example, if we wanted the LLM to do chain of thought reasoning before generating the answer, we would need to write additional logic to parse that reasoning text correctly.\nFurthermore, it can be difficult to look at plain English prompts like these and understand what the inputs and outputs of these systems are. DSPy solves all of the above. Let's write the above example using DSPy.\npython\n\n```\nclass JokeGenerator(dspy.Signature): \n    \"\"\"You're a comedian who tells jokes. You're always funny.\"\"\" \n    query: str = dspy.InputField()\n\n    setup: str = dspy.OutputField()\n    punchline: str = dspy.OutputField() \n    delivery: str = dspy.OutputField()\n\njoke_gen = dspy.Predict(JokeGenerator) \njoke_gen.set_lm(lm=dspy.LM(\"openai/gpt-4.1-mini\", temperature=1))\n\nresult = joke_gen(query=\"Write a joke about AI\")\nprint(result)\nprint_on_a_tshirt(result.punchline)\n\n```\n\nThis approach gives you structured, predictable outputs that you can work with programmatically, eliminating the need for regex parsing or error-prone string manipulation.\nDspy Signatures explicitly makes you define what the inputs to the system are (\"query\" in the above example), and the outputs to the system (setup, punchline, and delivery) as well as their data-types. It also tells the LLM the order in which you want them to be generated.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-17.02.35@2x-1024x253.png)\nThe output of the previous code block (minus the t-shirt stuff)\nThe `dspy.Predict` thing is an example of a DSPy Module. With modules, you define how the LLM converts from inputs to outputs. `dspy.Predict` is the most basic one - you can pass the query to it, as in `joke_gen(query=\"Write a joke about AI\")` and it will create a basic prompt to send to the LLM. Internally, DSPy just creates a prompt as you can see below.\nOnce the LLM responds, DSPy will create Pydantic `BaseModel` objects that perform automatic schema validation and send back the output. If errors occur during this validation process, DSPy automatically attempts to fix them by re-prompting the LLM—thereby significantly reducing the risk of a program crash.\n![Dspy.Predict vs Dspy.ChainOfThought](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide1-1024x576.jpeg)\nIn chain of thought, we ask the LLM to generate reasoning text before generating the answer (Source: Author)\nAnother common theme in context engineering is Chain of Thought. Here, we want the LLM to generate reasoning text before providing its final answer. This allows the LLM's context to be populated with its self-generated reasoning before it generates the final output tokens.\nTo do that, you can simply replace `dspy.Predict` with `dspy.ChainOfThought` in the example above. The rest of the code remains the same. Now you can see that the LLM generates reasoning before the defined output fields.\n## Multi-Step Interactions and Agentic Workflows\nThe best part of DSPy's approach is how it decouples system dependencies (`Signatures`) from control flows (`Modules`), which makes writing code for multi-step interactions trivial (and fun!). In this section, let's see how we can build some simple agentic flows.\n### Sequential Processing\nLet's remind ourselves about one of the key components of Context Engineering.\n> _It is a holistic approach that breaks up a larger problem into multiple subproblems_\n  \nLet's continue with our joke generation example. We can easily separate out two subproblems from it. Generating the idea is one, creating a joke is another.\n![Sequential flows](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide3-1024x576.jpeg)\nSequential flows allow us to design LLM systems in a modular way where each agent can be of appropriate strength/size and is given context and tools that are appropriate for its task (Illustrated by author)\nLet's have two agents then — the first Agent generates a joke idea (setup and punchline) from a query. A second agent then generates the joke from this idea.\n> _Each agent can be of appropriate capability and size depending on the complexity of the task_\nWe are also running the first agent with `gpt-4.1-mini` and the second agent with the more powerful `gpt-4.1`.\nNotice how we wrote our own `dspy.Module` called `JokeGenerator`. Here we use two separate dspy modules - the `query_to_idea` and the `idea_to_joke` to convert our original query to a `JokeIdea` and subsequently into a joke (as pictured above).\npython\n\n```\nclass JokeIdea(BaseModel):\n    setup: str\n    contradiction: str\n    punchline: str\n\nclass QueryToIdea(dspy.Signature):\n    \"\"\"Generate a joke idea with setup, contradiction, and punchline.\"\"\"\n    query = dspy.InputField()\n    joke_idea: JokeIdea = dspy.OutputField()\n\nclass IdeaToJoke(dspy.Signature):\n    \"\"\"Convert a joke idea into a full comedian delivery.\"\"\"\n    joke_idea: JokeIdea = dspy.InputField()\n    joke = dspy.OutputField()\n\nclass JokeGenerator(dspy.Module):\n    def __init__(self):\n        self.query_to_idea = dspy.Predict(QueryToIdea)\n        self.idea_to_joke = dspy.Predict(IdeaToJoke)\n        \n        self.query_to_idea.set_lm(lm=dspy.LM(\"openai/gpt-4.1-mini\"))\n        self.idea_to_joke.set_lm(lm=dspy.LM(\"openai/gpt-4.1\"))\n\n    \n    def forward(self, query):\n        idea = self.query_to_idea(query=query)\n        joke = self.idea_to_joke(joke_idea=idea.joke_idea)\n        return joke\n```\n\n### Iterative Refinement\nYou can also implement iterative improvement where the LLM reflects on and refines its outputs. For example, we can write a refinement module whose context is the output of a previous LM, and it must act as a feedback provider. The first LM can input this feedback and iteratively improve its response.\n![Iterative refinement](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide4-1024x576.jpeg)\nAn illustration of Iterative refinement. The Idea LM produces a \"Setup\", \"Contradiction\", and \"Punchline\" for a joke. The Joke LM generates a joke out of it. The Refinement LM provides feedback to the Joke LM to   \niteratively improve the final joke. (Source: Author)\n### Conditional Branching and Multi-Output Systems\n> _The agents are connected with control flows, and we orchestrate exactly how information flows through our system_\nSometimes you want your agent to output multiple variations, and then select the best among them. Let's look at an example of that.\nHere we have first defined a joke judge - it inputs several joke ideas, and then picks the index of the best joke. This joke is then passed into the next section.\npython\n\n```\nnum_samples = 5\n\nclass JokeJudge(dspy.Signature):\n    \"\"\"Given a list of joke ideas, you must pick the best joke\"\"\"\n    joke_ideas: list[JokeIdeas] = dspy.InputField()\n    best_idx: int = dspy.OutputField(\n        le=num_samples,\n        ge=1,\n        description=\"The index of the funniest joke\")\n\nclass ConditionalJokeGenerator(dspy.Module):\n    def __init__(self):\n        self.query_to_idea = dspy.ChainOfThought(QueryToIdea)\n        self.judge = dspy.ChainOfThought(JokeJudge)\n        self.idea_to_joke = dspy.ChainOfThought(IdeaToJoke)\n    \n    async def forward(self, query):\n        # Generate multiple ideas in parallel\n        ideas = await asyncio.gather(*[\n            self.query_to_idea.acall(query=query) \n            for _ in range(num_samples)\n        ])\n        \n        # Judge and rank ideas\n        best_idx = (await self.judge.acall(joke_ideas=ideas)).best_idx\n        \n        # Select best idea and generate final joke\n        best_idea = ideas[best_idx]\n\n        # Convert from idea to joke\n        return await self.idea_to_joke.acall(joke_idea=best_idea)\n```\n\n## Tool Calling\nLLM applications often need to interact with external systems. This is where tool-calling steps in. You can imagine a tool to be any Python function. You just need two things to define a Python function as an LLM tool:\n  * A description of what the function does\n  * A list of inputs and their data types\n\n\n![Tool Calling](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide11-1024x576.jpeg)\nAn example of a tool: Web Search. Given a query, the LLM decides if a web search is necessary, generates a query for the web if so, and then incorporates its search results to generate the final answer (Illustration by Author)\nLet's see an example of fetching news. We first write a simple Python function, where we use [Tavily](https://tavily.com/). The function inputs a search query and fetches recent news articles from the last 7 days.\npython\n\n```\nclient = TavilyClient(api_key=os.getenv(\"TAVILY_API_KEY\"))\n\ndef fetch_recent_news(query: str) -> str:\n    \"\"\"Inputs a query string, searches for news and returns top results.\"\"\"\n    response = tavily_client.search(query, search_depth=\"advanced\", \n                                    topic=\"news\", days=7, max_results=3)\n    return [x[\"content\"] for x in response[\"results\"]]\n\n```\n\nNow let's use`dspy.ReAct` (or the REasoning and ACTing). The module automatically reasons about the user's query, decides when to call which tools, and incorporates the tool results into the final response. Doing this is pretty easy:\npython\n\n```\nclass HaikuGenerator(dspy.Signature):\n    \"\"\"\nGenerates a haiku about the latest news on the query.\nAlso create a simple file where you save the final summary.\n    \"\"\"\n    query = dspy.InputField()\n    summary = dspy.OutputField(desc=\"A summary of the latest news\")\n    haiku = dspy.OutputField()\n\nprogram = dspy.ReAct(signature=HaikuGenerator,\n                     tools=[fetch_recent_news],\n                     max_iters=2)\n\nprogram.set_lm(lm=dspy.LM(\"openai/gpt-4.1\", temperature=0.7))\npred = program(query=\"OpenAI\")\n\n```\n\nWhen the above code runs, the LLM first reasons about what the user wants and which tool to call (if any). Then it generates the name of the function and the arguments to call the function.\nWe call the news function with the generated args, execute the function to generate the news data. This information is passed back into the LLM. The LLM makes a decision whether to call more tools, or \"finish\". If the LLM reasons that it has enough information to answer the user's original request, it chooses to finish, and generate the answer.\n> _Agents can take actions while generating responses. Each action the agent can take should be well defined so the LLM can interact with it through reasoning and acting._\n#### Advanced Tool Usage — Scratchpad and File I/O\nAn evolving standard for modern applications is to allow LLMs access to the file system, allowing them to read and write files, move between directories (with appropriate restrictions), grep and search text within files, and even run terminal commands!\nThis pattern opens a ton of possibilities. It transforms the LLM from a passive text generator into an active agent capable of performing complex, multi-step tasks directly within a user's environment. For example, just displaying the list of tools available to Gemini CLI will reveal a short but incredibly powerful collection of tools.\n![Gemini CLI Tools](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-11.53.29@2x-1024x908.png)\nA screenshot of the default tools available via Gemini CLI\n### A quick word on MCP Servers\nAnother new paradigm in the space of agentic systems are MCP servers. MCPs need their own dedicated article, so I won't go over them in detail in this one.\nThis has quickly become the industry-standard way to serve specialized tools to LLMs. It follows the classic Client-Server architecture where the LLM (a client) sends a request to the MCP server, and the MCP server carries out the requested action, and returns a result back to the LLM for downstream processing. MCPs are perfect for context engineering specific examples since you can declare system prompt formats, resources, restricted database access, etc, to your application.\n[This repository has a great list of MCP servers](https://github.com/punkpeye/awesome-mcp-servers) that you can study to make your LLM applications connect with a wide variety of applications.\n## Retrieval-Augmented Generation (RAG)\nRetrieval Augmented Generation has become a cornerstone of modern AI application development. It is an architectural approach that injects external, relevant, and up-to-date information into the Large Language Models (LLMs) that is contextually relevant to the user's query.\nRAG pipelines consist of a preprocessing and an inference-time phase. During pre-processing, we process the reference data corpus and save it in a queryable format. In the inference phase, we process the user query, retrieve relevant documents from our database, and pass them into the LLM to generate a response.\n> _The information available to the agents can come from multiple sources - external database with Retrieval-Augmented Generation (RAG), tool calls (like web search), memory systems, or classic few-shot examples._\nBuilding RAGs is complicated, and there has been a lot of great research and engineering optimizations that have made life easier. I made a 17-minute video that covers all the aspects of building a reliable RAG pipeline.\nPlease accept cookies to access this content\n### Some practical tips for Good RAG\n  * When preprocessing, generate additional metadata per chunk. This can be as simple as \"questions this chunk answers\". When saving the chunks to your database, also save the generated metadata!\n\n\npython\n\n```\nclass ChunkAnnotator(dspy.Signature):\n    chunk: str = dspy.InputField()\n    possible_questions: list[str] = dspy.OutputField(\n           description=\"list of questions that this chunk answers\"\n           )\n```\n\n  * **Query Rewriting:** Directly using the user's query to do RAG retrieval is often a bad idea. Users write pretty random things, which may not match the distribution of text in your corpus. Query rewriting does what it says - it \"rewrites\" the query, perhaps fixing grammar, spelling errors, contextualizes it with past conversation, or even adds additional keywords that make querying easier.\n\n\npython\n\n```\nclass QueryRewriting(dspy.Signature):\n    user_query: str = dspy.InputField()\n    conversation: str = dspy.InputField(\n           description=\"The conversation so far\")\n    modified_query: str = dspy.OutputField(\n           description=\"a query contextualizing the user query with the conversation's context and optimized for retrieval search\"\n           )\n```\n\n  * HYDE or **Hypothetical Document Embedding** is a type of Query Rewriting system. In HYDE, we generate an artificial (or hypothetical) answer from the LLM's internal knowledge. This response often contains important keywords that try to directly match with the answers database. Vanilla query rewriting is great for searching a database of questions, and HYDE is great for searching a database with answers.  \n\n\n\n![Direct retrieval vs Query rewriting vs HYDE](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide33-1024x846.jpeg)\nDirect Retrieval vs Query Rewriting vs HYDE (Source: Author)\n  * Hybrid search is almost always better than purely semantic or purely keyword-based search. For semantic search, I'd use cosine similarity nearest neighbor search with vector embeddings. And for semantic search, use BM25.\n  * **RRF:** You can choose multiple strategies to retrieve documents, and then use reciprocal rank fusion to combine them into one unified list!\n\n\n![Multi-Hop Retrieval](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide14-1024x576.jpeg)\nMulti-Hop Retrieval and Hybrid HyDE Search (Illustrated by Author)\n  * **Multi-Hop Search** is an option to consider as well if you can afford additional latency. Here, you pass the retrieved documents back into the LLM to generate new queries, which are used to conduct additional searches on the database.\n\n\npython\n\n```\nclass MultiHopHyDESearch(dspy.Module):\n    def __init__(self, retriever):\n        self.generate_queries = dspy.ChainOfThought(QueryGeneration)\n        self.retriever = retriever\n    \n    def forward(self, query, n_hops=3):\n        results = []\n        \n        for hop in range(n_hops): # Notice we loop multiple times\n\n            # Generate optimized search queries\n            search_queries = self.generate_queries(\n                query=query, \n                previous_jokes=retrieved_jokes\n            )\n            \n            # Retrieve using both semantic and keyword search\n            semantic_results = self.retriever.semantic_search(\n                search_queries.semantic_query\n            )\n            bm25_results = self.retriever.bm25_search(\n                search_queries.bm25_query\n            )\n            \n            # Fuse results\n            hop_results = reciprocal_rank_fusion([\n                semantic_results, bm25_results\n            ])\n            results.extend(hop_results)\n        \n        return results\n```\n\n  * **Citations** : When asking LLM to generate responses from the retrieved documents, we can also ask the LLM to cite references to the documents it found useful. This allows the LLM to first generate a plan of how it's going to use the retrieved content.\n  * **Memory:** If you are building a chatbot, it is important to figure out the question of memory. You can imagine Memory as a combination of Retrieval and Tool Calling. A well-known system is the Mem0 system. The LLM observes new data and calls tools to decide if it needs to add or modify its existing memories. During question-answering, it retrieves relevant memories using RAG to generate answers.\n\n\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-05-at-00.18.12@2x-1024x526.png)\nThe Mem0 architecture (Source: [The Mem0 paper](https://arxiv.org/pdf/2504.19413))\n## Best Practices and Production Considerations\nThis section is not directly about Context Engineering, but more about best practices to build LLM apps for production.\n> _Additionally, systems need to be evaluated with metrics and maintained with observability. Monitoring token usage, latency, and cost to output quality is a key consideration._\n### 1. Design Evaluation First\nBefore building features, decide how you'll measure success. This helps scope your application and guides optimization decisions.   \n\n![Hyperparameters impacting LLM outputs](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide8-1024x576.jpeg)\nA lot of parameters impact the quality of LLM's outputs (Illustrated by the author)\n  * If you can design verifiable or objective rewards, that's the best. (example: classification tasks where you have a validation dataset)\n  * If not, can you define functions that heuristically evaluate LLM responses for your use case? (example: number of times a specific chunk is retrieved given a question)\n  * If not, can you get humans to annotate your LLM's responses?\n  * If nothing works, use an LLM as a judge to evaluate responses. In most cases, you want to set your evaluation task as a comparison study, where the Judge receives multiple responses produced using different hyperparameters/prompts, and the judge must rank which ones are the best.\n\n\n![Evaluation of LLM apps](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/Slide9-1024x576.jpeg)\nA simple flowchart about evaluating LLM apps (Illustration by author)\n### 3. Use Structured Outputs Almost Everywhere\nAlways prefer structured outputs over free-form text. It makes your system more reliable and easier to debug. You can add validation and retries as well!\n### 4. Design for failure\nWhen designing prompts or dspy modules, make sure you always consider \"what happens if things go wrong?\"\nLike any good software, cutting down error states and failing with swagger is the ideal scenario.\n### 5. Monitor Everything\nDSpy integrates with MLflow to track:\n  * Individual prompts passed into the LLM and their responses\n  * Token usage and costs\n  * Latency per module\n  * Success/failure rates\n  * Model performance over time\n\n\nLangfuse, Logfire are equally great alternatives. \n## Outro\nContext engineering represents a paradigm shift from simple prompt engineering to building comprehensive and modular LLM applications. \nThe DSPy framework provides the tools and abstractions needed to implement these patterns systematically. As LLM capabilities continue to evolve, context engineering will become increasingly crucial for building applications that effectively leverage the power of large language models.\nTo watch the full video course on which this article is based, please visit this YouTube link.\nPlease accept cookies to access this content\nTo access the full GitHub repo, visit:\n<https://github.com/avbiswas/context-engineering-dspy>\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/CleanShot-2025-08-04-at-16.22.34@2x-1024x671.png)\n[Visit the Context Engineering repo for code access! ](https://github.com/avbiswas/context-engineering-dspy)\n## References\n**Author's YouTube channel** : <https://www.youtube.com/@avb_fj>\n**Author's Patreon** : [www.patreon.com/NeuralBreakdownwithAVB](https://www.patreon.com/c/NeuralBreakdownwithAVB)\n**Author's Twitter (X) account** : <https://x.com/neural_avb>\n**Full Context Engineering video course:**<https://youtu.be/5Bym0ffALaU>\n**Github Link:**<https://github.com/avbiswas/context-engineering-dspy>\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](https://assets.insightmediagroup.io/media/1787657599061_2dgnrr.jpeg)\nWritten By\n## [Avishek Biswas](https://towardsdatascience.com/author/neural.avb/)\n[See all from Avishek Biswas](https://towardsdatascience.com/author/neural.avb/)\n[Deep Dives](https://towardsdatascience.com/tag/deep-dives/)[Context Engineering](https://towardsdatascience.com/tag/context-engineering/)[Dspy](https://towardsdatascience.com/tag/dspy/)[Llm Applications](https://towardsdatascience.com/tag/llm-applications/)[Machine Learning](https://towardsdatascience.com/tag/machine-learning/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F&title=Context%20Engineering%20%E2%80%94%20A%20Comprehensive%20Hands-On%20Tutorial%20with%20DSPy)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F&title=Context%20Engineering%20%E2%80%94%20A%20Comprehensive%20Hands-On%20Tutorial%20with%20DSPy)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Fcontext-engineering-a-comprehensive-hands-on-tutorial-with-dspy%2F&text=Context%20Engineering%20%E2%80%94%20A%20Comprehensive%20Hands-On%20Tutorial%20with%20DSPy)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/06/19GG0bJ2ZhSMjsYhQpMqlfw.png)](https://towardsdatascience.com/pandas-indexes-and-headers-have-you-ever-been-confused-b5a11f143876/)\n## [Pandas Indexes And Headers, Have You Ever Been Confused?](https://towardsdatascience.com/pandas-indexes-and-headers-have-you-ever-been-confused-b5a11f143876/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nFrom single-level index and headers to multi-level, why and how?\n[Christopher Tao](https://towardsdatascience.com/author/christophertao/)\nJune 9, 20248 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2022/12/1Jvn7tyMX8OYGXVeSLn4U6Q.jpeg)](https://towardsdatascience.com/mathematical-optimization-heuristics-every-data-scientist-should-know-b26de0bd43e6/)\n## [Mathematical Optimization Heuristics Every Data Scientist Should Know](https://towardsdatascience.com/mathematical-optimization-heuristics-every-data-scientist-should-know-b26de0bd43e6/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nLocal search, genetic algorithms and more.\n[Hennie de Harder](https://towardsdatascience.com/author/hennie-de-harder/)\nDecember 9, 20229 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2023/08/1EosxuvAZXq3vDKnsSTPScg-scaled.jpeg)](https://towardsdatascience.com/increase-llama-2s-latency-and-throughput-performance-by-up-to-4x-23034d781b8c/)\n## [Increase Llama 2's Latency and Throughput Performance by Up to 4X](https://towardsdatascience.com/increase-llama-2s-latency-and-throughput-performance-by-up-to-4x-23034d781b8c/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nReal-world benchmarks for Llama-2 13B\n[Het Trivedi](https://towardsdatascience.com/author/het.trivedi05/)\nAugust 8, 20237 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2022/11/00PeGePMkelmI7RjP-scaled.jpg)](https://towardsdatascience.com/step-by-step-approach-of-building-data-pipelines-as-a-data-scientist-or-a-machine-learning-engineer-4b3e8da3bcd1/)\n## [Step-by-step Approach of Building Data Pipelines as a Data Scientist or a Machine Learning Engineer](https://towardsdatascience.com/step-by-step-approach-of-building-data-pipelines-as-a-data-scientist-or-a-machine-learning-engineer-4b3e8da3bcd1/)\n[Deep Learning](https://towardsdatascience.com/category/artificial-intelligence/deep-learning/)\nLearn how to build interesting pipelines in the field of machine learning to develop a system that can perform AI capabilities\n[Suhas Maddali](https://towardsdatascience.com/author/suhas-maddali007/)\nNovember 2, 20228 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/09/download-4.png)](https://towardsdatascience.com/what-is-universality-in-llm-and-how-to-find-universal-neurons/)\n## [What is Universality in LLMs? How to Find Universal Neurons](https://towardsdatascience.com/what-is-universality-in-llm-and-how-to-find-universal-neurons/)\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\nHow independently trained transformers form same the neurons\n[Shuyang Xiang](https://towardsdatascience.com/author/vanillaxiangshuyang/)\nSeptember 2, 20255 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2021/03/1M5pCg5RLzy1t6gEYVbnPRg-scaled.jpeg)](https://towardsdatascience.com/fit-vs-predict-vs-fit-predict-in-python-scikit-learn-f15a34a8d39f/)\n## [fit() vs predict() vs fit_predict() in Python scikit-learn](https://towardsdatascience.com/fit-vs-predict-vs-fit-predict-in-python-scikit-learn-f15a34a8d39f/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nWhat's the difference between fit, predict and fit_predict methods in sklearn\n[Giorgos Myrianthous](https://towardsdatascience.com/author/gmyrianthous/)\nMarch 9, 20215 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/03/1Q8vye7yAsxeiGwlnDbpQJQ-scaled.jpeg)](https://towardsdatascience.com/simulated-data-real-learnings-power-analysis-652045eeae22/)\n## [Simulated Data, Real Learnings : Power Analysis](https://towardsdatascience.com/simulated-data-real-learnings-power-analysis-652045eeae22/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nPart 2 - Experimental Power Analysis\n[Jarom Hulet](https://towardsdatascience.com/author/jarom.hulet/)\nMarch 26, 202411 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/08/image-269.jpg)](https://towardsdatascience.com/how-to-develop-powerf-interal-llm-benchmarks/)\n## [How to Develop Powerful Internal LLM Benchmarks](https://towardsdatascience.com/how-to-develop-powerf-interal-llm-benchmarks/)\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\nLearn how to compare LLMs using your own internal benchmark\n[Eivind Kjosbakken](https://towardsdatascience.com/author/oieivind/)\nAugust 26, 20256 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/01/0DRYPGuUHflPZ0a1R-scaled.jpg)](https://towardsdatascience.com/how-to-use-causal-inference-when-a-b-testing-is-not-possible-c87c1252724a/)\n## [How to use Causal Inference when A/B testing is not available](https://towardsdatascience.com/how-to-use-causal-inference-when-a-b-testing-is-not-possible-c87c1252724a/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nEvaluating ad targeting product using causal inference: propensity score matching!\n[Harry Lu](https://towardsdatascience.com/author/chinheng.h.lu/)\nJanuary 8, 20247 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n",
  "body_base_path": "attachments/fcbb8e52d971/pages/",
  "body_markdown_path": "attachments/fcbb8e52d971/pages/archived-body.md",
  "local_html": "attachments/fcbb8e52d971/pages/archived-body.html",
  "body_source": "prior_capture"
}