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.
| Passo | Cosa scrivi | Chiama un modello? |
|---|---|---|
| 1. Template | prompt greet(name) { user: "Hello, {name}" } poi greet("Alice") | No. Ottieni un PromptInstance (stringhe interpolate). |
2. await strutturato | schema + prompt ... -> Schema + await extract(...) | Sì. Il JSON viene validato rispetto allo schema. |
| 3. Tool | Mode B: tools: [...] in una chiamata. Mode C: gather: [...] poi un extract tipizzato | Sì. 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
| Mode | Campo | Su await |
|---|---|---|
| B | tools: ["name"] | Un turno LLM con i tool. Lo schema response_format non viene inviato. |
| C | gather: ["name"] e -> Type | Round 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:
- Parametri: Come le funzioni, i prompt possono prendere parametri che vengono interpolati nelle stringhe del prompt
- Tipo di ritorno: Annotazione opzionale (
-> ReturnType). Perprompt(...)(senzaawait) influisce solo sul tipo delPromptInstance. Quando usiawait prompt(...), il tipo di ritorno è applicato: la risposta LLM viene parsata come JSON, validata rispetto al tipo dichiarato (primitivi,Plano nomi di classi custom) e, in caso di fallimenti ripetuti della validazione, viene inviata un'istruzione di riparazione e la chiamata viene ritentata (tentativi limitati); dopo l'esaurimento viene lanciato un errore runtime. I tipi di ritorno a classe custom (es.-> MyClass) sono supportati sia nelle esecuzioni interpretate sia in quelle transpile. - Corpo: Due sintassi supportate:
- Object literal: Sintassi tradizionale con coppie
key: value - A istruzioni: Nuova sintassi con istruzioni
keyword expression;. Il campouserpuò essere qualsiasi espressione (convertita in stringa), non solo letterali stringa.
- Object literal: Sintassi tradizionale con coppie
- Campi del prompt:
system/system:(stringa opzionale) - System prompt per l'LLMuser/user:(obbligatorio) - User prompt. Nella sintassi a istruzioni può essere qualsiasi espressione (convertita in stringa). Supporta l'interpolazione{param}e le stringhe interpolate$"...".model/model:(stringa opzionale) - Nome del modello LLMtemperature/temperature:(numero opzionale) - Impostazione di temperature per l'LLMtools/tools:(array opzionale di stringhe) - Elenco dei nomi di tool da rendere disponibili (Mode B). Non combinare congather.gather/gather:(array opzionale di stringhe) - Marker Mode C: suawaitcon-> Type, esegue un round di tool, poi un extract tipizzato fresco senza tool. Richiede-> Type. Costruire ilPromptInstancesenzaawaitnon chiama il modello.maxTokens/maxTokens:(intero opzionale) - Massimo di token per la rispostaexamples/examples:(array opzionale) - Coppie di esempi few-shot iniettate come messaggi user/assistant prima del promptuserfinale. Ogni voce è un oggetto con stringheinputeoutput(sono accettati gli aliasuser/assistant).
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():
- Il prompt
system(se fornito) viene impostato sulla conversazione - Il prompt
userviene usato come messaggio - I metadati opzionali (
model,temperature,tools,maxTokens) possono essere applicati (evoluzione futura)
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:
- Quando usi
await prompt(...), il prompt viene eseguito subito - Il sistema usa l'agente di default (impostato con
setDefaultAgent(agent)) oppure, se non ne è impostato nessuno, crea un agente temporaneo con l'LLM locale di default (Qwen/Qwen2.5-0.5B-Instruct, scaricato come build GGUF da Hugging Face al primo uso) - Se non è dichiarato un tipo di ritorno, il contenuto della risposta LLM viene restituito come stringa
- Se è dichiarato un tipo di ritorno (
prompt name(...) -> Type), il valore di ritorno è applicato: MALDA estrae il JSON dalla risposta (anche da markdown o code fence), lo valida rispetto allo schema del tipo dichiarato e, in caso di successo, restituisce il valore parsato - Mode A (structured): quando è presente
-> Typee il corpo del prompt non ha né tool négather:, lo schema JSON viene inviato al backend LLM (es.response_formatdi OpenAI) così il modello restituisce JSON conforme allo schema al primo tentativo. Lo stesso gate aggiunge anche una appendice schema compatta al messaggio system (markerMALDA_OUTPUT_SCHEMA), così i backend locali che ignoranoresponse_formatvedono comunque la forma attesa. VediExamples/Prompts/schema_prompt_structured.malda. - Mode B (tools): per v1,
response_formatetoolssono mutuamente esclusivi — se il prompt elencatools:, lo schema non viene inviato e l'appendice non viene aggiunta. Suawaitcon-> Type, validazione e riparazione girano comunque (più difficile per i modelli locali senza l'appendice). VediExamples/Prompts/prompt_tools_mode.malda.tools:non viene reinterpretato come due chiamate LLM. - Mode C (gather-then-extract):
gather: ["tool", …]più-> Typesu un solo prompt. Suawait, MALDA esegue un round di tool (senzaresponse_format), poi un prompt tipizzato fresco senza tool (validazione/riparazione Mode A). L'output di gather non viene passato avalidatefino allo step di extract. Unprompt(...)offline senzaawaitnon chiama il modello. VediExamples/Prompts/prompt_tools_then_structured.malda. Il rituale precedente a due prompt continua a funzionare; preferiscigather:. - In caso di fallimento della validazione, MALDA aggiunge al prompt un'istruzione di riparazione e ritenta (fino a un massimo fisso di tentativi); dopo l'esaurimento viene lanciato un errore runtime
- I tipi di ritorno supportati includono i primitivi (
string,int,number,bool,array,object), il tipo built-inPlan, i nomi di classi custom (es.-> MyClass), i nomi dischema, i sum type (es.-> Intent) e i program (-> program(ApiName)); funzionano sia in interprete sia in modalità transpile - È una sintassi più pulita di
agent.think(prompt(...)).content
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:
- Struttura: I prompt sono funzioni nominate e parametrizzate
- Riusabilità: Definisci una volta, usa ovunque
- Validazione dei parametri: A runtime viene validato il matching del numero di argomenti
- Supporto IDE: Autocomplete, go-to-definition, refactoring
- Manutenibilità: Versiona i prompt in modo indipendente, testa facilmente le variazioni
Vedi anche
- 9. Funzioni - Parametri, return, lambda e decoratori
- 4. Tipi di dati - Sum type usati come tipi di ritorno dei prompt
- 7. Espressioni - Operatore pipe per le pipeline di prompt
- 13. Funzioni built-in - Helper della pipeline AI
- 18. Orchestrazione di agenti - Usare gli agenti con i prompt