<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged dspy</title>
<link>https://coderlegion.com/tag/dspy</link>
<description>Powered by Question2Answer</description>
<item>
<title>Day 1: I'm Done Writing Prompts by Hand — Meet DSPy</title>
<link>https://coderlegion.com/19118/day-1-im-done-writing-prompts-by-hand-meet-dspy</link>
<description>&lt;p&gt;Let me paint you a picture that probably feels familiar.&lt;/p&gt;
&lt;p&gt;You spend 45 minutes crafting the &lt;em&gt;perfect&lt;/em&gt; prompt. You test it. It works. You ship it. Two days later your colleague tries it with slightly different input and... it falls apart completely. So you're back at it — tweaking a word here, rearranging a sentence there, re-testing, repeat.&lt;/p&gt;
&lt;p&gt;Sound familiar? I've been there more times than I'd like to admit. And it turns out there's a name for this exhausting loop: &lt;strong&gt;prompt engineering&lt;/strong&gt;. More importantly, there's now a smarter way to escape it.&lt;/p&gt;
&lt;p&gt;I'm kicking off a series where I read and share insights from &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.manning.com/books/building-llm-applications-with-dspy&quot;&gt;Building LLM Applications with DSPy&lt;/a&gt;&lt;/strong&gt; by &lt;em&gt;Serj Smorodinsky and William Brett Kennedy&lt;/em&gt; (Manning Publications, MEAP V01, 2026). One chapter a day, straight to the point. Let's get into Day 1.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-with-prompt-engineering-be-honest-you-know-it-&quot;&gt;The Problem with Prompt Engineering (Be Honest, You Know It)&lt;/h3&gt;
&lt;p&gt;Prompt engineering is essentially &lt;em&gt;trial and error dressed up as a skill&lt;/em&gt;. You're manually rephrasing the same instruction hoping the LLM eventually &quot;gets it.&quot; The authors describe it well — even prompts that &lt;em&gt;seem equivalent&lt;/em&gt; can produce wildly different results, and once your prompts grow to dozens of lines, you lose track of &lt;em&gt;why&lt;/em&gt; each phrase is even there.&lt;/p&gt;
&lt;p&gt;Here's what makes this particularly painful in production systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every time you switch LLMs (GPT-4 → Claude → Gemini), you start over&lt;/li&gt;
&lt;li&gt;You can't easily track &lt;em&gt;which&lt;/em&gt; prompt variant performed best&lt;/li&gt;
&lt;li&gt;Complex apps with multiple LLM calls compound errors fast — one bad prompt poisons the whole pipeline&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;enter-dspy-prompt-programming-not-prompt-engineering&quot;&gt;Enter DSPy: Prompt &lt;em&gt;Programming&lt;/em&gt;, Not Prompt &lt;em&gt;Engineering&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;DSPy (short for &lt;strong&gt;Declarative Self-improving Python&lt;/strong&gt;, pronounced &lt;em&gt;dee-ess-pie&lt;/em&gt;) flips the script. Instead of writing prompts, you write &lt;strong&gt;code that describes what you want&lt;/strong&gt;. DSPy handles generating, evaluating, and optimizing the actual prompts automatically.&lt;/p&gt;
&lt;p&gt;The book frames this as a natural evolution — similar to how we no longer hand-write assembly code when building apps in Python. We work at the right level of abstraction. Prompt programming is that next level for LLM apps.&lt;/p&gt;
&lt;p&gt;Here's what that looks like in practice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import dspy

lm = dspy.LM(&quot;openai/gpt-4o-mini&quot;, api_key=OPENAI_API_KEY)
dspy.settings.configure(lm=lm)

predictor = dspy.Predict(&quot;question, context -&amp;gt; answer, confidence&quot;)
prediction = predictor(question=&quot;What is the capital of France?&quot;, context=&quot;&quot;)

print(prediction.answer, prediction.confidence)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No prompt string in sight. You declare &lt;em&gt;what&lt;/em&gt; you want (a question answered with a confidence score), and DSPy figures out &lt;em&gt;how&lt;/em&gt; to ask the LLM for it. Clean, readable, testable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-dspy-actually-does-under-the-hood&quot;&gt;What DSPy Actually Does Under the Hood&lt;/h3&gt;
&lt;p&gt;This is the part I found genuinely impressive. DSPy doesn't just wrap your LLM call — it &lt;strong&gt;optimizes&lt;/strong&gt; your prompts systematically. Given a task and some evaluation criteria, it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generates many candidate prompts automatically&lt;/li&gt;
&lt;li&gt;Evaluates each one against your metric&lt;/li&gt;
&lt;li&gt;Uses techniques like hill climbing, genetic algorithms, and Bayesian optimization to find the best performer&lt;/li&gt;
&lt;li&gt;Iterates until it converges on something strong&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The authors note that experiments pitting DSPy against &lt;em&gt;professional prompt engineers&lt;/em&gt; found DSPy produced stronger prompts in &lt;em&gt;less time&lt;/em&gt;. That's not a small claim.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-recommended-dev-workflow&quot;&gt;The Recommended Dev Workflow&lt;/h3&gt;
&lt;p&gt;The book lays out a clean three-step process:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Baseline → Evaluate → Optimize&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You start by building a simple working version of your app. Then you evaluate it rigorously (DSPy gives you the tools for this). Then you let DSPy optimize the prompts across your full pipeline — not just one call, but &lt;em&gt;all of them together&lt;/em&gt;, tuned for the best combined outcome.&lt;/p&gt;
&lt;p&gt;This matters a lot in agentic systems, where LLM calls chain together and one weak prompt cascades into a messy failure downstream.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-should-you-actually-use-dspy-&quot;&gt;When Should You Actually Use DSPy?&lt;/h3&gt;
&lt;p&gt;DSPy shines when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have &lt;strong&gt;complex prompts&lt;/strong&gt; or &lt;strong&gt;multiple LLM calls&lt;/strong&gt; in a pipeline&lt;/li&gt;
&lt;li&gt;You need prompts to be reliable and production-grade&lt;/li&gt;
&lt;li&gt;You're experimenting with different LLMs and don't want to rewrite everything&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For casual, one-off LLM interactions? Direct prompting is probably fine. DSPy has a learning curve — it's a framework, not a magic wand. But once you're past the basics, the payoff is real.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;my-takeaway-from-chapter-1&quot;&gt;My Takeaway from Chapter 1&lt;/h3&gt;
&lt;p&gt;What struck me most isn't the automation — it's the &lt;strong&gt;mindset shift&lt;/strong&gt;. Prompt engineering is fundamentally reactive: you write something, see what breaks, fix it. Prompt programming is systematic: you define what &quot;good&quot; means, and the framework finds the path there.&lt;/p&gt;
&lt;p&gt;As someone who builds production AI systems, that distinction matters enormously. Less time debugging prompts means more time shipping features.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What's Next&lt;/h3&gt;
&lt;p&gt;Tomorrow I'm covering &lt;strong&gt;Chapter 2: Basic Prompting and DSPy&lt;/strong&gt; — where we get into the actual anatomy of a well-formed prompt and build our first real DSPy application. If you've ever wondered what goes into those multi-section prompt templates, that chapter breaks it down nicely.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt; &lt;strong&gt;Source:&lt;/strong&gt; &lt;em&gt;Building LLM Applications with DSPy&lt;/em&gt; by Serj Smorodinsky and William Brett Kennedy — Manning Publications, MEAP V01, 2026. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.manning.com/books/building-llm-applications-with-dspy&quot;&gt;manning.com/books/building-llm-applications-with-dspy&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;All concepts, examples, and code snippets referenced in this post are drawn from the book above. This series is a reader's journal, not a reproduction — pick up the book for the full depth.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Are you still hand-writing your prompts? Drop a comment — I'm curious how many of you have tried DSPy already and what your experience was like.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19118/day-1-im-done-writing-prompts-by-hand-meet-dspy</guid>
<pubDate>Wed, 27 May 2026 19:29:25 +0000</pubDate>
</item>
<item>
<title>Leveling Up LLM Game Development with DSPy</title>
<link>https://coderlegion.com/2827/leveling-up-llm-game-development-with-dspy</link>
<description>&lt;p&gt;After completing &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/concurrency-vs-parallelism-achieving-scalability-with-processpoolexecutor-1n7n&quot;&gt;the series on asynchronous programming&lt;/a&gt;, I needed some time off to deal with the stress in life. During the break, I decided to explore &lt;a rel=&quot;nofollow&quot; href=&quot;https://dspy.ai/&quot;&gt;DSPy&lt;/a&gt; hands-on. The library claims to help build LLM applications programmatically. Let’s see if we can avoid prompt engineering by revisiting &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/i-built-a-word-guessing-game-with-llm-357&quot;&gt;the word-guessing game we discussed previously&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*aWgwMrlbuo97Kajv8KHkKQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Image generated by copilot on the topic&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-problem-with-traditional-prompts&quot;&gt;The Problem with Traditional Prompts&lt;/h4&gt;
&lt;p&gt;Previously, the game logic was almost completely defined by the prompt we sent to the LLM chatbot. While the game worked almost perfectly with &lt;a rel=&quot;nofollow&quot; href=&quot;https://gemini.google.com/&quot;&gt;Gemini&lt;/a&gt; through &lt;a rel=&quot;nofollow&quot; href=&quot;https://openrouter.ai/&quot;&gt;OpenRouter&lt;/a&gt;, I quickly exhausted the limit. Fortunately, the code worked with the compatible API offered by &lt;a rel=&quot;nofollow&quot; href=&quot;https://jan.ai/&quot;&gt;Jan.ai&lt;/a&gt;, a tool to manage LLM models locally. However, the game logic breaks whenever I change to a new model. Minor adjustments needed to be made for each model, like rephrasing parts or even adding to the prompt. Every seemingly insignificant change to the prompt may cause failure in parsing user input or responding in proper format.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*nV3LJC2uhitrkB2L&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Om Kamath on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The chatbot from DSPy’s website says&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;DSPy is a framework for building modular AI software, focusing on optimizing language model prompts and weights. It uses structured code instead of string prompts, enabling fast iteration and reliable software. DSPy abstracts prompt engineering and model fine-tuning, compiling AI programs into effective prompts and weights for tasks like classifiers and RAG pipelines, enhancing portability across models.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If I understand correctly, we can probably avoid writing precise prompts while building a LLM application through DSPy. Since we already build our game purely with prompts, it would be interesting to see how it compares when coded without.&lt;/p&gt;
&lt;p&gt;The goal of this exercise is to explore, so the proposed solution may not be the most efficient. As the use of DSPy changes how logic is represented, it should also improve the gameplay experience.&lt;/p&gt;
&lt;h4 id=&quot;from-prompt-templates-to-dspy-modules&quot;&gt;From Prompt Templates to DSPy Modules&lt;/h4&gt;
&lt;p&gt;While going through the documentation and tutorial, I realize DSPy encourages developers to define behaviour in smaller units. Instead of lumping everything together into a long prompt, we break the task into smaller sub-tasks. For instance, in our word-guessing game, once the game is started, we used one single prompt to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Classify if the user input is a valid question or an answer&lt;/li&gt;
&lt;li&gt;Parse the question, and prepare a response to the question&lt;/li&gt;
&lt;li&gt;Check if the submitted guess matches the answer&lt;/li&gt;
&lt;li&gt;Generate an error message if the input is invalid&lt;/li&gt;
&lt;li&gt;Lastly, format the answer into a format our program expects&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Recently, I started to hear a lot of discussions on how breaking tasks into smaller units may help to improve the quality of response. DSPy achieves this through the use of &lt;a rel=&quot;nofollow&quot; href=&quot;https://dspy.ai/learn/programming/signatures/&quot;&gt;signatures&lt;/a&gt;, with a shorthand notation, it could be as simple as question -&amp;gt; answer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*iYFmz2Yxqr0nHPQ2&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Susan Holt Simpson on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Instead of writing a prompt, I see signatures as a way to define a task in a more declarative fashion. Through the class-based definition, it is possible to supply more meta information to the task through a docstring, or the desc property of input and output fields. For example, we can break the original prompt into 2 parts, first on classification:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Classifier(dspy.Signature):
    attempt: str = dspy.InputField(desc=&quot;The user input in a 20 questions game&quot;)
    category: Literal[&quot;question&quot;, &quot;invalid&quot;] = dspy.OutputField(
        desc=&quot;The type of attempt supplied by the user in a 20 questions word guessing game&quot;
    )
    response: str = dspy.OutputField(
        desc=&quot;The response to be shown to user for invalid input&quot;
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing I like about the use of signature is that it implicitly does the data formatting work in the background. In the previous incarnation, I needed to explicitly include the desired data template in the prompt, and for some reason every model responds to the template differently.&lt;/p&gt;
&lt;p&gt;Then we have the part where we parse and respond to the questions our players submit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Question(dspy.Signature):
    question: str = dspy.InputField(
        desc=&quot;The user's question or a guess in a 20 questions game.&quot;
    )
    answer: str = dspy.InputField(
        desc=&quot;The noun user is attempting to guess in a 20 questions game.&quot;
    )
    response: str = dspy.OutputField(
        desc=&quot;The yes/no answer to the user's question in a sentence with the answer word replaced by 'the answer' whenever applicable&quot;
    )
    result: bool = dspy.OutputField(desc=&quot;The true/false response to the user question&quot;)
    found: bool = dspy.OutputField(
        desc=&quot;True only if this is a guess and it matches the answer&quot;
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I eventually realized it is easier to just assume players would guess, by asking questions “is the answer XYZ?”. In this reimplementation, I am testing it with a locally hosted LLM, so the simpler setup should work better.&lt;/p&gt;
&lt;p&gt;Lastly, I needed a welcome speech, as well as an answer generator. For that, we define an output only signature as shown below,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class NewGame(dspy.Signature):
    speech: str = dspy.OutputField(
        desc=&quot;The elaborative welcome text for a word guessing game called GuessMe, with gameplay explanation adapted from the game 20 questions without the question limit.&quot;
    )
    answer: str = dspy.OutputField(
        desc=&quot;The chosen random noun as the answer for a 20 questions game session&quot;
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each signature roughly correspond to one prompt. So instead of 2 long ones, we now not only have one more, and it seems like we are defining more things now (though managed by DSPy in the background).&lt;/p&gt;
&lt;p&gt;Let’s compare the original new game prompt&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I want to welcome users to play a word guessing game called GuessMe. Please craft a welcome message, and quickly explain that the user can either ask a yes no question, or attempt to answer. Then, pick a random noun from the dictionary as the answer to the game. Construct a JSON following the template below and fill the placeholder accordingly,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;{&quot;message&quot;: , &quot;type&quot;: &quot;answer&quot;, &quot;response&quot;: }&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Return only the JSON without comments, do not include answer in welcome message.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Notice how it was written as an instruction to the language model. On the other hand, in the NewGame signature, it was written such that we only explain what each field expects. When we reduce the scope of each definition, it also means no more trivial adjustments when changing models. Ideally, that is.&lt;/p&gt;
&lt;p&gt;There are other pros to the approach, but I am not going through all of them as they are not immediately obvious in our toy example. Let’s move on to turning the signature to work.&lt;/p&gt;
&lt;p&gt;Declaring a signature is like writing a prompt template, to be populated with user input whenever applicable. The composition, and response handling work, would be done by a DSPy module. Let’s start with the easiest task, the new game setup. We could instantiate a new module by passing the signature class name to the dspy.Predict built-in module, as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module_new = dspy.Predict(NewGame) # instatiate a module to start a new game
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then to start a new game, we call&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;game = module_new()
game.speech # the welcome speech explaining the game rule
game.answer # the secret word
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we are not expecting an input, so module_new callable does not take any parameter. However, when we pass user input to the classifier&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module_classifier = dspy.ChainOfThought(Classifier)
input_type = module_classifier(attempt=user_input, answer=game.answer)
input_type.category # either &quot;question&quot; or &quot;invalid&quot;
input_type.response # some error message if category is &quot;invalid&quot;
input_type.reasoning # some additional insight on how output fieldss are generated
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like dspy.Predict, dspy.ChainOfThought is also a built-in module. It encourages the language model to perform some inferencing before generating a proper response, therefore improving the output quality. Practically, it also helps with debugging and can provide valuable insight if we log the reasoning field value.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*CzJO7SlJQ_YNPNIaICJvjQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;A playthrough with reasoning shown&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It seems like we have everything ready to assemble the final application. However, there’s one thing we need to address. As shown previously, the answer gets spoiled easily when we switch to a smaller model. Thankfully, there’s a way to discourage such behaviour, which is essential in improving the gameplay. Let’s write a reward function to score the output, so it does not return the answer in response ever.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def do_not_spoil(answer: str) -&amp;gt; Callable[..., float]:
    def inner(_args, pred: dspy.Prediction) -&amp;gt; float:
        DEBUG and print(pred) # type: ignore

        if hasattr(pred, &quot;found&quot;) and pred.found:
            return 1.0

        return (
            1.0
            if answer.lower() not in pred.response.lower()
            and len(pred.response.split()) &amp;gt; 1
            else 0.0
        )

    return inner
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I know we &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/the-versatility-of-call-a-python-developers-secret-weapon-4akd&quot;&gt;discussed on __call__ dunder&lt;/a&gt;, but I opt for a closure for simplicity. So if the response has more than a word and does not contain the answer, we return a rank of 1.0, or 0 otherwise. To encourage that behaviour, we use dspy.BestOfN as follows,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module_question = dspy.BestOfN(
    module=dspy.ChainOfThought(Question),
    N=10,
    reward_fn=do_not_spoil(game.answer),
    threshold=1.0,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the supplied model will be executed with different temperature values, and the first prediction that returns the threshold ranking of 1.0 according to do_not_spoil will be returned.&lt;/p&gt;
&lt;p&gt;Lastly, we initialize the set-up of the language model. Since the beginning we only work with OpenAI compatible API. As DSPy wraps liteLLM, we need to configure the model &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.litellm.ai/docs/providers/openai#using-openai-proxy-with-litellm&quot;&gt;according to the documentation&lt;/a&gt;, i.e. prefixing openai/ provider to the desired model name&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dspy.configure(
    lm=dspy.LM(
        f&quot;openai/gemma2:9b&quot;,
        api_base=&quot;https://localhost:1337/v1&quot;,
        api_key=&quot;&quot;,
        temperature=1.0,
        cache=False,
    )
) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are skipping caching as this is a game, so regeneration of responses is fine.&lt;/p&gt;
&lt;p&gt;The complete implementation &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/guessme-llm/blob/main/src/guessme/cli.py&quot;&gt;can be found on GitHub&lt;/a&gt;, in the same repository as before. It is implemented as a simple command line interface (CLI) application, so just run it with uv run python ./src/guessme/cli.py after configuring the .env as instructed in the README. Prefix the environment variable declaration DEBUG=True to read the reasoning to each response.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*f_pLWlhtHEqw9FbDvPNVcQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;An example playthrough with Gemma2:9b&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I only tested with some of the available models that my GPU is capable of running, namely gemma2:9b (with reduced context length and GPU layers), deepseek-r1:7b and cogito-v1:8b. So far only the gemma2 gave reasonable playing experience, and I managed to finish a game (as shown in the screenshot above).&lt;/p&gt;
&lt;p&gt;The parsing of questions has proved to be problematic for smaller models, even though we simplified the scope. Apart from gemma2, none of the other tested models managed to properly parse the question and set the found (indicating the answer is found) and result (indicate yes/no response to the question) flag properly.&lt;/p&gt;
&lt;h4 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*ELK5Le7qIN-JsMrd&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Hitesh Choudhary on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To answer the question we raised in the opening, we did manage to eliminate prompts in this reimplementation. While doing so, we managed to get a decent gameplay experience with at least one locally hosted language model. I would consider this a win (no pun intended). The exercise proves that with clever engineering, it is possible to build usable applications with smaller models.&lt;/p&gt;
&lt;p&gt;Though we managed to build our application without writing a single line of prompt, the adoption of this declarative technique remained to be observed. Vibe-coding is gaining a lot of traction these days, and everyone seems to be still deciding whether it is a good thing. The key to vibe-coding lies in producing clear and precise prompts, and prompt engineering is definitely staying for a bit. However, I still hope to explore further into DSPy, as I really like the declarative design.&lt;/p&gt;
&lt;p&gt;Besides saving us from writing (and adjusting) prompts, I like how we could encourage response to fulfil specific criteria through a reward function. My attempt to prevent spoilers with smaller models with prompts in the response previously was painful and yet unsuccessful. In the current iteration, it worked immediately. I hope to explore further into more LLM application building in future, and would definitely keep an eye on the library. I am particularly interested in the composability aspect of the library, where multiple modules can be assembled together, forming a pipeline (imagine composing the classifier and question parser one after another). This should open the possibility of more complex use-cases, and the simplicity leads to a more maintainable code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*3h6S_xvwOtHtOCTT&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Clem Onojeghuo on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I started the project not too long after concluding the asynchronous programming series, and was stuck for a bit with liteLLM because it wouldn’t stop bugging me for missing provider in the model name. At the time, I was in the middle of a multi-stage job interviews (it was my only application that was progressing at the time) and I got quite anxious throughout. Hopefully I am doing decent enough as I progressed into the final stage, though it was also rather stressful as my unemployment persists. Let’s hope things are turning out to be in my favor.&lt;/p&gt;
&lt;p&gt;Thanks for reading this far, and I shall write again, next week.&lt;/p&gt;
&lt;p&gt;This article is a collaboration: I wrote the draft, and Gemini provided editorial assistance. The story and voice are mine. The code examples are unaltered. For project collaborations and job opportunities, contact me via &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/&quot;&gt;Medium&lt;/a&gt; or &lt;a rel=&quot;nofollow&quot; href=&quot;https://linkedin.com/in/jeffrey04&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Natural Language Processing</category>
<guid isPermaLink="true">https://coderlegion.com/2827/leveling-up-llm-game-development-with-dspy</guid>
<pubDate>Sun, 27 Apr 2025 19:50:20 +0000</pubDate>
</item>
</channel>
</rss>