#4557 Domain Specific Language for Prompting

#4557 Domain Specific Language for Prompting

https://www.jetbrains.com/mps/

#4557 Define a domain specific language for LLM prompting

Designing a Domain-Specific Language (DSL) for LLM prompting—let’s call it PromptScript—is an excellent way to move away from messy, unstructured text and toward predictable, reproducible, and structured AI interactions.

A great prompting DSL needs to handle variable injection, role definition, few-shot examples, and output constraints cleanly.

Here is a blueprint for a declarative, human-readable DSL designed for LLM workflows.

The Core Blueprint: PromptScript

1. Keywords and Syntax

  • @agent: Defines the persona, system instructions, and temperature.

  • @context: Injects background data, documents, or variables.

  • @examples: Groups few-shot input/output pairs.

  • @execute: The main prompt instruction or user query.

  • @schema: Enforces a structured output format (e.g., JSON, YAML).

  • {{variable}}: Standard syntax for dynamic data binding.

Example 1: Customer Support Classifier

Here is how you would write a script to classify incoming emails and extract key data.

@agent SupportBot {
    role: "Expert Customer Support Triage Specialist"
    temperature: 0.1
    style: "concise, professional"
}

@context {
    current_date: "{{date}}"
    urgency_levels: ["Low", "Medium", "High", "Critical"]
}

@examples [
    {
        input: "My order #1234 never arrived. It was supposed to be here yesterday."
        output: { category: "Shipping", urgency: "High", needs_refund: false }
    },
    {
        input: "Can I change my password?"
        output: { category: "Account Security", urgency: "Medium", needs_refund: false }
    }
]

@schema {
    category: string,
    urgency: enum(urgency_levels),
    summary: string,
    needs_refund: boolean
}

@execute {
    Analyze the following incoming customer ticket: "{{ticket_body}}".
    Classify it and extract the required fields according to the schema.
}

Example 2: Multi-Turn Chain of Thought

For complex reasoning, the DSL can support chained execution blocks where the output of a previous step is piped into the next.

@agent Analyst {
    role: "Financial Risk Analyst"
    temperature: 0.3
}

# Step 1: Brainstorming/Reasoning
@execute Step1 {
    Analyze the market risks for the following company data: "{{company_profile}}".
    List at least 3 major vulnerabilities. Think step-by-step.
}

# Step 2: Structuring the final report
@schema ReportFormat {
    vulnerabilities: array(string),
    overall_risk_score: int(1-10)
}

@execute Step2 {
    Using the reasoning from {{Step1}}, extract the final vulnerabilities 
    and assign a risk score format conforming to ReportFormat.
}

Why This Architecture Works

Clear Separation of Concerns

In standard string-concatenation prompting, system instructions, user data, and few-shot examples often blur together, causing the LLM to get confused. By separating them into native components (@agent, @context, @examples), the underlying compiler can format the prompt into the exact API structure the model expects (e.g., separating system, user, and assistant message arrays for OpenAI/Anthropic APIs).

Native Validation

By including a @schema block, the DSL parser can automatically append formatting instructions to the prompt (like JSON Mode or Instructor-style schemas) and validate the LLM's response before returning it to your application.

What specific use case are you building this DSL for (e.g., automated agents, data extraction, creative writing), and would you like to see how to implement a basic parser for it in Python?


Seems like both examples could be coded almost as-is as JSON strings in Python

You hit the nail on the head. Under the hood, almost every modern LLM orchestration framework (like LangChain, LlamaIndex, or Instructor) ultimately serializes instructions into JSON-like structures or standard dictionaries before shipping them off to an API.

In fact, you could write Example 1 directly as a Python dictionary:

prompt_config = {
    "agent": {"role": "Support Specialist", "temperature": 0.1},
    "context": {"urgency_levels": ["Low", "Medium", "High"]},
    "execute": "Analyze this ticket: {ticket_body}"
}

However, while JSON works great for configuration, dedicated DSLs usually pop up to solve a few specific headaches that JSON introduces when things get complex.

Where JSON Starts to Struggle in Prompting

  • The "Wall of Text" Problem: Prompts inherently require massive, multi-line blocks of natural language text, often with markdown formatting, bullet points, and specific indentation. Writing long, multi-line paragraphs with newlines (\n) inside JSON strings quickly becomes a messy, unreadable nightmare.

  • Variable Scope and Logic: JSON is strictly data. It can't handle things like piping the output of one step into another (like {{Step1}} in the second example), conditional loops (e.g., looping through a list of documents to inject them), or referencing local variables natively.

  • Comments: JSON famously does not support comments. When you are tuning complex prompts, being able to comment out a line or write # This constraint stops the model from hallucinating x is incredibly important.

The Middle Ground: YAML or TOML

Because JSON can be rigid, many developers use YAML or TOML as a middle ground for prompt engineering. They handle multi-line strings beautifully and support comments, giving you a DSL-like feel without needing to write a custom parser.

Are you looking to build a strict tool/framework for others to use, or are you just trying to find the cleanest way to manage and organize your own prompts in a Python project?


Re-write the two examples in YAML

Converting this to YAML makes it incredibly clean. YAML natively supports multi-line text blocks using the literal block scalar (|), which completely solves the ugly newline (\n) formatting issue found in JSON. It also allows for comments.

Here is how both examples translate into valid YAML structures.

Example 1: Customer Support Classifier

# Configuration for the LLM persona and generation parameters
agent:
  role: "Expert Customer Support Triage Specialist"
  temperature: 0.1
  style: "concise, professional"

# Static or dynamic context injected into the prompt environment
context:
  current_date: "{{date}}"
  urgency_levels:
    - Low
    - Medium
    - High
    - Critical

# Few-shot examples to guide the model's output formatting
examples:
  - input: "My order #1234 never arrived. It was supposed to be here yesterday."
    output:
      category: "Shipping"
      urgency: "High"
      needs_refund: false
  - input: "Can I change my password?"
    output:
      category: "Account Security"
      urgency: "Medium"
      needs_refund: false

# Expected structure of the final response
schema:
  category: "string"
  urgency: "enum(urgency_levels)"
  summary: "string"
  needs_refund: "boolean"

# The core instruction utilizing YAML's literal block '|' for clean multi-line text
execute: |
  Analyze the following incoming customer ticket: "{{ticket_body}}".
  Classify it and extract the required fields according to the schema.

Example 2: Multi-Turn Chain of Thought

agent:
  role: "Financial Risk Analyst"
  temperature: 0.3

# A pipeline of execution steps where the output of Step 1 is passed to Step 2
pipeline:
  step_1:
    instruction: |
      Analyze the market risks for the following company data: "{{company_profile}}".
      List at least 3 major vulnerabilities. Think step-by-step.
      
  step_2:
    schema:
      vulnerabilities: "array(string)"
      overall_risk_score: "int(1-10)"
    instruction: |
      Using the reasoning from {{step_1.output}}, extract the final vulnerabilities 
      and assign a risk score format conforming to the schema.

Why this works so well in Python

If you load these files using a library like PyYAML, you instantly get a native Python dictionary. You can then use Python's built-in .format() or a templating engine like Jinja2 to swap out the {{variables}} seamlessly before sending the payload to your LLM API.

Would you like to see the quick Python snippet to parse and run a YAML prompt like this?


Repeat for TOML

TOML is another fantastic alternative. While YAML relies on indentation, TOML uses an explicit, ini-like syntax with tables ([table]) and arrays of tables ([[array]]).

Crucially, TOML handles multi-line strings beautifully using triple quotes ("""), and like YAML, it natively supports comments.

Here is how both configurations look in TOML.

Example 1: Customer Support Classifier

# Configuration for the LLM persona and generation parameters
[agent]
role = "Expert Customer Support Triage Specialist"
temperature = 0.1
style = "concise, professional"

# Static or dynamic context injected into the prompt environment
[context]
current_date = "{{date}}"
urgency_levels = ["Low", "Medium", "High", "Critical"]

# Few-shot examples (Array of Tables)
[[examples]]
input = "My order #1234 never arrived. It was supposed to be here yesterday."
output = { category = "Shipping", urgency = "High", needs_refund = false }

[[examples]]
input = "Can I change my password?"
output = { category = "Account Security", urgency = "Medium", needs_refund = false }

# Expected structure of the final response
[schema]
category = "string"
urgency = "enum(urgency_levels)"
summary = "string"
needs_refund = "boolean"

# Core instruction using multi-line strings
[execute]
instruction = """
Analyze the following incoming customer ticket: "{{ticket_body}}".
Classify it and extract the required fields according to the schema.
"""

Example 2: Multi-Turn Chain of Thought

[agent]
role = "Financial Risk Analyst"
temperature = 0.3

# A pipeline of execution steps represented as nested tables
[pipeline.step_1]
instruction = """
Analyze the market risks for the following company data: "{{company_profile}}".
List at least 3 major vulnerabilities. Think step-by-step.
"""

[pipeline.step_2]
schema = { vulnerabilities = "array(string)", overall_risk_score = "int(1-10)" }
instruction = """
Using the reasoning from {{pipeline.step_1.output}}, extract the final vulnerabilities 
and assign a risk score format conforming to the schema.
"""

Python Integration

Starting in Python 3.11, TOML parsing is built right into the standard library via tomllib. Loading one of these files is incredibly straightforward:

import tomllib

with open("prompt.toml", "rb") as f:
    prompt_config = tomllib.load(f)

# prompt_config is now a clean Python dictionary ready for Jinja2 templating
print(prompt_config["agent"]["role"])


From <https://gemini.google.com/app/>  Google Gemini (3.5 Flash)

Comments

Popular posts from this blog

#4054 AI Agents in AI Studio

#3679 Google Gemini 2.5 Pro: Create Python Application to View and Query Neo4j KG