PageSourceSearch

https://mirascope.com/assets/engineers-should-handle-prompting-llms-DqIQvGad.js

js mirascope.com collected 2026-10-03 22:58:52 UTC 29,476 bytes, 552 lines download raw bytes

1const e={frontmatter:{title:"Engineers Should Handle Prompting LLMs (and Prompts Should Live in Your Codebase)",description:"Many prompt engineering tools neither follow developer best practices, nor manage prompts with their LLM calls. We created Mirascope to address this.",date:"2024-03-29",readTime:"20 min read",author:"William Bakst"},tableOfContents:[{id:"our-frustrations-with-developer-tools-for-prompt-engineering",content:"Our Frustrations with Developer Tools for Prompt Engineering",level:2},{id:"how-mirascope-makes-prompt-engineering-intuitive-and-scalable",content:"How Mirascope Makes Prompt Engineering Intuitive and Scalable",level:2},{id:"code-like-you-already-code-with-pythonic-simplicity",content:"Code Like You Already Code, with Pythonic Simplicity",level:3},{id:"built-in-data-validation-for-error-free-prompting",content:"Built-in Data Validation for Error-Free Prompting",level:3},{id:"simplify-llm-interactions-with-decorators-and-integrations",content:"Simplify LLM Interactions with Decorators and Integrations",level:3},{id:"expand-llm-capabilities-with-tools",content:"Expand LLM Capabilities with Tools",level:3},{id:"extract-structured-data-from-llm-generated-text",content:"Extract Structured Data from LLM-Generated Text",level:3},{id:"facilitate-your-prompt-workflows-with-cli-and-ide-support",content:"Facilitate Your Prompt Workflows with CLI and IDE Support",level:3}],content:`We’ve seen many discussions around Large Language Model (LLM) software development allude to a workflow where prompts live apart from LLM calls and are managed by multiple stakeholders, including non-engineers. In fact, many popular LLM development frameworks and libraries are built in a way that _requires_ prompts to be managed separately from their calls.
2
3We think this is an unnecessarily cumbersome approach that’s not scalable for complex, production-grade LLM software development.
4
5Here’s why: for anyone developing production-grade LLM apps, prompts that include code will necessarily be a part of your engineering workflow. Therefore, separating prompts from the rest of your codebase, especially from their API calls, means you’re splitting that workflow into different, independent parts.
6
7Separating concerns and assigning different roles to manage each may _seem_ to bring certain efficiencies, for example, easing collaboration between tech and non-tech roles. But **it introduces fundamental complexity that can disrupt the engineering process.** For instance, introducing a change in one place-like adding a new key-value pair to an input for an LLM call-means hunting down that change manually. And then, you will likely _still_ not catch all the errors.
8
9Don’t get us wrong. If your prompts are purely text or have very minimal code, then managing them separately from your calls may not have much of an impact. And there are legitimate examples of prompts with minimal or no code, like prompts for ChatGPT. In such cases, managing prompts separately from calls can make sense.
10
11But any enterprise-grade LLM apps require sophisticated prompts, **which means you’ll end up writing code for such prompts anyway.**
12
13In fact, **trying to write out that logic in text would be even more complicated.** In our view, code makes prompting both efficient and manageable, as well as the purview of engineers.
14
15Below, we outline how we arrived at this truth, and how the solution we’ve developed (a Python-based LLM development library) helps developers manage prompts in the codebase easily and efficiently, making, in our experience, LLM app development faster and more enjoyable.
16
17## Our Frustrations with Developer Tools for Prompt Engineering
18
19Our view on prompting started as we were using an early version of the OpenAI SDK to build out interpretable machine learning tools at a previous company. This was the standard OpenAI API for accessing GPT model functionalities.
20
21Back then we didn’t have the benefit of any useful helper libraries, so we wrote all the API code ourselves. This amounted to writing lots of boilerplate to accomplish what seemed like simple tasks. For example, automatically extracting the model configuration (such as constraints) from just the name of the features in a given dataset. This required many prompt iterations and it was a pain to evaluate them.
22
23It was around that time that we began asking ourselves: why aren’t there better [developer tools in the prompt engineering
24space](/blog/prompt-engineering-tools)? Is it because people are bringing experimental stuff into production too quickly? Or simply because the space is so new?
25
26The more we worked to develop our LLM applications, the more it was clear that from a software engineer's perspective, the **separation of prompt management from the calls was fundamentally flawed.** It made the actual engineering slow, cumbersome and arguably more error prone. It was almost as if current tools weren't built around developer best practices but rather around Jupyter notebook best practices (if there even is such a thing).
27
28Beyond that, we noticed some other issues:
29
30- Our prompts became unmanageable past two versions. We weren’t using a prompt management workflow back then, so implementing changes was a manual process. We started telling colleagues not to touch the code because it might break a function somewhere else.
31- A lot of libraries tried to offer functionality for as many use cases as possible, sometimes making you feel dependent on them. They required y
31ou to do things their way, or you’d have to wait for them to catch up with new features from the LLMs.
32
33All this led us to rethink how prompts should be managed to make developers’ lives easier. In the end, these frustrations boiled over into us wanting to build our own library that approached LLM development in a developer-first way to make LLM app development faster and more enjoyable. This ultimately became [Mirascope](https://github.com/mirascope/mirascope).
34
35## How Mirascope Makes Prompt Engineering Intuitive and Scalable
36
37For us, prompt engineering boils down to the relationship between the prompt and the API call. Mirascope represents what we feel is a best-in-class approach for generating that prompt, taking the LLM response, and tracking all aspects of that flow.
38
39As developers, we want to focus on innovation and creativity, rather than on managing and troubleshooting underlying processes.
40
41To that end, we designed Mirascope with the following features and capabilities to make your prompting more efficient, simpler, and scalable.
42
43### Code Like You Already Code, with Pythonic Simplicity
44
45It was important to us to be able to just code in Python, without having to learn superfluous abstractions or extra, fancy structures that make development more cumbersome than it needs to be. So we designed Mirascope to do just that.
46
47For instance, we don’t make you implement directed acyclic graphs in the context of sequencing function calls. We provide code that’s eminently readable, lightweight, and maintainable.
48
49An example of this is our [\`prompt_template\`](/docs/v1/learn/prompts/#prompt-templates-messages) decorator, which encapsulates as much logic within the prompt as feasible.
50
51Within a decorated function, the return value can be used as the prompt template. The following example requests book recommendations based on particular genre:
52
53\`\`\`python
54from mirascope.core import prompt_template
55
56@prompt_template()
57def book_recommendation_prompt(genre: str) -> str:
58    return f"Recommend a {genre} book"
59
60prompt = book_recommendation_prompt("fantasy")
61print(prompt)
62
63# > [BaseMessageParam(role='user', content='Recommend a fantasy book')]
64
65\`\`\`
66
67You can also provide a string template directly as an argument of the decorator where the function arguments will be injected into the template automatically:
68
69\`\`\`python
70from mirascope.core import prompt_template
71
72@prompt_template("Recommend a {genre} book")
73def book_recommendation_prompt(genre: str): ...
74
75prompt = book_recommendation_prompt("fantasy")
76print(prompt)
77
78# > [BaseMessageParam(role='user', content='Recommend a fantasy book')]
79
80\`\`\`
81
82By default, Mirascope's \`prompt_template\` treats the prompt message as a single user message in order to simplify initial use and implementation for straightforward scenarios.
83
84But you may want to add more context to prompts in the form of different roles, such as system, user, or assistant roles to generate more nuanced responses, as shown here:
85
86\`\`\`python
87from mirascope.core import Messages, prompt_template
88
89@prompt_template()
90def book_recommendation_prompt(genre: str) -> Messages.Type:
91    return [
92        Messages.System("You are a librarian"),
93        Messages.User(f"Recommend a {genre} book"),
94    ]
95
96prompt = book_recommendation_prompt("fantasy")
97print(prompt)
98
99# > [
100
101#     BaseMessageParam(role='system', content='You are a librarian'),
102
103#     BaseMessageParam(role='user', content='Recommend a fantasy book'),
104
105#   ]
106
107\`\`\`
108
109Now running this prompt template will automatically parse and structure the prompt as a list of \`BaseMessageParam\` message objects that work across all supported providers.
110
111You can also use all caps keywords in a string template for the same functionality:
112
113\`\`\`python
114from mirascope.core import prompt_template
115
116@prompt_template(
117    """
118    SYSTEM: You are a librarian
119    USER: Recommend a {genre} book
120    """
121)
122def book_recommendation_prompt(genre: str): ...
123
124prompt = book_recommendation_prompt("fantasy")
125print(prompt)
126
127# > [
128
129#     BaseMessageParam(role='system', content='You are a librarian'),
130
131#     BaseMessageParam(role='user', content='Recommend a fantasy book'),
132
133#   ]
134
135\`\`\`
136
137We also provide decorators for turning prompt templates into actual calls to an LLM API. This further reduces the boilerplate needed to write LLM-powered functions:
138
139\`\`\`python
140from mirascope.core import openai
141
142@openai.call("gpt-4o-mini")
143def recommend_book(genre: str) -> str:
144    return f"Recommend a {genre} book"
145
146response = recommend_book("fantasy")
147print(response.content)
148
149# > Sure! I'd be happy to recommend...
150
151\`\`\`
152
153In the above example, we've directly decorated the function, which means the function is now tied to a specific provider. As before, you can always instead use the \`prompt_template\` decorator to write provider-agnostic prompts, which you can then use with the decorator for any supported provider:
154
155\`\`\`python
156from mirascope.core import prompt_template
157
158@prompt_template(
159    """
160    SYSTEM: You are a librarian
161    USER: Recommend a {genre} book
162    """
163)
164def book_recommendation_prompt(genre: str): ...
165
166# OpenAI call and response
167
168openai_recommend_book = openai.call(
169    "gpt-4o-mini",
170)(book_recommendation_prompt)
171openai_response = openai_recommend_book("fantasy")
172print(openai_response.content)
173
174# Anthropic call and response
175
176anthropic_recommend_book = anthropic.call(
177    "claude-3-5-sonnet-20240620",
178)(book_recommendation_prompt)
179anthropic_response = anthropic_recommend_book("fantasy")
180print(anthropic_response.content)
181\`\`\`
182
183Writing prompts this way enables endless possibilities and customization, from few-shot prompting to chat interactions with an LLM and so much more.
184
185We also wanted to avoid introducing complexity where it's not absolutely necessary. For example, given a choice in how we would chain together components, we'd prefer relying on native Python rather than relying on something like the pipe moderator.
186
187The goal is to maintain an interface that remains as Pythonic as possible.
188
189**Note** : This isn’t to say there’s one “best” way to accomplish chaining, and you're certainly not required to do it exactly as we've shown. For instance, you can also have two separate calls where you pass the output of the first into the second as an argument (rather than calling it inside of the first call). It's not our recommendation since it breaks colocation, but you're free to do what you like. We just have opinionated guidelines, not requirements.
190
191Nevertheless, this approach to chaining encapsulates each step of the process within class methods, allowing for a clean and readable way to sequentially execute tasks that depend on the outcome of previous steps:
192
193\`\`\`python
194from mirascope.core import Messages, openai
195
196@openai.call("gpt-4o-mini")
197def select_chef(food_type: str) -> str:
198    return f"Name a chef who is good at cooking {food_type} food"
199
200@openai.call("gpt-4o-mini")
201def recommend_recipe(ingredient: str, food_type: str) -> str:
202    chef = select_chef(food_type)
203    return "Imagine you are {chef}. Recommend a {food_type} recipe using {ingredient}"
204
205response = recommend_recipe("apples", "japanese")
206print(response.content)
207
208# > Certainly! Here's a delightful Japanese-inspired recipe using apples: ...
209
210\`\`\`
211
212Finally, we show an example of how you can use Mirascope to do few-shot prompting, which provides the language model with a few examples (shots) to help it understand the task and generate better output.
213
214Below are three example sets of book recommendations for different topics to guide the model in understanding the format and type of response expected when asked to recommend books on a new topic, such as "coding."
215
216\`\`\`python
217from mirascope.core import prompt_template
218
219@prompt_template(
220    """
221    I'm looking for book recommendations on various topics. Here are some examples:
222
223    1. For a topic on 'space exploration', you might recommend:
224        - 'The Right Stuff' by Tom Wolfe
225        - 'Cosmos' by Carl Sagan
226
227    2. For a topic on 'artificial intelligence', you might recommend:
228        - 'Life 3.0' by Max Tegmark
229        - 'Superintelligence' by Nick Bostrom
230
231    3. For a topic on 'historical fiction', you might recommend:
232        - 'The Pillars of the Earth' by Ken Follett
233        - 'Wolf Hall' by Hilary Mantel
234
235    Can you recommend some books on {topic}?
236    """
237)
238def few_shot_book_recommendation_prompt(topic: str): ...
239\`\`\`
240
241Minimizing complexity lowers the learning curve. In Mirascope’s case, beyond knowing our library and Python, the only framework to learn is Pydantic for structuring and validating outputs.
242
243### Built-in Data Validation for Error-Free Prompting
244
245We find that high-quality prompts-ones that are type and error checked-lead to more accurate and useful LLM responses, and so data validation is at the heart of what we do.
246
247Automatic validation against predefined schemas is built into the fabric of our framework, allowing you to be more productive rather than having to chase down bugs or code your own basic error handling logic.
248
249For starters, by writing prompts as typed functions, you can easily ensure val
249id and well-formed inputs for you prompts by using Pydantic's [\`validate_call\`](https://docs.pydantic.dev/latest/concepts/validation_decorator/) decorator. We've also written our \`BaseCallResponse\` class as an extension of Pydantic's [\`BaseModel\`](https://docs.pydantic.dev/latest/api/base_model/), and we've implemented first-class support for structuring LLM outputs as BaseModel instances as well.
250
251All of this means:
252
253- Mirascope prompt templates can easily ensure the data is correctly typed before it's processed and sent over to the API, leading to cleaner, more maintainable code. Developers can focus more on the business logic specific to prompting rather than on writing boilerplate.
254- Pydantic easily serializes data both to and from JSON format, which simplifies the process of dumping and saving LLM responses (without any additional tooling).
255- Everything is engineered with editor support in mind, offering autocomplete and proper type hints.
256- It also lets developers define custom validation methods if needed, allowing them to enforce complex rules that go beyond type checks and basic validations.
257
258An example of using Pydantic for enforcing type validation (with graceful error handling) is shown below:
259
260\`\`\`python
261from pydantic import BaseModel, ValidationError
262from mirascope.core import openai
263
264class Book(BaseModel):
265    title: str
266    author: str
267
268@openai.call("gpt-4o-mini", response_model=Book)
269def recommend_book(genre: str) -> str:
270    return f"Recommend a {genre} book"
271
272try:
273    book = recommend_book("fantasy")
274    assert isinstance(book, Book)
275    print(book)
276except ValidationError as e:
277    print(e)
278    # > 1 validation error for Book
279
280    #  price
281
282    #    Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='standard', input_type=str]
283
284    #      For further information visit      https://errors.pydantic.dev/2.6/v/float_parsing
285
286\`\`\`
287
288You can also validate data in ways that are difficult if not impossible to code successfully, but that LLMs excel at, such as analyzing sentiment. For instance, you can add Pydantic’s [\`AfterValidator\`](/docs/v1/learn/response_models/#validation-and-error-handling) annotation to Mirascope’s extracted output as shown below:
289
290\`\`\`python
291from typing import Annotated, Literal
292
293from pydantic import AfterValidator, BaseModel, ValidationError
294from mirascope.core import openai
295
296@openai.call("gpt-4o-mini", response_model=Literal["happy", "sad"])
297def classify_sentiment(text: str) -> str:
298    return f"Is the following text happy or sad? {text}"
299
300def validate_happy(story: str) -> str:
301    """Confirm that the story is happy."""
302    assert classify_sentiment(story) == "happy", "Story wasn't happy"
303    return story
304
305@openai.call(
306    model="gpt-4o-mini",
307    response_model=Annotated[str, AfterValidator(validate_happy)],
308)
309def tell_sad_story() -> str:
310    return "Tell me a very sad story."
311
312try:
313    story = tell_sad_story()
314    print(story)
315except ValidationError as e:
316    print(e)
317    # > 1 validation error for Story
318
319    #   story
320
321    #     Assertion failed, Story wasn't happy. [type=assertion_error, input_value="Once upon a time, there ...er every waking moment.", input_type=str]
322
323    #       For further information visit https://errors.pydantic.dev/2.6/v/assertion_error
324
325\`\`\`
326
327### Simplify LLM Interactions with Decorators and Integrations
328
329We believe in freeing you from writing boilerplate to interact with APIs, we we made available a number of arguments for easily modifying and customizing how you use the underlying APIs.
330
331For example, you can set \`stream=True\` in the function decorator, which will generate a stream of chunks (and tools, which will be \`None\` if not using tools):
332
333\`\`\`python
334from mirascope.core import openai
335
336@openai.call("gpt-4o-mini", stream=True)
337def recommend_book(genre: str) -> str:
338    return f"Recommend a {genre} book"
339
340for chunk, _ in recommend_book("fantasy"):
341    print(chunk.content, end="", flush=True)
342\`\`\`
343
344You can also further configure the API call by setting the \`call_params\` argument, meaning that any time the decorated function is called it will use the correct, colocated call parameters:
345
346\`\`\`python
347from mirascope.core import openai
348
349@openai.call("gpt-4o-mini", call_params={"temperature": 0.7})
350def recommend_book(genre: str) -> str:
351    return f"Recommend a {genre} book"
352
353response = recommend_book("fantasy")
354print(response.content)
355\`\`\`
356
357If a provider has a custom endpoint you can call with their own API (for example, OpenAI compatibility), you can set the \`client\` argument in the decorator to use the custom client. This is true for providers such as [Ollama](https://ollama.com/), [Anyscale](https://www.anyscale.com/), [Together](https://www.together.ai/), [AzureOpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service), and others that support the OpenAI API through a proxy (as well as any other APIs such as Anthropic that have similar support).
358
359\`\`\`python
360from mirascope.core import openai
361from openai import OpenAI
362
363client = OpenAI(
364    base_url="http://localhost:11434/v1",
365    api_key="ollama",  # required, but unused
366
367)
368
369@openai.call("gpt-4o-mini", client=client)
370def recommend_book(genre: str) -> str:
371    return f"Recommend a {genre} book"
372
373response = recommend_book("fantasy")
374print(response.content)
375\`\`\`
376
377We also made sure that Mirascope works well with or integrates directly with tools such as Logfire, OpenTelemetry, HyperDX, Langfuse, and more for easily tracking machine learning experiments and visualizing data as well as improving prompt effectiveness through automated refinement and testing. All together these tools work together with Mirascope as [an alternative to LangChain](/blog/langchain-alternatives).
378
379Beyond OpenAI, Mirascope supports currently these [other LLM providers](/docs/v1/learn/calls):
380
381- Anthropic
382- Mistral
383- Google Gemini
384- Groq
385- Cohere
386- LiteLLM
387- Azure AI
388- AWS Bedrock
389
390We've made sure that every example in our documentation shows how to use each provider for easy copy-paste to try it out yourself.
391
392We consistently add support for new providers, so if there is a provider you want to see supported that isn't yet, let us know!
393
394If you want to switch to another model provider (like [Anthropic](/docs/v1/api/core/anthropic/call/) or [Mistral](/docs/v1/api/core/mistral/call/), for instance), you just need to change the decorator and the corresponding call parameters:
395
396\`\`\`python
397from mirascope.core import anthropic
398
399@anthropic.call("claude-3-5-sonnet-20240620")
400def recommend_book(genre: str) -> str:
401    return f"Recommend a {genre} book"
402
403response = recommend_book("fantasy")
404print(response.content)
405\`\`\`
406
407### Expand LLM Capabilities with Tools
408
409Although LLMs are known mostly for text generation, you can provide them with specific tools (also known as [function calling](/docs/v1/learn/tools)) to extend their capabilities.
410
411Examples of what you can do with tools include:
412
413- Granting access to the Bing API or DuckDuckGo Search API for internet search to fetch the latest information on various topics.
414- Providing a secure sandbox environment like Repl.it for dynamically running code snippets provided by users in a coding tutorial platform.
415- Allowing access to the Google Cloud Natural Language API for evaluating customer feedback and reviews to determine sentiment and help businesses quickly identify areas for improvement.
416- Providing a Machine Learning (ML) recommendation engine API for giving personalized content or product recommendations for an e-commerce website, based on natural language interactions with users.
417
418Mirascope lets you easily [define tools](/docs/v1/learn/tools/#basic-usage-and-syntax) by documenting any function using a docstring as shown below. It automatically converts this into a tool, saving you additional work.
419
420\`\`\`python
421from typing import Literal
422
423from mirascope.core import openai, prompt_template
424
425def get_current_weather(
426    location: str, unit: Literal["celsius", "fahrenheit"] = "fahrenheit"
427):
428    """Get the current weather in a given location."""
429    if "tokyo" in location.lower():
430        print(f"It is 10 degrees {unit} in Tokyo, Japan")
431    elif "san francisco" in location.lower():
432        print(f"It is 72 degrees {unit} in San Francisco, CA")
433    elif "paris" in location.lower():
434        print(f"It is 22 degrees {unit} in Paris, France")
435    else:
436        print("I'm not sure what the weather is like in {location}")
437
438@openai.call(model="gpt-4o", tools=[get_current_weather])
439def forecast(city: str) -> str:
440    return f"What's the weather in {city}?"
441
442response = forecast("Tokyo")
443if tool := response.tool:
444    tool.call()
445\`\`\`
446
447Mirascope supports Google, ReST, Numpydoc, and Epydoc style docstrings for creating tools. If a particular function doesn’t have a docstring, you can define your own \`BaseTool\` class. You can then define the \`call()\` method to attach the docstring-less function’s functionality to the tool, but use your own custom docstrings:
448
449\`\`\`python
450from mirascope.core import BaseTool
451
452# has no docstings
453
454def get_weather(city: str) -> str: ...
455
456class GetWeather(BaseTool):
457    """Gets the weather in a city."""
458
459    city: str
460
461    def call(self) -> str:
462        return get_weather(self.city)
463\`\`\`
464
465Tools allow you to dynamically generate prompts based on current or user-specified data such as extracting current weather data in a given city before generating a prompt like, "Given the current weather conditions in Tokyo, what are fun outdoor activities?"
466
467See our [documentation](/docs/v1/learn/tools) for details on generating prompts in this way (for instance, by calling the \`call\` method).
468
469### Extract Structured Data from LLM-Generated Text
470
471LLMs are great at producing conversations in text, which is unstructured information. But many applications need structured data from LLM outputs. Scenarios include:
472
473- Extracting structured information from a PDF invoice (i.e., invoice number, vendor, total charges, taxes, etc.) so that you can automatically insert that information into another system like a CRM or tracking tool, a spreadsheet, etc.
474- Automatically extracting sentiment, feedback categories (product quality, service, delivery, etc.), and customer intentions from customer reviews or survey responses.
475- Pulling out specific medical data such as symptoms, diagnoses, medication names, dosages, and patient history from clinical notes.
476- Extracting financial metrics, stock data, company performance indicators, and market trends from financial reports and news articles.
477
478To handle such scenarios, we support extraction with the [\`response_model\`](/docs/v1/learn/response_models) argument in the decorator, which leverages tools (or optionally \`json_mode=True\`) to reliably extract structured data from the outputs of LLMs according to the schema defined in a Pydantic \`BaseModel\`. In the example below you can see how due dates, priorities, and descriptions are being extracted:
479
480\`\`\`python
481from typing import Literal
482
483from mirascope.core import openai, prompt_template
484from pydantic import BaseModel, Field
485
486class TaskDetails(BaseModel):
487    due_date: str = Field(...)
488    priority: Literal["low", "normal", "high"] = Field(...)
489    description: str = Field(...)
490
491@openai.call(
492    model="gpt-4o",
493    response_model=TaskDetails,
494    call_params={"tool_choice": "required"},
495)
496def get_task_details(task: str) -> str:
497    return f"Extract the details from the following task: {task}"
498
499task = "Submit quarterly report by next Friday. Task is high priority."
500task_details = get_task_details(task)
501assert isinstance(task_details, TaskDetails)
502print(task_details)
503
504# > due_date='next Friday' priority='high' description='Submit quarterly report'
505
506\`\`\`
507
508You can define schema parameters against which to extract data in Pydantic’s \`BaseModel\` class, by setting certain attributes and fields in that class. Mirascope also lets you set the number of retries to extract data in case a failure occurs. But you also don’t have to use a detailed schema like \`BaseModel\` if you’re [extracting built-in types](/docs/v1/learn/response_models/#built-in-types) like strings, integers, booleans, etc. The code sample below shows how extraction for a simple structure like a list of strings doesn’t need a full-fledged schema definition.
509
510\`\`\`python
511from mirascope.core import openai
512
513@openai.call(model="gpt-4o", response_model=list[str])
514def recommend_books(genre: str) -> str:
515    return f"Recommend 3 {genre} books"
516
517books = recommend_books(genre="fantasy")
518print(books)
519
520# > [
521
522#   'The Name of the Wind by Patrick Rothfuss',
523
524#   'Mistborn: The Final Empire by Brandon Sanderson',
525
526#   'The Way of Kings by Brandon Sanderson'
527
528#   ]
529
530\`\`\`
531
532Mirascope makes things as simple as feasible, requiring you to write less code in cases where more code isn't necessary.
533
534### Facilitate Your Prompt Workflows with CLI and IDE Support
535
536As mentioned earlier, our experience with prompts is that they generally become unmanageable after a certain 
536number of iterations. Versioning is obviously a good idea, and we see some cloud prompting tools that offer this, but as they don’t generally colocate prompts with LLM calls, not all the relevant information gets versioned, unfortunately.
537
538We believe it’s important to colocate as much information with the prompt as feasible, and that it should all be versioned together as a single unit. It's also extremely important that this versioning happens automatically and captures the entire lexical closure (so that changes to dependent functions can be easily identified through the automatic versioning).
539
540Our prompt engineering framework [Lilypad](/docs/lilypad) lets you:
541
542- Write prompts and LLM functions like you normally would
543- Automatically versions any change to the function
544- Automatically traces every call to the function against its version
545- Is fully open-source and can be used locally
546
547This ensures that you can engineer your prompts as part of your standard Git workflow without having to worry about manually tracking minor changes during development. Colleagues can also see everything that was tried as well as the differences between prompts.
548
549By versioning the entire lexical closure and tracing every call automatically, you can continue iterating on your prompts and any other code in your codebase without having to worry about manually tracking the potential impacts of such changes on the outputs of your functions.
550
551If you want to give Mirascope a try, we recommend taking a look at our [documentation](/docs/mirascope). You can also find our source code on [GitHub](https://github.com/mirascope/mirascope). Don't forget to give us a star!
552`};export{e as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.