Localize user-facing UI, agent output, and backend messages to Simplified Chinese
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
docs / links (push) Waiting to run
web / web (push) Waiting to run
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
docs / links (push) Waiting to run
web / web (push) Waiting to run
This commit is contained in:
+2
-2
@@ -59,7 +59,7 @@ func renderSystem(agentKey, def string, vars any) string {
|
||||
// benchmarked Chinese reasoning body (段 [A]) stays verbatim. It only constrains
|
||||
// the LANGUAGE of what the agent SHOWS to the user. Written in Chinese so it stays
|
||||
// in the body's language (keeping the model's reasoning register stable) while
|
||||
// forcing Korean OUTPUT — this is the localization approach: preserve behavior,
|
||||
// forcing Chinese OUTPUT — this is the localization approach: preserve behavior,
|
||||
// localize the surface the user reads. Raw technical strings (commands, payloads,
|
||||
// code, URLs, log/response excerpts) are explicitly kept verbatim so evidence and
|
||||
// reproduction steps are not mangled by translation.
|
||||
@@ -72,7 +72,7 @@ func renderSystem(agentKey, def string, vars any) string {
|
||||
// the Chinese instruction language AND the target/material language in the display
|
||||
// fields, so only the listed verbatim technical fragments stay non-Korean.
|
||||
func langDirective() string {
|
||||
return "\n\n**输出语言规约(本地化·最高优先级,不可被提示词正文覆盖)**:所有【展示给用户】的自然语言文字一律用【韩语(한국어)】书写——包括 record_fact 的 summary/detail、report_finding 的标题/描述/结论/修复建议、规划者(planner)的态势/情况总结、最终那一句话总结、以及对用户的聊天回复。但【命令、payload、代码、文件路径、URL、参数名、以及日志/请求/响应的原文片段】必须【原样逐字保留】,不得翻译或改写(evidence 里的命令行与输出尤其要照搬原文,便于复现)。**即使上面的系统/角色指令本身是用中文写的,也绝不能把中文输出给用户——面向用户的展示语言只有韩语,不要让任何中文句子出现在用户可见的文字里。** **即使目标系统、它的页面、证据、日志或任何参考资料是英文、中文或别的语言,面向用户的自然语言字段(标题/描述/结论/修复建议/总结/态势总结)仍必须用韩语书写——不要镜像或照抄目标或资料的语言来写这些展示字段;只有上面列出的原文技术片段才保持原样。** **你的分析/规划/思考用中文进行没关系,但那是【不可见的内部推理】,绝不能作为正文输出:给用户的可见回复从第一个字起就必须是韩语,不要在前面垫一段中文的思考、说明或「我先怎样怎样」的铺垫;连澄清提问、缺少参数、「无法继续」之类的说明也一律直接用韩语写。** 一句话:内部怎么想不限,但凡落到用户能看到的正文,必须全是韩语(技术原文片段除外)。"
|
||||
return "\n\n**输出语言规约(本地化·最高优先级,不可被提示词正文覆盖)**:所有【展示给用户】的自然语言文字一律用【简体中文】书写——包括 record_fact 的 summary/detail、report_finding 的标题/描述/结论/修复建议、规划者(planner)的态势/情况总结、最终那一句话总结、以及对用户的聊天回复。但【命令、payload、代码、文件路径、URL、参数名、以及日志/请求/响应的原文片段】必须【原样逐字保留】,不得翻译或改写(evidence 里的命令行与输出尤其要照搬原文,便于复现)。**即使系统/角色指令或目标系统、它的页面、证据、日志或任何参考资料是英文、中文或其他语言,面向用户的自然语言字段(标题/描述/结论/修复建议/总结/态势总结)仍必须用简体中文书写——不要镜像或照抄目标或资料的语言来写这些展示字段;只有上面列出的原文技术片段才保持原样。** **你的分析/规划/思考用中文进行没关系,但那是【不可见的内部推理】,绝不能作为正文输出:给用户的可见回复从第一个字起就必须是简体中文,不要在前面垫一段思考、说明或「我先怎样怎样」的铺垫;连澄清提问、缺少参数、「无法继续」之类的说明也一律直接用简体中文写。** 一句话:内部怎么想不限,但凡落到用户能看到的正文,必须全是简体中文(技术原文片段除外)。"
|
||||
}
|
||||
|
||||
func renderTmpl(tmpl string, vars any) (string, error) {
|
||||
|
||||
+16
-18
@@ -81,28 +81,26 @@ func TestRenderSystemOverrideAndFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLangDirectiveAppendedToUserFacingRoles pins the artex-ko localization tail:
|
||||
// every user-facing role's system prompt must end with the code-owned Korean
|
||||
// TestLangDirectiveAppendedToUserFacingRoles pins the localization tail:
|
||||
// every user-facing role's system prompt must end with the code-owned Chinese
|
||||
// output-language directive, and a DB-edited body must NOT be able to drop it.
|
||||
func TestLangDirectiveAppendedToUserFacingRoles(t *testing.T) {
|
||||
t.Cleanup(func() { PromptOverride = nil })
|
||||
|
||||
// The directive forces Korean OUTPUT and preserves raw technical strings; both
|
||||
// signals must be present. 한국어 marker + verbatim-preservation clause.
|
||||
// The directive forces Chinese OUTPUT and preserves raw technical strings; both
|
||||
// signals must be present. 简体中文 marker + verbatim-preservation clause.
|
||||
dir := langDirective()
|
||||
if !strings.Contains(dir, "한국어") {
|
||||
t.Fatalf("langDirective must force Korean output, got %q", dir)
|
||||
if !strings.Contains(dir, "简体中文") {
|
||||
t.Fatalf("langDirective must force Chinese output, got %q", dir)
|
||||
}
|
||||
if !strings.Contains(dir, "payload") || !strings.Contains(dir, "原样逐字保留") {
|
||||
t.Fatalf("langDirective must keep commands/payloads verbatim, got %q", dir)
|
||||
}
|
||||
// L1 anti-drift hardening: the directive must (1) forbid leaking the Chinese
|
||||
// instruction/brain language into user-facing text (planner situation-summary
|
||||
// drift), and (2) forbid mirroring the target/material language — e.g. an
|
||||
// English target app — in the display fields (report_finding drift). Both
|
||||
// clauses are locked here so a future edit can't silently drop them.
|
||||
if !strings.Contains(dir, "也绝不能把中文输出给用户") {
|
||||
t.Fatalf("langDirective must forbid leaking Chinese to the user, got %q", dir)
|
||||
// The directive is highest priority (cannot be overridden by the body) and must
|
||||
// forbid mirroring the target/material language — e.g. an English target app —
|
||||
// in the display fields.
|
||||
if !strings.Contains(dir, "不可被提示词正文覆盖") {
|
||||
t.Fatalf("langDirective must be top priority, got %q", dir)
|
||||
}
|
||||
if !strings.Contains(dir, "不要镜像或照抄目标") {
|
||||
t.Fatalf("langDirective must forbid mirroring the target/material language, got %q", dir)
|
||||
@@ -113,7 +111,7 @@ func TestLangDirectiveAppendedToUserFacingRoles(t *testing.T) {
|
||||
|
||||
// Even with a DB body that is pure non-directive text, the code-owned tail is
|
||||
// still appended for each user-facing builder — identical guarantee to the
|
||||
// artifact tail. A custom body can never translate away the Korean mandate.
|
||||
// artifact tail. A custom body can never translate away the Chinese mandate.
|
||||
PromptOverride = func(string) (string, bool) { return "BODY-ONLY", true }
|
||||
cases := map[string]string{
|
||||
"worker": workerSystem("", "", "/data", "/data"),
|
||||
@@ -122,7 +120,7 @@ func TestLangDirectiveAppendedToUserFacingRoles(t *testing.T) {
|
||||
"chat": chatSystem("chat", "/data", "/data"),
|
||||
// goals is user-facing too: set_goals/set_constraints persist goal and
|
||||
// constraint nodes shown in the UI graph/plan tab. withScope=true exercises
|
||||
// the longer assembly (body + scope tail), so the Korean tail must still land
|
||||
// the longer assembly (body + scope tail), so the Chinese tail must still land
|
||||
// last — after both the body and the code-owned scope tail.
|
||||
"goals": goalsSystem("/data", true),
|
||||
}
|
||||
@@ -130,11 +128,11 @@ func TestLangDirectiveAppendedToUserFacingRoles(t *testing.T) {
|
||||
if !strings.HasPrefix(sys, "BODY-ONLY") {
|
||||
t.Fatalf("%s: DB body not honored: %q", role, sys)
|
||||
}
|
||||
if !strings.Contains(sys, "한국어") {
|
||||
t.Fatalf("%s: missing Korean output-language tail: %q", role, sys)
|
||||
if !strings.Contains(sys, "简体中文") {
|
||||
t.Fatalf("%s: missing Chinese output-language tail: %q", role, sys)
|
||||
}
|
||||
// The directive is the tail — it must come AFTER the body (recency).
|
||||
if strings.Index(sys, "한국어") <= strings.Index(sys, "BODY-ONLY") {
|
||||
if strings.Index(sys, "简体中文") <= strings.Index(sys, "BODY-ONLY") {
|
||||
t.Fatalf("%s: langDirective must be appended after the body: %q", role, sys)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func TestSideActualChatCheckpointToolResultAndTranscriptIsolation(t *testing.T)
|
||||
p.calls = 0
|
||||
p.path = filepath.Join(dir, "nonexistent")
|
||||
answer, err := (sidequestion.SideQuestionService{Provider: bound}).Answer(t.Context(), req, streaming, nil)
|
||||
if err != nil || !answer.ToolUse || p.calls != 1 || !strings.Contains(answer.Text, "도구 작업을 실행할 수 없습니다") {
|
||||
if err != nil || !answer.ToolUse || p.calls != 1 || !strings.Contains(answer.Text, "无法执行工具操作") {
|
||||
t.Fatalf("tool denial %+v %v", answer, err)
|
||||
}
|
||||
after, err := os.ReadFile(store.MainPath("conv-987654"))
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ func (w *Worker) SetRunTimeout(run time.Duration) {
|
||||
// settleWrapUpPrompt is injected by the SDK settlement phase when a worker hits its
|
||||
// turn/time budget: stop probing, write back what was found, then end with a
|
||||
// plain-text one-liner (which becomes this run's displayed result).
|
||||
const settleWrapUpPrompt = "이번 실행이 예산 소진으로 곧 종료됩니다. 더 이상 어떤 명령이나 탐지도 실행하지 마십시오. 다음 순서대로 처리하십시오. (1) 위에서 이미 식별했지만 아직 기록하지 않은 내용을 하나씩 기록합니다. 새 자산은 insert_assets, 탐색 결론과 사실은 record_fact, 확인된 취약점은 report_finding 으로 기록합니다. (2) **맨 마지막에 한 문장짜리 순수 텍스트로만** 무엇을 했고 어떤 핵심 결론을 얻었는지 한국어로 요약합니다. 이 한 문장이 이번 실행의 결과로 사용자에게 표시되므로 반드시 출력해야 합니다."
|
||||
const settleWrapUpPrompt = "本次运行因预算耗尽即将结束。不要再执行任何命令或探测。请按以下顺序处理:(1) 把上面已识别但尚未记录的内容逐条记录。新资产用 insert_assets,探索结论与事实用 record_fact,确认的漏洞用 report_finding 记录。(2) **最后只用一句纯文本**用简体中文总结你做了什么、得到什么核心结论。这一句会作为本次运行的结果显示给用户,必须输出。"
|
||||
|
||||
func NewWorker(prov llm.Provider, model, workDir string, tx *transcript.Store, window, maxTurns int, extra ...actool.CoreTool) *Worker {
|
||||
return &Worker{prov: prov, model: model, workDir: workDir, tx: tx, window: window, maxTurns: maxTurns, extraTools: extra}
|
||||
|
||||
+5
-5
@@ -41,11 +41,11 @@ var wrapupTurnDefaults = map[string]int{
|
||||
|
||||
const genericWrapupTurns = 10
|
||||
|
||||
const plannerWrapUpDefault = "이번 계획 라운드의 단계 예산이 곧 소진됩니다. 다만 【이번 라운드】만 끝나는 것이며, 시스템은 이후에도 상황 변화에 따라 당신을 다시 깨워 계획을 이어가게 합니다. 작업 자체가 끝나는 것이 아니므로 여기서 전체 계획을 마무리 지을 필요는 없습니다. 이번 라운드에서 이미 명확히 판단한 결론은 실제로 반영하여 이번 라운드가 헛되지 않게 하되, 【마무리를 위해 억지로 의도를 지어내지는】 마십시오(이번 라운드에 의도가 0개인 것도 완전히 정상적인 결과입니다). (1) 【지금 바로 파견해야 할】 탐색 방향을 이미 판단했다면 add_intent 한 번으로 묶어서 제출합니다(이미 정한 것은 묵히지 말고 바로 보냅니다). (2) 어떤 발견이나 사실로 이미 달성이 증명된 목표는 prove_goal 로 met 표시합니다(빠뜨리지 마십시오). (3) 단계를 나눠야 하는 직렬 익스플로잇 체인을 식별했다면 TodoWrite 로 기록하여 다음에 깨어났을 때 이어서 파견할 수 있게 합니다. 다 끝냈으면 이번 라운드를 바로 종료하며, 요약 텍스트는 출력하지 않습니다."
|
||||
const plannerWrapUpDefault = "本轮计划的步骤预算即将耗尽。不过仅仅【本轮】结束,系统之后仍会随情况变化再次唤醒你继续规划。这不是整个任务结束,因此这里无需为整体计划收尾。本轮已明确判断的结论应实际落实,使本轮不白费,但【不要为了收尾而勉强编造意图】(本轮意图为 0 个也是完全正常的结果)。(1) 若已判断出【现在就该派发】的探索方向,用一次 add_intent 打包提交(已确定的不要压着,立即发出)。(2) 已被某个发现或事实证明达成的目标,用 prove_goal 标记为 met(不要遗漏)。(3) 若识别出需要分步的串行利用链,用 TodoWrite 记录,以便下次唤醒时继续派发。全部完成后直接结束本轮,不输出总结文本。"
|
||||
|
||||
const mainAgentWrapUpDefault = "단계 예산이 곧 소진되어 이번 상호작용이 끝나려 합니다. 더 이상 새로운 탐색이나 조작을 시작하지 마십시오. **한 문장짜리 순수 텍스트로만** 현재 진행 상황, 핵심 결론, 그리고 권장하는 다음 단계를 사용자에게 한국어로 요약하십시오."
|
||||
const mainAgentWrapUpDefault = "步骤预算即将耗尽,本次交互即将结束。不要再开始任何新的探索或操作。**只用一句纯文本**用简体中文向用户总结当前进展、核心结论以及建议的下一步。"
|
||||
|
||||
const genericWrapUpDefault = "예산 소진으로 곧 종료됩니다. 먼저 완료했지만 아직 저장하지 않은 결과를 기록한 뒤, **한 문장짜리 순수 텍스트로만** 무엇을 했고 어떤 핵심 결론을 얻었는지 한국어로 요약하십시오(이 한 문장이 이번 실행의 결과로 표시됩니다)."
|
||||
const genericWrapUpDefault = "预算耗尽,即将结束。先记录已完成但尚未保存的结果,然后**只用一句纯文本**用简体中文总结你做了什么、得到什么核心结论(这一句将作为本次运行的结果显示)。"
|
||||
|
||||
// WrapupDefault returns the built-in default wrap-up prompt for an agent key —
|
||||
// used by the admin UI as the "restore default" value and empty-field placeholder.
|
||||
@@ -117,9 +117,9 @@ var taskTimeoutWrapupDefaults = map[string]string{
|
||||
"planner": plannerTaskTimeoutDefault,
|
||||
}
|
||||
|
||||
const workerTaskTimeoutDefault = "**전체 작업이 시간 제한 상한에 도달하여 곧 종료됩니다**(이번 run 의 예산이 아니라 탐색 전체가 끝나는 시점입니다). 이것이 마지막 기회입니다. (1) 이미 식별했지만 아직 기록하지 않은 내용을 【전부】 저장합니다. 새 자산은 insert_assets, 탐색 결론과 사실은 record_fact, 확인된 취약점은 report_finding 으로 기록합니다. (2) 더 이상 어떤 새 명령이나 탐지도 시작하지 마십시오. (3) **맨 마지막에 한 문장짜리 순수 텍스트로만** 이번 의도에서 얻은 핵심 결론을 한국어로 요약하십시오."
|
||||
const workerTaskTimeoutDefault = "**整个任务已达到时间上限,即将结束**(不是本次 run 的预算,而是整个探索结束的时刻)。这是最后一次机会。(1) 把已识别但尚未记录的内容【全部】保存。新资产用 insert_assets,探索结论与事实用 record_fact,确认的漏洞用 report_finding 记录。(2) 不要再开始任何新命令或探测。(3) **最后只用一句纯文本**用简体中文总结本次意图得到的核心结论。"
|
||||
|
||||
const plannerTaskTimeoutDefault = "**전체 작업이 시간 제한 상한에 도달하여 곧 종료됩니다**(이번 라운드가 아니라 작업 전체가 종료됩니다). 현재까지의 【모든】 사실과 발견을 근거로 마지막 목표 판정을 수행하십시오. 증거로 달성이 증명된 목표는 prove_goal 로 met 표시합니다(빠뜨리지 마십시오). **더 이상 어떤 새 의도도 생성하지 마십시오**(지금 의도를 파견해도 더 이상 실행되지 않습니다). 판정을 마치면 바로 종료하며, 요약 텍스트는 출력하지 않습니다."
|
||||
const plannerTaskTimeoutDefault = "**整个任务已达到时间上限,即将结束**(不是本轮,而是整个任务结束)。依据目前为止的【全部】事实与发现做最后一次目标判定。证据已证明达成的目标用 prove_goal 标记为 met(不要遗漏)。**不要再生成任何新意图**(现在派发意图也不会再被执行)。判定完成后立即结束,不输出总结文本。"
|
||||
|
||||
// TaskTimeoutWrapupDefault 返回某 agent 的任务超时内置默认收尾词(供后台占位/恢复默认)。
|
||||
func TaskTimeoutWrapupDefault(agentKey string) string {
|
||||
|
||||
+13
-13
@@ -6,17 +6,17 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// A2: wrap-up / settlement 프롬프트 한국어화.
|
||||
// A2: wrap-up / settlement 프롬프트 중국어화.
|
||||
//
|
||||
// 이 상수들은 run 또는 task 가 단계/시간 예산에 걸려 종료될 때 settlement 단계에서
|
||||
// 주입되어, 사용자에게 그대로 노출되는 최종 요약을 직접 지시한다. 따라서 (1) 한국어로
|
||||
// 작성되어야 하고, (2) 중국어(CJK 한자) 잔재가 없어야 하며, (3) 도구 이름과
|
||||
// 주입되어, 사용자에게 그대로 노출되는 최종 요약을 직접 지시한다. 따라서 (1) 중국어로
|
||||
// 작성되어야 하고, (2) 한글(Hangul) 잔재가 없어야 하며, (3) 도구 이름과
|
||||
// "한 문장 순수 텍스트" 같은 지시 의미가 보존되어야 한다.
|
||||
//
|
||||
// DB 시드는 wrapup_prompt / task_timeout_wrapup_prompt 를 빈 문자열로 두고(db/db.go 의
|
||||
// builtin 에이전트 INSERT 는 이 컬럼을 채우지 않는다), 비어 있으면 이 상수로 떨어진다.
|
||||
// 즉 이 상수들이 wrap-up 문구의 유일한 원천이다.
|
||||
func TestWrapupPromptsLocalizedToKorean(t *testing.T) {
|
||||
func TestWrapupPromptsLocalizedToChinese(t *testing.T) {
|
||||
all := map[string]string{
|
||||
"settleWrapUpPrompt": settleWrapUpPrompt,
|
||||
"plannerWrapUpDefault": plannerWrapUpDefault,
|
||||
@@ -36,12 +36,12 @@ func TestWrapupPromptsLocalizedToKorean(t *testing.T) {
|
||||
}
|
||||
|
||||
for name, p := range all {
|
||||
if !hasScript(p, unicode.Hangul) {
|
||||
t.Errorf("%s: 한글이 전혀 없어 한국어화되지 않았다", name)
|
||||
if !hasScript(p, unicode.Han) {
|
||||
t.Errorf("%s: 한자가 전혀 없어 중국어화되지 않았다", name)
|
||||
}
|
||||
// 도구 이름은 ASCII, 한글은 Hangul 블록이라 번역이 끝났다면 CJK 한자가 하나도 없어야 한다.
|
||||
if hasScript(p, unicode.Han) {
|
||||
t.Errorf("%s: CJK 한자 잔재가 남아 번역이 미완이다: %q", name, p)
|
||||
// 도구 이름은 ASCII 이므로, 번역이 끝났다면 한글(Hangul)이 하나도 없어야 한다.
|
||||
if hasScript(p, unicode.Hangul) {
|
||||
t.Errorf("%s: 한글 잔재가 남아 번역이 미완이다: %q", name, p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ func TestWrapupPromptsLocalizedToKorean(t *testing.T) {
|
||||
}
|
||||
}
|
||||
mustContain("settleWrapUpPrompt", settleWrapUpPrompt,
|
||||
"insert_assets", "record_fact", "report_finding", "한 문장", "순수 텍스트")
|
||||
"insert_assets", "record_fact", "report_finding", "一句", "纯文本")
|
||||
mustContain("workerTaskTimeoutDefault", workerTaskTimeoutDefault,
|
||||
"insert_assets", "record_fact", "report_finding", "한 문장", "순수 텍스트")
|
||||
mustContain("genericWrapUpDefault", genericWrapUpDefault, "한 문장", "순수 텍스트")
|
||||
mustContain("mainAgentWrapUpDefault", mainAgentWrapUpDefault, "한 문장", "순수 텍스트")
|
||||
"insert_assets", "record_fact", "report_finding", "一句", "纯文本")
|
||||
mustContain("genericWrapUpDefault", genericWrapUpDefault, "一句", "纯文本")
|
||||
mustContain("mainAgentWrapUpDefault", mainAgentWrapUpDefault, "一句", "纯文本")
|
||||
// planner 는 요약 문장을 내지 않고(판정만 하고 종료) 의도·목표·할일 도구를 유지한다.
|
||||
mustContain("plannerWrapUpDefault", plannerWrapUpDefault, "add_intent", "prove_goal", "TodoWrite")
|
||||
mustContain("plannerTaskTimeoutDefault", plannerTaskTimeoutDefault, "prove_goal")
|
||||
|
||||
Reference in New Issue
Block a user