Unified Agent + reusable Go agent core.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor: Separate intent prompt rendering into templates

Lyric 21f805a0 70cd93fa

+128 -25
+5 -23
agent/intent.go
··· 38 38 if task == "" { 39 39 return Intent{}, fmt.Errorf("empty task") 40 40 } 41 - payload := map[string]any{ 42 - "task": task, 43 - "history": trimIntentHistory(history, maxHistory), 44 - "rules": []string{ 45 - "Return a compact, structured intent summary.", 46 - "Use the same language as the user for values.", 47 - "goal: the user's true objective (not the literal request).", 48 - "deliverable: the minimum acceptable output form (e.g., list of concrete items, decision, plan, code diff).", 49 - "constraints: explicit constraints like time range, quantity, sources, format, language.", 50 - "ambiguities: only material uncertainties that block a good answer.", 51 - "question: true if the user message asks a question (explicit or implicit).", 52 - "request: true if the user message asks the assistant to perform an action or produce an output.", 53 - "question and request are independent; both may be true.", 54 - "ask: default false; set true only if you cannot proceed safely or would risk irreversible harm without clarification.", 55 - "Prefer proceeding with stated assumptions over asking questions.", 56 - "Do not invent constraints or facts.", 57 - "Do not copy these instruction bullets into output fields.", 58 - "Do not include meta instructions about intent formatting in constraints.", 59 - }, 41 + trimmedHistory := trimIntentHistory(history, maxHistory) 42 + sys, user, err := renderIntentPrompts(task, trimmedHistory) 43 + if err != nil { 44 + return Intent{}, fmt.Errorf("render intent prompts: %w", err) 60 45 } 61 - b, _ := json.Marshal(payload) 62 - sys := "You infer user intent. Return ONLY JSON with keys: " + 63 - "goal (string), deliverable (string), constraints (array of strings), ambiguities (array of strings), question (boolean), request (boolean), ask (boolean)." 64 46 65 47 res, err := client.Chat(ctx, llm.Request{ 66 48 Model: model, 67 49 ForceJSON: true, 68 50 Messages: []llm.Message{ 69 51 {Role: "system", Content: sys}, 70 - {Role: "user", Content: string(b)}, 52 + {Role: "user", Content: user}, 71 53 }, 72 54 Parameters: map[string]any{ 73 55 "max_tokens": 1024,
+49
agent/intent_template.go
··· 1 + package agent 2 + 3 + import ( 4 + _ "embed" 5 + "encoding/json" 6 + "text/template" 7 + 8 + "github.com/quailyquaily/mistermorph/internal/prompttmpl" 9 + "github.com/quailyquaily/mistermorph/llm" 10 + ) 11 + 12 + //go:embed prompts/intent_system.tmpl 13 + var intentSystemPromptTemplateSource string 14 + 15 + //go:embed prompts/intent_user.tmpl 16 + var intentUserPromptTemplateSource string 17 + 18 + var intentPromptTemplateFuncs = template.FuncMap{ 19 + "toJSON": func(v any) (string, error) { 20 + b, err := json.Marshal(v) 21 + if err != nil { 22 + return "", err 23 + } 24 + return string(b), nil 25 + }, 26 + } 27 + 28 + var intentSystemPromptTemplate = prompttmpl.MustParse("agent_intent_system_prompt", intentSystemPromptTemplateSource, nil) 29 + var intentUserPromptTemplate = prompttmpl.MustParse("agent_intent_user_prompt", intentUserPromptTemplateSource, intentPromptTemplateFuncs) 30 + 31 + type intentUserPromptTemplateData struct { 32 + Task string 33 + History []llm.Message 34 + } 35 + 36 + func renderIntentPrompts(task string, history []llm.Message) (string, string, error) { 37 + systemPrompt, err := prompttmpl.Render(intentSystemPromptTemplate, struct{}{}) 38 + if err != nil { 39 + return "", "", err 40 + } 41 + userPrompt, err := prompttmpl.Render(intentUserPromptTemplate, intentUserPromptTemplateData{ 42 + Task: task, 43 + History: history, 44 + }) 45 + if err != nil { 46 + return "", "", err 47 + } 48 + return systemPrompt, userPrompt, nil 49 + }
+46
agent/intent_template_test.go
··· 1 + package agent 2 + 3 + import ( 4 + "encoding/json" 5 + "strings" 6 + "testing" 7 + 8 + "github.com/quailyquaily/mistermorph/llm" 9 + ) 10 + 11 + func TestRenderIntentPrompts_UserPayloadIsJSON(t *testing.T) { 12 + task := "Hi" 13 + history := []llm.Message{ 14 + {Role: "user", Content: "hello"}, 15 + {Role: "assistant", Content: "hi"}, 16 + } 17 + 18 + sys, user, err := renderIntentPrompts(task, history) 19 + if err != nil { 20 + t.Fatalf("renderIntentPrompts() error = %v", err) 21 + } 22 + if !strings.Contains(sys, "Return ONLY JSON") { 23 + t.Fatalf("system prompt missing schema guard: %q", sys) 24 + } 25 + 26 + var payload struct { 27 + Task string `json:"task"` 28 + History []llm.Message `json:"history"` 29 + Rules []string `json:"rules"` 30 + } 31 + if err := json.Unmarshal([]byte(user), &payload); err != nil { 32 + t.Fatalf("user prompt is not valid json: %v", err) 33 + } 34 + if payload.Task != task { 35 + t.Fatalf("payload.task = %q, want %q", payload.Task, task) 36 + } 37 + if len(payload.History) != len(history) { 38 + t.Fatalf("payload.history len = %d, want %d", len(payload.History), len(history)) 39 + } 40 + if len(payload.Rules) == 0 { 41 + t.Fatalf("payload.rules is empty") 42 + } 43 + if !strings.Contains(strings.Join(payload.Rules, "\n"), "question and request are independent") { 44 + t.Fatalf("payload.rules missing expected rule") 45 + } 46 + }
+2
agent/prompts/intent_system.tmpl
··· 1 + You infer user intent. Return ONLY JSON with keys: 2 + goal (string), deliverable (string), constraints (array of strings), ambiguities (array of strings), question (boolean), request (boolean), ask (boolean).
+20
agent/prompts/intent_user.tmpl
··· 1 + { 2 + "task": {{toJSON .Task}}, 3 + "history": {{toJSON .History}}, 4 + "rules": [ 5 + "Return a compact, structured intent summary.", 6 + "Use the same language as the user for values.", 7 + "goal: the user's true objective (not the literal request).", 8 + "deliverable: the minimum acceptable output form (e.g., list of concrete items, decision, plan, code diff).", 9 + "constraints: explicit constraints like time range, quantity, sources, format, language.", 10 + "ambiguities: only material uncertainties that block a good answer.", 11 + "question: true if the user message asks a question (explicit or implicit).", 12 + "request: true if the user message asks the assistant to perform an action or produce an output.", 13 + "question and request are independent; both may be true.", 14 + "ask: default false; set true only if you cannot proceed safely or would risk irreversible harm without clarification.", 15 + "Prefer proceeding with stated assumptions over asking questions.", 16 + "Do not invent constraints or facts.", 17 + "Do not copy these instruction bullets into output fields.", 18 + "Do not include meta instructions about intent formatting in constraints." 19 + ] 20 + }
+1 -1
assets/config/config.example.yaml
··· 237 237 # If true, run a lightweight intent inference before the main task. 238 238 enabled: true 239 239 # Max history messages to include for intent inference. 240 - max_history: 8 240 + max_history: 3 241 241 # Per-request timeout for intent inference. 242 242 timeout: "8s" 243 243
+5 -1
docs/prompt.md
··· 90 90 ### 1) Intent inference 91 91 92 92 - File/Function: `agent/intent.go` / `InferIntent(...)` 93 + - Templates: 94 + - `agent/prompts/intent_system.tmpl` 95 + - `agent/prompts/intent_user.tmpl` 96 + - Renderer: `agent/intent_template.go` (via `internal/prompttmpl`) 93 97 - Purpose: infer structured user intent and ambiguity level 94 - - Primary input: current `task`, trimmed recent `history`, intent extraction rules 98 + - Primary input: current `task`, trimmed recent `history`(规则由模板内置) 95 99 - Output: `Intent{goal, deliverable, constraints, ambiguities, ask}` 96 100 - JSON required: **Yes** (`ForceJSON=true`) 97 101