多语言仓库里给补全定目标语言:一条提示词怎么写才不串 API
在单条提示词里锁定目标语言,最稳的做法不是“声明语言”,而是用文件路径 + 符号锚点 + 显式排除项把上下文边界钉死。语言声明本身几乎不会被模型稳定执行,真正起作用的是你喂进去的上下文长什么样、以及你让模型在什么“位置”开口。
下面是我在几个多语言仓库里反复调出来的写法,按有效性从高到低排。
用文件路径当第一锚点,而不是语言名称
模型对“这是一个 Go 文件”的判断,很大程度来自路径和扩展名,而不是你写一句“请用 Go”。所以提示词第一行应该直接给路径上下文,让模型自己推断语言,而不是你替它下结论。
实测有效度最高的是这种开头:
You are completing code in src/queue/worker.go (Go 1.22).
The repository also contains Python, TypeScript, and Rust code.
Do not import or reference APIs from those languages.
Complete only the Go function body below.
“Go 1.22”比“Go”更有效,因为版本号会进一步收窄标准库和语法假设。模型在补全 slices、maps、errors.Join 这类较新 API 时会少很多犹豫。
如果目标文件是 internal/api/handler.ts,那就写:
File: internal/api/handler.ts (TypeScript 5.4, Node.js runtime)
路径和扩展名本身就是最强的语言信号,比任何“请使用 TypeScript”都管用。
让模型“在符号内部”开始补全,而不是从空白处开始
多语言仓库里最容易串 API 的场景,是你给了一个函数签名,让模型从零写函数体。这时候模型会去“联想”它在训练数据里见过的相似函数,而训练数据里跨语言的相似命名函数太多了。
更稳的做法是:先给出目标语言里已经存在的、同文件或同包的符号引用,让模型在明确的“语言内引力场”里开始补。
比如:
File: pkg/store/cache.go (Go)
Existing symbols in this file: Get(ctx, key) ([]byte, error), Set(ctx, key, val, ttl) error
Complete the body of:
func (c *Client) GetOrSet(ctx context.Context, key string, ttl time.Duration, fallback func() ([]byte, error)) ([]byte, error) {
这里的 context.Context、time.Duration、func() ([]byte, error) 全都是 Go 的类型签名。模型看到这些,几乎没有空间去“漂移”到 Python 的 asyncio 或 TypeScript 的 Promise。符号锚点比语言声明强一个数量级。
如果目标文件里还没有可引用的符号,就从同仓库同语言的另一个文件里抽一个最小符号集放进去。两条 import 或一个类型定义,往往就够了。
显式写出“排除的语言和包路径”
这是最容易被忽略的一条。很多人只写“用 Go 补全”,却不写“不要用 Python/TypeScript 的 API”。在单语言仓库里这没必要,在多语言仓库里这是刚需。
原因很简单:多语言仓库的模型输入里,往往已经混进了其他语言的文件片段。如果 IDE 或工具自动把“相关文件”拼进了上下文,模型会自然地跨语言联想。你不排除,它就默认可以借用。
排除要具体到包名/模块名,而不是泛泛说“不要用其他语言”:
Do not use:
- Python: asyncio, aiohttp, requests, typing
- TypeScript: Promise, async/await, axios, node:fs
- Rust: tokio, serde, Result/Option
“不要用 Promise”比“不要用 TypeScript”有效得多,因为模型在 token 层面更容易执行“避开某个具体符号”,而不是执行“避开某个抽象语言类别”。
让目标语言“先开口”:给一句起始代码
如果补全场景允许,最直接的办法是在提示词里先给出目标语言的第一行代码,让模型接着写。这一行的语言信号密度极高,比任何自然语言声明都硬。
比如要补全一个 Python 函数,不要写:
Complete the function that fetches user data.
而是写:
File: scripts/fetch_user.py (Python 3.12)
Complete from here:
import httpx
def fetch_user(user_id: int) -> dict:
import httpx 和 -> dict 这两个 token 已经把语言锁死了。模型接下来补出的代码,几乎不可能跳到 TypeScript 的 fetch() 或 Go 的 http.Get。
同理,TypeScript 场景给一行 import { z } from "zod";,Go 场景给一行 resp, err := http.Get(url),Rust 场景给一行 let client = reqwest::Client::new();。一行起始代码胜过三行语言声明。
顺序敏感:排除项放在符号锚点之后
提示词里各部分的顺序不是随意的。我的经验是:路径 → 符号锚点 → 起始代码 → 排除项 → 补全指令。
排除项如果放在最前面,模型容易进入“防御模式”,反而在生成时过度关注“不要用什么”,造成补全质量下降。放在符号锚点和起始代码之后,模型已经“进入”了目标语言,这时候再给排除项,它执行起来是“顺手避开”,而不是“先想避开什么再想写什么”。
一个完整例子:
File: services/notifier/email.go (Go 1.22)
Existing symbols: Send(ctx context.Context, to string, subject string, body string) error
Complete from here:
func (n *Notifier) SendWelcome(ctx context.Context, to string) error {
return n.Send(ctx, to, "Welcome", "Thanks for signing up.")
Do not use:
- Python: smtplib, email.mime
- TypeScript: nodemailer, Promise
- Rust: lettre, tokio::spawn
常见问题
问:为什么我在提示词里写了“请用 Go 补全”,模型还是给我 Python 代码?
因为语言声明是弱信号。模型真正的语言判断来自上下文里的文件路径、扩展名、import 语句、类型签名这些硬 token。如果上下文里混入了大量 Python 文件内容,或者你给的补全位置没有足够的 Go 符号锚点,模型就会漂移。把“请用 Go”换成具体的 Go 文件路径和一行 Go 起始代码,效果会立刻不同。
问:多语言仓库里 IDE 自动拼接的上下文已经很乱了,提示词还能救回来吗?
能救,但要靠“覆盖”而不是“顺其自然”。在提示词开头就用文件路径重新声明当前焦点文件,然后显式列出排除的包名。模型对提示词末尾和开头的信息权重最高,中间拼进来的杂乱上下文影响会被削弱。如果 IDE 支持手动选择上下文文件,尽量只保留同语言文件。
问:排除项写“不要用其他语言的 API”够不够?
不够。抽象排除对模型几乎无效,要具体到包名、模块名、甚至关键符号。比如“不要用 asyncio、aiohttp、Promise、tokio::spawn”。模型避开具体 token 的能力远比避开抽象类别可靠。
问:起始代码给多少合适?一行还是更多?
一行通常就够。关键是要包含该语言的高信号 token:Go 的 := 或 err、Python 的类型注解 -> dict、TypeScript 的 import ... from、Rust 的 ::。一行里有两个以上这类 token,语言就锁住了。给太多反而会限制补全空间。