Chapter 6 · Prompt Engineering: instructions and the Description System
Learning objectives: Master the division of labor and priority among
description,instructions, andsystem_messagein Agno; learn to write stable, maintainable Agent prompts.
6.1 Agno's Three-Layer Prompt System
Agno doesn't ask you to hand-write one giant system prompt — it splits it into structured configuration items. Following the official Context Engineering model:
| Config item | Layer | Role |
|---|---|---|
description | Top | What the Agent is, overall responsibility — like a "job description" |
instructions | Middle | List of specific behavioral rules — like an "operations manual" |
system_message | Override | Fully custom system message; when set, replaces the auto-assembled combination of the above two |
# prompt_layers.py —— Using the three-layer system
import os
from dotenv import load_dotenv
from agno.agent import Agent
from agno.models.openai import OpenAIChat
load_dotenv()
model = OpenAIChat(
id="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
)
agent = Agent(
model=model,
# Job description: one sentence clarifying role and boundaries
description="You are a rigorous Python technical interviewer.",
# Operations manual: list behavioral rules one per line; Agno numbers and injects them into the system message
instructions=[
"Ask one question at a time; wait for the candidate's answer before following up",
"Follow-up questions should target weaknesses in the previous answer",
"Use Chinese throughout; code examples may use English identifiers",
"Don't give the answer directly — guide the candidate to think first",
],
markdown=True,
)
agent.print_response("Start the interview")Why a list instead of one big paragraph? A list puts each rule on its own line; models follow "itemized instructions" significantly better than large paragraphs. It also makes code review and version diffs cleaner — this is the key to treating prompts as "config" rather than "copywriting."
6.2 Template Variables in instructions
Instructions support placeholders that are filled at runtime from session state, enabling "one prompt serves many users":
# prompt_template.py —— Template variable injection
agent = Agent(
model=model,
db=None,
instructions=[
"The user's current membership tier is {plan}; adjust tone accordingly",
"When recommending to free users, always note paid feature limitations",
],
session_state={"plan": "pro"}, # Values from session state fill {plan}
)
# Same Agent serves different tiers with different session_state
agent.print_response("What features are available?", session_state={"plan": "free"})This decouples "business data" from prompt text: changing membership rules only touches session_state, not the prompt.
6.3 When to Use system_message
In most scenarios, description + instructions is sufficient. Only use system_message when you need full control over the raw system message text — note that it bypasses auto-assembly:
# system_message_demo.py —— Fully custom system message
agent = Agent(
model=model,
description="This field is ignored", # ⚠️ Ineffective once system_message is set
instructions=["Neither is this"],
system_message="You are a pirate translator. Translate user input into pirate-style English.",
)Recommendation
Unless migrating a legacy system or conforming to a third-party prompt spec with hard constraints, don't use system_message. Abandoning structured layering means giving up Agno's fine-grained context management.
6.4 Few-shot Example Injection
The most effective way to make output format extremely stable is to provide examples in the instructions (few-shot). Combined with the structured output from Chapter 5, the format is guaranteed by the schema; style and judgment criteria are anchored by examples:
# few_shot.py —— Anchoring classification criteria with examples
from pydantic import BaseModel, Field
from typing import Literal
class TicketCategory(BaseModel):
category: Literal["bug", "feature", "question", "complaint"]
urgency: int = Field(ge=1, le=5, description="Urgency level")
support_router = Agent(
model=model,
description="Customer service ticket classifier.",
instructions=[
"Output category and urgency (1–5) based on ticket content",
"Judgment criteria examples:",
"- 'Login button does nothing' → bug, urgency=3",
'- "Add dark mode support" → feature, urgency=2',
'- "Will my data be leaked?" → question, urgency=1',
'- "I will complain to the consumer association if this isn\'t fixed!" → complaint, urgency=5',
],
output_schema=TicketCategory,
)
cases = ["App crashes on launch, can't open at all", "Can you add a WeChat mini-program version?"]
for c in cases:
r = support_router.run(c)
print(c, "->", r.content.category, r.content.urgency)Examples eliminate ambiguity in standards: is a "crash" a bug or a complaint? With reference samples, the model's choice becomes much more stable.
6.5 Chinese Prompt Practical Tips
Since the tutorial uses Chinese models and Chinese scenarios, here are some empirically effective practices:
- Match instruction language to task language: When having DeepSeek handle Chinese tasks, write instructions in Chinese for better term comprehension;
- Avoid long negative sentences: "Don't use complex sentence structures" is worse than "Keep each sentence under 25 characters";
- Keep rule count under 10: Beyond that, middle rules get ignored; split into multiple specialized Agents instead;
- Put hard format constraints at the beginning and end: First and last items have the highest compliance rates;
- Use
additional_guidancefor temporary rules (when dynamically adding behavioral requirements); keep main instructions stable for easier comparison.
6.6 Iterative Method: Test Prompts Like Code
The correct approach to prompt engineering is not "tweak by feel" — build a small eval set for regression testing:
# prompt_eval.py —— Minimal prompt regression test
EVAL_CASES = [
("Login page white screen", "bug"),
("Want Excel export support", "feature"),
("How to change bound phone number", "question"),
]
def evaluate(agent) -> float:
hits = sum(
agent.run(q).content.category == expect
for q, expect in EVAL_CASES
)
return hits / len(EVAL_CASES)
print(f"Accuracy: {evaluate(support_router):.0%}")
# Run before and after modifying instructions; adopt the version with better numbersScale this to dozens of cases and hook into CI for periodic runs — this is the precursor to the evaluation system (Evals) in Chapter 25.
6.7 Chapter Summary
descriptionsets the role,instructionssets the rules,system_messageoverrides everything (use sparingly);- Instructions as an itemized list + template variables
{placeholder}are maintainable "config," not copywriting; - Few-shot examples eliminate ambiguity in judgment criteria; pair well with output_schema;
- Chinese prompts: same language as task, fewer negations, ≤10 rules, hard constraints at start/end;
- Prompt changes must be validated with eval sets — never ship based on feeling alone.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. After setting system_message, what happens to description and instructions?
2. Instructions contain "User plan is {plan}". How do you make the placeholder get replaced with an actual value?
3. Regarding the primary purpose of few-shot examples, which statement is correct?
4. According to the iterative methodology in this chapter, what is the correct workflow for improving a prompt?
🛠️ Hands-On Exercises
- Refactor the ticket classification Agent from Chapter 5's hands-on exercise into description + instructions layered style; add 3 boundary rules.
- Write 10 Chinese eval cases for your Agent; implement an upgraded
prompt_eval.pythat outputs a per-case expected-vs-actual comparison table. - Experiment with rule ordering: place "output must be JSON" at the first position vs. the last position in instructions; run 5 times each and count format error frequency.
Beginner section complete. Proceed to Chapter 7: Tools — Built-in Toolkit Principles and Usage.