Go构建LLM服务需分层处理,Langchaingo是适配Go并发与内存安全的原生框架,支持OpenAI/Gemini/Ollama等模型统一调用,强调上下文控制、Prompt模板校验、RAG安全检索及HTTP请求防护。

Go 适合构建高并发、可部署的 LLM 应用服务,但直接调用大模型不能只靠 http.Client 手写请求——你需要分层处理:模型接入、提示编排、上下文管理、向量检索。Langchaingo 是目前最实用的 Go 原生框架,它不是 Python LangChain 的简单翻译,而是针对 Go 并发模型和内存安全做了重构。
用 langchaingo 接入 OpenAI/Gemini/Ollama 等主流 LLM
langchaingo 的 llms 模块统一了不同后端的调用接口,避免为每个模型重写逻辑。关键点在于初始化时传参要匹配实际服务类型:
-
openai.New()默认连官方 API;若用代理或自建服务,必须显式传openai.WithBaseURL("https://your-api.com/v1") -
gemini.New()需要gemini.WithAPIKey(os.Getenv("GEMINI_API_KEY")),且当前版本不支持流式响应(Call返回完整字符串,非stream) - Ollama 本地模型要用
ollama.New(),默认连接http://localhost:11434;若改端口或加认证,得手动构造ollama.New(ollama.WithEndpoint("http://...")) - 所有
llm.Call()调用都要求传context.Context,超时控制必须由你设(例如ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)),否则请求卡死会拖垮整个 HTTP handler
用 prompts.PromptTemplate 控制输出格式与角色
硬拼接字符串写 prompt 容易出错、难复用、无法做变量校验。prompts.PromptTemplate 提供 Go template 语法支持,且在 Format() 时做输入变量检查:
- 模板中未声明的变量(如写了
{{.lang}}却没在InputVariables里列)会 panic,不是静默忽略 -
PartialVariables适合填固定值(如系统角色:“你是一个金融合规助手”),InputVariables用于运行时动态内容(如用户提问原文) - 别用
TemplateFormatJinja2—— langchaingo 当前只完整支持TemplateFormatGoTemplate,Jinja2 解析器存在变量转义 bug - 示例:
Template: "请基于以下文档回答问题:{{.docs}}。问题:{{.query}}。只返回答案,不要解释。",这样能压住模型“过度发挥”
RAG 场景下如何避免向量检索结果污染 prompt
检索增强生成(RAG)的核心风险不是模型不准,而是把无关或过长的 chunk 塞进 prompt 导致 token 超限或干扰判断。langchaingo 本身不带向量库,需自己组合:
大模型 Token 成本节约工具。在请求到达大模型之前自动压缩 prompt 和上下文,减少 60-95% 的 token 消耗,直接降低 API 成本。支持 Claude/OpenAI/Gemini 等主流模型,提供代理模式、CLI 包装、Python SDK 和 MCP Server 四种接入方式。内置一键安...
立即学习“go语言免费学习笔记(深入)”;
- 用
text2vec或bge类模型做嵌入时,确保编码器和检索时用的是**同一套 tokenizer 和归一化逻辑**,否则余弦相似度失效 - 从向量数据库(如 Weaviate、Qdrant)查出 top-k 文档后,别直接拼接全文——先按相关性分数倒序,再按长度截断(例如每段 ≤ 300 token),总长度控制在模型上下文窗口 60% 以内
- langchaingo 的
chains.RetrievalQA尚未稳定(v0.5.x 版本中仍为实验性模块),生产环境建议手写 chain:先retriever.GetRelevantDocuments(),再用PromptTemplate注入,最后llm.Call() - 注意
documents切片顺序会影响最终 prompt 位置权重——把最相关的放前面,模型更倾向参考它
HTTP 服务中如何安全传递用户 prompt 并防越界
暴露 /query 接口给前端时,不能直接把用户输入当 prompt 丢给 LLM。常见漏洞包括 prompt 注入、token 溢出、敏感信息泄露:
- 对用户输入做基础清洗:
strings.TrimSpace()+strings.ReplaceAll(input, "\x00", "")(防 null byte 注入) - 用
countTokens()(如基于gpt-tokenizer-go)预估 prompt 总长度,超阈值(如 2000 token)直接返回400 Bad Request - 禁止用户控制
system角色字段——所有 system prompt 必须由服务端硬编码,不能从请求 body 解析 - 若用 Gin 框架,别在
c.BindJSON()后直接传input.Content给 LLM,中间加一层sanitizeUserQuery(input.Content)函数做白名单字符过滤
真正难的不是调通第一个 llm.Call(),而是让每次请求都可控、可测、可回溯。langchaingo 的链式结构(chain.Call())在调试时比裸写 HTTP 更容易插桩打点,但它的中间状态不自动记录——你要主动把 prompt、embedding 输入、检索结果存到日志或 trace 中,否则出问题时根本没法区分是模型飘了,还是检索错了,还是 prompt 写崩了。

















