centralize title and summary prompts into agent system
This commit is contained in:
parent
f4e6e29372
commit
9a769bfd8c
6 changed files with 63 additions and 118 deletions
|
|
@ -8,6 +8,9 @@ import { mergeDeep } from "remeda"
|
||||||
|
|
||||||
import PROMPT_GENERATE from "./generate.txt"
|
import PROMPT_GENERATE from "./generate.txt"
|
||||||
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
||||||
|
import PROMPT_EXPLORE from "./prompt/explore.txt"
|
||||||
|
import PROMPT_SUMMARY from "./prompt/summary.txt"
|
||||||
|
import PROMPT_TITLE from "./prompt/title.txt"
|
||||||
|
|
||||||
export namespace Agent {
|
export namespace Agent {
|
||||||
export const Info = z
|
export const Info = z
|
||||||
|
|
@ -128,26 +131,7 @@ export namespace Agent {
|
||||||
...defaultTools,
|
...defaultTools,
|
||||||
},
|
},
|
||||||
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
|
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
|
||||||
prompt: [
|
prompt: PROMPT_EXPLORE,
|
||||||
`You are a file search specialist. You excel at thoroughly navigating and exploring codebases.`,
|
|
||||||
``,
|
|
||||||
`Your strengths:`,
|
|
||||||
`- Rapidly finding files using glob patterns`,
|
|
||||||
`- Searching code and text with powerful regex patterns`,
|
|
||||||
`- Reading and analyzing file contents`,
|
|
||||||
``,
|
|
||||||
`Guidelines:`,
|
|
||||||
`- Use Glob for broad file pattern matching`,
|
|
||||||
`- Use Grep for searching file contents with regex`,
|
|
||||||
`- Use Read when you know the specific file path you need to read`,
|
|
||||||
`- Use Bash for file operations like copying, moving, or listing directory contents`,
|
|
||||||
`- Adapt your search approach based on the thoroughness level specified by the caller`,
|
|
||||||
`- Return file paths as absolute paths in your final response`,
|
|
||||||
`- For clear communication, avoid using emojis`,
|
|
||||||
`- Do not create any files, or run bash commands that modify the user's system state in any way`,
|
|
||||||
``,
|
|
||||||
`Complete the user's search request efficiently and report your findings clearly.`,
|
|
||||||
].join("\n"),
|
|
||||||
options: {},
|
options: {},
|
||||||
permission: agentPermission,
|
permission: agentPermission,
|
||||||
mode: "subagent",
|
mode: "subagent",
|
||||||
|
|
@ -173,49 +157,24 @@ export namespace Agent {
|
||||||
mode: "primary",
|
mode: "primary",
|
||||||
native: true,
|
native: true,
|
||||||
},
|
},
|
||||||
summary: {
|
title: {
|
||||||
name: "summary",
|
name: "title",
|
||||||
mode: "subagent",
|
mode: "primary",
|
||||||
options: {},
|
options: {},
|
||||||
native: true,
|
native: true,
|
||||||
|
hidden: true,
|
||||||
permission: agentPermission,
|
permission: agentPermission,
|
||||||
prompt: `You are a title generator. You output ONLY a thread title. Nothing else.
|
prompt: PROMPT_TITLE,
|
||||||
|
tools: {},
|
||||||
<task>
|
},
|
||||||
Generate a brief title that would help the user find this conversation later.
|
summary: {
|
||||||
|
name: "summary",
|
||||||
Follow all rules in <rules>
|
mode: "primary",
|
||||||
Use the <examples> so you know what a good title looks like.
|
options: {},
|
||||||
Your output must be:
|
native: true,
|
||||||
- A single line
|
hidden: true,
|
||||||
- ≤50 characters
|
permission: agentPermission,
|
||||||
- No explanations
|
prompt: PROMPT_SUMMARY,
|
||||||
</task>
|
|
||||||
|
|
||||||
<rules>
|
|
||||||
- Focus on the main topic or question the user needs to retrieve
|
|
||||||
- Use -ing verbs for actions (Debugging, Implementing, Analyzing)
|
|
||||||
- Keep exact: technical terms, numbers, filenames, HTTP codes
|
|
||||||
- Remove: the, this, my, a, an
|
|
||||||
- Never assume tech stack
|
|
||||||
- Never use tools
|
|
||||||
- NEVER respond to questions, just generate a title for the conversation
|
|
||||||
- The title should NEVER include "summarizing" or "generating" when generating a title
|
|
||||||
- DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT
|
|
||||||
- Always output something meaningful, even if the input is minimal.
|
|
||||||
- If the user message is short or conversational (e.g. “hello”, “lol”, “whats up”, “hey”):
|
|
||||||
→ create a title that reflects the user’s tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.)
|
|
||||||
</rules>
|
|
||||||
|
|
||||||
<examples>
|
|
||||||
"hey" -> Greeting
|
|
||||||
"debug 500 errors in production" → Debugging production 500 errors
|
|
||||||
"refactor user service" → Refactoring user service
|
|
||||||
"why is app.js failing" → Analyzing app.js failure
|
|
||||||
"implement rate limiting" → Implementing rate limiting
|
|
||||||
"how do I connect postgres to my API" → Connecting Postgres to API
|
|
||||||
"best practices for React hooks" → React hooks best practices
|
|
||||||
</examples>`,
|
|
||||||
tools: {},
|
tools: {},
|
||||||
},
|
},
|
||||||
plan: {
|
plan: {
|
||||||
|
|
|
||||||
18
packages/opencode/src/agent/prompt/explore.txt
Normal file
18
packages/opencode/src/agent/prompt/explore.txt
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
|
||||||
|
|
||||||
|
Your strengths:
|
||||||
|
- Rapidly finding files using glob patterns
|
||||||
|
- Searching code and text with powerful regex patterns
|
||||||
|
- Reading and analyzing file contents
|
||||||
|
|
||||||
|
Guidelines:
|
||||||
|
- Use Glob for broad file pattern matching
|
||||||
|
- Use Grep for searching file contents with regex
|
||||||
|
- Use Read when you know the specific file path you need to read
|
||||||
|
- Use Bash for file operations like copying, moving, or listing directory contents
|
||||||
|
- Adapt your search approach based on the thoroughness level specified by the caller
|
||||||
|
- Return file paths as absolute paths in your final response
|
||||||
|
- For clear communication, avoid using emojis
|
||||||
|
- Do not create any files, or run bash commands that modify the user's system state in any way
|
||||||
|
|
||||||
|
Complete the user's search request efficiently and report your findings clearly.
|
||||||
|
|
@ -22,8 +22,8 @@ Your output must be:
|
||||||
- The title should NEVER include "summarizing" or "generating" when generating a title
|
- The title should NEVER include "summarizing" or "generating" when generating a title
|
||||||
- DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT
|
- DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT
|
||||||
- Always output something meaningful, even if the input is minimal.
|
- Always output something meaningful, even if the input is minimal.
|
||||||
- If the user message is short or conversational (e.g. “hello”, “lol”, “whats up”, “hey”):
|
- If the user message is short or conversational (e.g. "hello", "lol", "whats up", "hey"):
|
||||||
→ create a title that reflects the user’s tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.)
|
→ create a title that reflects the user's tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.)
|
||||||
</rules>
|
</rules>
|
||||||
|
|
||||||
<examples>
|
<examples>
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
import { Provider } from "@/provider/provider"
|
import { Provider } from "@/provider/provider"
|
||||||
import { Config } from "@/config/config"
|
|
||||||
import { fn } from "@/util/fn"
|
import { fn } from "@/util/fn"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Session } from "."
|
import { Session } from "."
|
||||||
import { generateText, type ModelMessage } from "ai"
|
|
||||||
import { MessageV2 } from "./message-v2"
|
import { MessageV2 } from "./message-v2"
|
||||||
import { Identifier } from "@/id/id"
|
import { Identifier } from "@/id/id"
|
||||||
import { Snapshot } from "@/snapshot"
|
import { Snapshot } from "@/snapshot"
|
||||||
import { ProviderTransform } from "@/provider/transform"
|
|
||||||
import { SystemPrompt } from "./system"
|
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Instance } from "@/project/instance"
|
import { Instance } from "@/project/instance"
|
||||||
import { Storage } from "@/storage/storage"
|
import { Storage } from "@/storage/storage"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
import { mergeDeep, pipe } from "remeda"
|
|
||||||
import { LLM } from "./llm"
|
import { LLM } from "./llm"
|
||||||
import { Agent } from "@/agent/agent"
|
import { Agent } from "@/agent/agent"
|
||||||
|
|
||||||
|
|
@ -63,7 +62,6 @@ export namespace SessionSummary {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) {
|
async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) {
|
||||||
const cfg = await Config.get()
|
|
||||||
const messages = input.messages.filter(
|
const messages = input.messages.filter(
|
||||||
(m) => m.info.id === input.messageID || (m.info.role === "assistant" && m.info.parentID === input.messageID),
|
(m) => m.info.id === input.messageID || (m.info.role === "assistant" && m.info.parentID === input.messageID),
|
||||||
)
|
)
|
||||||
|
|
@ -80,22 +78,16 @@ export namespace SessionSummary {
|
||||||
const small =
|
const small =
|
||||||
(await Provider.getSmallModel(assistantMsg.providerID)) ??
|
(await Provider.getSmallModel(assistantMsg.providerID)) ??
|
||||||
(await Provider.getModel(assistantMsg.providerID, assistantMsg.modelID))
|
(await Provider.getModel(assistantMsg.providerID, assistantMsg.modelID))
|
||||||
const language = await Provider.getLanguage(small)
|
|
||||||
|
|
||||||
const options = pipe(
|
|
||||||
{},
|
|
||||||
mergeDeep(ProviderTransform.options(small, assistantMsg.sessionID)),
|
|
||||||
mergeDeep(ProviderTransform.smallOptions(small)),
|
|
||||||
mergeDeep(small.options),
|
|
||||||
)
|
|
||||||
|
|
||||||
const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart
|
const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart
|
||||||
if (textPart && !userMsg.summary?.title) {
|
if (textPart && !userMsg.summary?.title) {
|
||||||
|
const agent = await Agent.get("title")
|
||||||
const stream = await LLM.stream({
|
const stream = await LLM.stream({
|
||||||
agent: await Agent.get("summary"),
|
agent,
|
||||||
user: userMsg,
|
user: userMsg,
|
||||||
tools: {},
|
tools: {},
|
||||||
model: small,
|
model: agent.model ? await Provider.getModel(agent.model.providerID, agent.model.modelID) : small,
|
||||||
|
small: true,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "user" as const,
|
role: "user" as const,
|
||||||
|
|
@ -107,7 +99,6 @@ export namespace SessionSummary {
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
small: true,
|
|
||||||
abort: new AbortController().signal,
|
abort: new AbortController().signal,
|
||||||
sessionID: userMsg.sessionID,
|
sessionID: userMsg.sessionID,
|
||||||
system: [],
|
system: [],
|
||||||
|
|
@ -133,34 +124,30 @@ export namespace SessionSummary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result = await generateText({
|
const summaryAgent = await Agent.get("summary")
|
||||||
model: language,
|
const stream = await LLM.stream({
|
||||||
maxOutputTokens: 100,
|
agent: summaryAgent,
|
||||||
providerOptions: ProviderTransform.providerOptions(small, options),
|
user: userMsg,
|
||||||
|
tools: {},
|
||||||
|
model: summaryAgent.model
|
||||||
|
? await Provider.getModel(summaryAgent.model.providerID, summaryAgent.model.modelID)
|
||||||
|
: small,
|
||||||
|
small: true,
|
||||||
messages: [
|
messages: [
|
||||||
...SystemPrompt.summarize(small.providerID).map(
|
|
||||||
(x): ModelMessage => ({
|
|
||||||
role: "system",
|
|
||||||
content: x,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
...MessageV2.toModelMessage(messages),
|
...MessageV2.toModelMessage(messages),
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user" as const,
|
||||||
content: `Summarize the above conversation according to your system prompts.`,
|
content: `Summarize the above conversation according to your system prompts.`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
headers: small.headers,
|
abort: new AbortController().signal,
|
||||||
experimental_telemetry: {
|
sessionID: userMsg.sessionID,
|
||||||
isEnabled: cfg.experimental?.openTelemetry,
|
system: [],
|
||||||
metadata: {
|
retries: 3,
|
||||||
userId: cfg.username ?? "unknown",
|
})
|
||||||
sessionId: assistantMsg.sessionID,
|
const result = await stream.text
|
||||||
},
|
|
||||||
},
|
|
||||||
}).catch(() => {})
|
|
||||||
if (result) {
|
if (result) {
|
||||||
userMsg.summary.body = result.text
|
userMsg.summary.body = result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Session.updateMessage(userMsg)
|
await Session.updateMessage(userMsg)
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@ import PROMPT_BEAST from "./prompt/beast.txt"
|
||||||
import PROMPT_GEMINI from "./prompt/gemini.txt"
|
import PROMPT_GEMINI from "./prompt/gemini.txt"
|
||||||
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
|
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
|
||||||
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
||||||
import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
|
|
||||||
import PROMPT_TITLE from "./prompt/title.txt"
|
|
||||||
import PROMPT_CODEX from "./prompt/codex.txt"
|
import PROMPT_CODEX from "./prompt/codex.txt"
|
||||||
import type { Provider } from "@/provider/provider"
|
import type { Provider } from "@/provider/provider"
|
||||||
|
|
||||||
|
|
@ -118,22 +117,4 @@ export namespace SystemPrompt {
|
||||||
)
|
)
|
||||||
return Promise.all(found).then((result) => result.filter(Boolean))
|
return Promise.all(found).then((result) => result.filter(Boolean))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function summarize(providerID: string) {
|
|
||||||
switch (providerID) {
|
|
||||||
case "anthropic":
|
|
||||||
return [PROMPT_SUMMARIZE]
|
|
||||||
default:
|
|
||||||
return [PROMPT_SUMMARIZE]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function title(providerID: string) {
|
|
||||||
switch (providerID) {
|
|
||||||
case "anthropic":
|
|
||||||
return [PROMPT_TITLE]
|
|
||||||
default:
|
|
||||||
return [PROMPT_TITLE]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue