Manuale di riferimento MALDA™

Il linguaggio di programmazione AI-First - Versione 1.0.11

10. Prompt

Un prompt è un template nominato e parametrizzato. Chiamarlo interpola i parametri e restituisce un PromptInstance. await può chiamare un LLM e validare il JSON rispetto a un tipo dichiarato. Decoratori come @within e @budget si applicano anche qui, non solo alle funzioni — vedi 9.7 Decoratori.

10.1 Panoramica

I blocchi prompt sono template di prompt riusabili. Parti da un template che non chiama un modello, poi await con uno schema, poi i tool. Stessi file: Examples/Prompts/basic_prompt.malda, Examples/Prompts/schema_prompt_structured.malda, Examples/Prompts/prompt_tools_mode.malda, Examples/Prompts/prompt_tools_then_structured.malda.

PassoCosa scriviChiama un modello?
1. Templateprompt greet(name) { user: "Hello, {name}" } poi greet("Alice")No. Ottieni un PromptInstance (stringhe interpolate).
2. await strutturatoschema + prompt ... -> Schema + await extract(...)Sì. Il JSON viene validato rispetto allo schema.
3. ToolMode B: tools: [...] in una chiamata. Mode C: gather: [...] poi un extract tipizzatoSì. Non combinare tools: e gather:.

Passo 1 — template, senza API key

prompt greet(name) {
    user: "Hello, {name}! How are you today?"
}

var greeting = greet("Alice");
print(greeting.user);

Passo 2 — await più schema (serve un LLM)

schema ContactCard {
    name: string;
    email: string;
}

prompt extractContact(raw) -> ContactCard {
    system: "Extract a contact. Reply with JSON only matching the schema.";
    user: """
    Text:
    {raw}
    """
}

var card = await extractContact("Ada Lovelace — ada@example.com");
print("name=" + card.name);

Passo 3 — Mode B e Mode C

ModeCampoSu await
Btools: ["name"]Un turno LLM con i tool. Lo schema response_format non viene inviato.
Cgather: ["name"] e -> TypeRound di tool, poi un extract tipizzato fresco senza tool.

Senza await, costruire il prompt non chiama comunque il modello. I dettagli di validazione, repair, pipe e PromptInstance seguono in questo capitolo.

10.2 Sintassi di dichiarazione dei prompt

I prompt supportano due sintassi del corpo:

Sintassi a object literal (compatibile con le versioni precedenti)

prompt name(params) -> ReturnType? {
    system: "Optional system prompt"
    user: "Required user prompt with {parameter} interpolation"
    model: "Optional model name"
    temperature: 0.7
    tools: ["tool1", "tool2"]
    gather: ["read_file", "grep"]
    maxTokens: 2000
}

Sintassi a istruzioni (nuova)

prompt name(params) -> ReturnType? {
    system "Optional system prompt";
    user expression;
    model "Optional model name";
    temperature 0.7;
    tools ["tool1", "tool2"];
    gather ["read_file", "grep"];
    maxTokens 2000;
}

Caratteristiche principali:

Esempi few-shot

prompt classify(text) {
    system: "Classify sentiment as positive or negative."
    examples: [
        { input: "I love it!", output: "positive" },
        { input: "Terrible.", output: "negative" }
    ]
    user: "Classify: {text}"
}

var p = classify("Pretty good");
print(length(p.examples));  // 2

Quando il prompt viene eseguito (via agent.think, await prompt(...) o runPrompt), ogni esempio viene inviato come messaggio user seguito da un messaggio assistant, poi il campo user interpolato.

Few-shot dinamici a runtime

Usa withExamples() per attaccare o sostituire gli esempi su un PromptInstance costruito a runtime. Funziona nelle pipeline a pipe:

prompt classify(text) {
    user: "Classify: {text}"
}

var dynamic = [
    { input: "Great!", output: "positive" },
    { input: "Awful.", output: "negative" }
];

var p = classify("Pretty good") |> withExamples(dynamic);
// merge static + runtime examples from the prompt declaration:
var merged = withExamples(classify("x"), dynamic, { merge: true });

await (p |> runPrompt(client));

10.3 Esempi base di prompt

Prompt semplice

prompt simple(task) {
    user: "Task: {task}"
}

var p = simple("Add logging");
print(p.user);  // Output: Task: Add logging

Prompt con messaggio system

prompt planTask(task, docs) -> Plan {
    system: "You are a senior MALDA engineer specializing in feature planning.",
    user: """
    Implement feature: {task}
    
    Relevant documentation:
    {docs}
    
    Provide a detailed implementation plan.
    """
}

var plan = planTask("Add error handling", readFile("docs.md"));

Prompt con interpolazione

prompt greet(name, age) {
    user: $"Hello {name}, you are {age} years old."
}

var greeting = greet("Alice", 25);

10.4 Usare i prompt con gli agenti

I blocchi prompt si possono passare direttamente a agent.think():

var client = new OpenRouterClient("openai/gpt-4");
var agent = new CodingAgent("Dev", "developer", "");

prompt codeReview(code, language) {
    system: "You are an expert code reviewer.",
    user: """
    Review this {language} code:
    
    ```{language}
    {code}
    ```
    
    Provide:
    1. Bug findings
    2. Performance issues
    3. Style suggestions
    """
}

var review = agent.think(codeReview(sourceCode, "malda"));
print(review.content);

Quando un PromptInstance viene passato a agent.think():

10.5 Esecuzione diretta con await

I prompt si possono eseguire direttamente con await, che chiama l'LLM e restituisce la stringa di risposta:

// Optional: use OpenRouter; omit to use default local LLM
var client = new OpenRouterClient();
var agent = new Agent("Assistant", "helper", "You are helpful.", client);
setDefaultAgent(agent);

prompt summarize(text) {
    system "You are a summarizer. Respond with only the summary.";
    user "Summarize: " + text;
}

var summary = await summarize("Long document text here...");
print(summary);  // Prints the LLM response directly

Come funziona:

10.6 Dichiarazioni schema per l'output strutturato

Dichiara schema JSON riusabili con la keyword schema. Gli schema sono usati da parseJson() e dai ritorni tipizzati dei prompt (-> Type):

schema Answer {
    text: string;
    sources: string[];   // array types: elementType[]
    score: float?;       // optional field
}

schema Address {
    city: string;
}

schema Person {
    name: string;
    address: Address;    // nested schema (expanded for validate / typed prompts)
}

var parsed = parseJson('{"text":"hi","sources":["a.md"]}', "Answer");

Tipi di campo supportati: primitivi JSON (string, int, float, bool, object, array), forme array come string[] o Address[], altri nomi di schema dichiarati (gli oggetti nested vengono espansi inline) e sum type dichiarati (intent: Intent / Intent[] — JSON tagged oneOf). Usa ? dopo il tipo per i campi opzionali. I nomi di tipo di campo sconosciuti danno errore quando lo schema viene risolto; i riferimenti ciclici tra schema vengono rifiutati. Vedi Examples/Basics/schema_nested_validate.malda e Examples/Basics/schema_sumtype_validate.malda.

Per pipeline RAG end-to-end che combinano schema, prompt, retriever e pipe, vedi Examples/Prompts/rag_pipeline.malda e 13. Funzioni built-in (helper di pipeline AI).

10.7 Sum type come tipi di ritorno dei prompt

Quando il modello deve scegliere una di più forme (intent), dichiara un sum type e usalo come tipo di ritorno del prompt. In caso di successo, await restituisce un valore variante reale per match:

type Intent =
    Search(query: string)
  | Buy(sku: string, qty: int)
  | Help();

prompt parseUtterance(text) -> Intent {
    system: "Map the user to exactly one Intent variant. JSON only.";
    user: text;
}

var intent = await parseUtterance("buy 2 of SKU-9");
match intent {
    case Search(q): print("search: " + q);
    case Buy(sku, qty): print("buy " + sku);
    case Help(): print("help");
}

Il JSON sul filo usa un campo tag uguale al nome del costruttore, più campi payload nominati come i parametri del costruttore — per esempio {"tag":"Buy","sku":"SKU-9","qty":2} o {"tag":"Help"}. I tipi opzionali del payload (Buy(sku: string, qty: int)) diventano tipi JSON Schema per validate e i prompt tipizzati; i parametri solo-nome restano permissivi. Lo stesso nome non può essere insieme uno schema e un type (sum type). validate("Intent", dict) accetta quella forma tagged e restituisce il dict originale; non produce una variante. Vedi Examples/Prompts/sum_type_intent_prompt.malda, Examples/Basics/sumtype_typed_payloads.malda, Examples/Basics/schema_sumtype_validate.malda e Tipi di dati per i sum type.

10.8 API chiuse e programmi deterministici

Dichiara un api chiuso di firme di metodo. Le implementazioni sono function top-level ordinarie con gli stessi nomi. I parametri del metodo possono avere un SchemaType opzionale (la stessa forma dei campi schema e dei payload dei sum type: add(a: number, b: number)); i parametri solo-nome restano permissivi. I tipi dichiarati alimentano lo schema JSON del programma e coercono gli argomenti LLM ("2" diventa un numero solo se il hint è number/int). I parametri dei prompt restano solo-nome e il corpo della function di implementazione resta senza tipi. Un prompt tipizzato può restituire program(ApiName); in caso di successo ottieni un valore programma che runProgram esegue senza ulteriori chiamate LLM (a differenza dei loop agente @Tool o di executePlan):

api Calc {
    function add(a: number, b: number);
    function mul(a: number, b: number);
}

function add(a, b) { return a + b; }
function mul(a, b) { return a * b; }

prompt solve(expr) -> program(Calc) {
    system: "Translate into Calc steps. JSON only.";
    user: expr;
}

var prog = await solve("(2 + 3) * 4");
var result = runProgram(prog);

Forma del JSON sul filo. Offline (senza LLM), passa lo stesso oggetto direttamente a runProgram — gli args sono letterali JSON oppure riferimenti "$alias" all'as di uno step precedente. I modelli spesso emettono chiamate annidate {"call","args"}, forme TypeChat @func/@ref, stringhe numeriche ("2") o wrapper {"type":"number","value":2}; l'host le appiattisce e le coerce prima di chiamare i metodi dell'api. Se i metodi si chiamano _add/_mul, l'host mappa anche alias univoci come add/mul e +/*, e tratta un t0 nudo come "$t0". Gli oggetti residui in args vengono rifiutati (prima venivano passati così com'erano, quindi add riceveva un oggetto invece di un numero). Preferisci numeri JSON, non stringhe numeriche:

api Calc {
    function add(a: number, b: number);
    function mul(a: number, b: number);
}

function add(a, b) { return a + b; }
function mul(a, b) { return a * b; }

var prog = parseJSON("""
{
  "@api": "Calc",
  "steps": [
    { "call": "add", "args": [2, 3], "as": "t0" },
    { "call": "mul", "args": ["$t0", 4], "as": "result" }
  ],
  "return": "$result"
}
""");

var result = runProgram(prog);
print(result);

Vedi Examples/Prompts/api_program_calc.malda. Un nome non può essere insieme un api e uno schema / type sum.

10.9 Pipeline a pipe riusabili

Nomina e riusa pipeline AI a più step con dichiarazioni function ordinarie e l'operatore pipe (|>). Restituisci esplicitamente il risultato finale della pipe.

function ragContext(question, retriever) {
    return question |> retriever.get |> formatRetrievedDocs;
}

function ragPrompt(question, context) {
    return context |> (ctx) => answerPrompt(question, ctx);
}

var context = ragContext("What is GraphMemory?", retriever);
var prompt = ragPrompt("What is GraphMemory?", context);

Con un client LLM e output tipizzato:

function ragAnswer(question, client) -> Answer {
    return question |> retriever.get |> formatRetrievedDocs
        |> (ctx) => answerPrompt(question, ctx)
        |> runPrompt(client)
        |> parseJson("Answer");
}

var answer = await ragAnswer("How does retrieval work?", client);

Binding locali per corpi multi-stage leggibili (retrieval, branching, riuso di valori intermedi):

function buildContext(question, retriever, fallback) {
    var hits = question |> retriever.get;
    if (length(hits) == 0) {
        return fallback;
    }
    var text = formatRetrievedDocs(hits);
    return text;
}

Stream dei token durante runPrompt in modalità interpretata e transpile: passa { onToken: (token) => ... } per il contenuto e { onReasoning: (token) => ... } per i delta di reasoning (vedi 13. Funzioni built-in).

Vedi 7. Espressioni (operatore pipe) e Examples/Prompts/rag_function_pipeline.malda.

Composizione avanzata: composePipe, parallelRun e mergeRetrievedDocs si compongono con funzioni e pipe per retrieval parallelo e funzioni di pipeline riusabili. Vedi Examples/Prompts/rag_compose_pipeline.malda e 13. Funzioni built-in.

Esempio di await tipizzato (output validato)

prompt planTask(task) -> Plan {
    system "You are a planner. Return only JSON.";
    user "Task: " + task;
}

// With a return type, await validates the model output
var plan = await planTask("Add parser tests");
print(plan.steps[0].description);

Impostare un agente di default:

Se ometti un client, il default è il modello locale Qwen/Qwen2.5-0.5B-Instruct (scaricato automaticamente come build GGUF da Hugging Face). Per usare un modello remoto (es. OpenRouter), passa un client:

// Optional: use OpenRouter for remote model
var client = new OpenRouterClient();
var agent = new Agent("MyAgent", "assistant", "You are helpful.", client);
setDefaultAgent(agent);

// Now await prompt() calls will use this agent
var result = await myPrompt("input");

10.10 Proprietà di PromptInstance

Quando un prompt viene invocato, restituisce un oggetto PromptInstance con le proprietà seguenti:

var p = myPrompt("arg1", "arg2");

// Access prompt fields
var system = p.system;      // string? (null if not provided)
var user = p.user;          // string (required)
var examples = p.examples;  // array? of { input, output } few-shot pairs
var model = p.model;        // string? (null if not provided)
var temperature = p.temperature;  // number? (null if not provided)
var tools = p.tools;        // array? (null if not provided; Mode B)
var gather = p.gather;      // array? (null if not provided; Mode C tool list)
var maxTokens = p.maxTokens; // number? (null if not provided)

// Methods
var userStr = p.getUser();     // Returns user prompt string
var systemStr = p.getSystem(); // Returns system prompt string or null
var promptStr = p.toPromptString(); // Returns user prompt (for backward compatibility)

10.11 Funzionalità avanzate dei prompt

Prompt con metadati (sintassi a object literal)

prompt advanced(task) {
    system: "You are an expert.",
    user: "Task: {task}",
    model: "openai/gpt-4",
    temperature: 0.7,
    tools: ["read_file", "write_file", "grep"],
    maxTokens: 2000
}

Prompt con metadati (sintassi a istruzioni)

prompt advanced(task) {
    system "You are an expert.";
    user "Task: " + task;
    model "openai/gpt-4";
    temperature 0.7;
    tools ["read_file", "write_file", "grep"];
    maxTokens 2000;
}

Sintassi a istruzioni con espressioni

La sintassi a istruzioni permette a user di essere qualsiasi espressione, non solo un letterale stringa:

prompt combine(a, b) {
    user "First: " + a + ", Second: " + b;
}

var result = combine("A", "B");
print(result.user);  // Output: First: A, Second: B

Prompt su più righe

prompt detailedPlan(goal, context) {
    system: "You are a strategic planner.",
    user: """
    Goal: {goal}
    
    Context:
    {context}
    
    Please provide:
    1. Step-by-step plan
    2. Risk assessment
    3. Timeline estimate
    """
}

10.12 Blocchi prompt e concatenazione di stringhe

Prima (concatenazione di stringhe):

var prompt = "Task: " + task + "\n\n" +
             "Documentation:\n" + docs;
var response = agent.think(prompt);

Dopo (blocchi prompt):

prompt planTask(task, docs) {
    user: """
    Task: {task}
    
    Documentation:
    {docs}
    """
}

var response = agent.think(planTask(task, docs));

Vantaggi:

Vedi anche