从0到1:我是如何设计大模型结构化输出系统的
·
从0到1:我是如何设计大模型结构化输出系统的

前言
最近在做智能客服系统时遇到个问题:大模型返回的自然语言回复不好解析。
用户问"今天天气怎么样",模型可能返回:"今天北京晴转多云,最高温度28度"。
但我需要把温度、天气状况提取出来作为结构化数据。
经过调研,我设计了一套基于Agent拓扑的结构化输出方案,准确率提升到95%以上。
一、底层原理
1.1 核心机制
结构化输出的关键是让大模型按照指定格式返回数据:
graph TD
A[用户输入] --> B[Prompt模板]
B --> C[大模型]
C --> D{输出格式校验}
D -->|通过| E[JSON解析]
D -->|失败| F[重试机制]
F --> B
E --> G[业务逻辑]
Prompt工程要点:
{
"temperature": 0.0,
"max_tokens": 512,
"response_format": {
"type": "json_object"
}
}
1.2 与同类方案的对比
| 方案 | 准确率 | 灵活性 | 成本 |
|---|---|---|---|
| JSON模式 | 高 | 中 | 低 |
| 函数调用 | 高 | 高 | 中 |
| XML标签 | 中 | 低 | 低 |
| 正则提取 | 低 | 低 | 低 |
二、快速上手
package main
import (
"encoding/json"
"fmt"
)
type WeatherResponse struct {
City string `json:"city"`
Temperature int `json:"temperature"`
Condition string `json:"condition"`
Humidity int `json:"humidity"`
}
func main() {
// 大模型返回的JSON字符串
modelOutput := `{
"city": "北京",
"temperature": 28,
"condition": "晴转多云",
"humidity": 45
}`
// 解析为结构化数据
var weather WeatherResponse
err := json.Unmarshal([]byte(modelOutput), &weather)
if err != nil {
panic(err)
}
fmt.Printf("城市: %s\n", weather.City)
fmt.Printf("温度: %d°C\n", weather.Temperature)
fmt.Printf("天气: %s\n", weather.Condition)
fmt.Printf("湿度: %d%%\n", weather.Humidity)
}
三、核心 API / 深水区
3.1 核心方法速查
| 方法 | 功能 | 适用场景 |
|---|---|---|
json.Unmarshal() |
JSON解析 | 标准JSON输出 |
json.Marshal() |
JSON序列化 | 构造请求体 |
json.Decoder.Decode() |
流式解析 | 大文件处理 |
reflect.StructTag() |
标签解析 | 动态字段映射 |
3.2 生产级配置
// 带校验的结构化输出解析器
type StructuredOutputParser struct {
model *LLMClient
schema interface{}
maxRetries int
}
func NewParser(model *LLMClient, schema interface{}) *StructuredOutputParser {
return &StructuredOutputParser{
model: model,
schema: schema,
maxRetries: 3,
}
}
func (p *StructuredOutputParser) Parse(prompt string) (interface{}, error) {
for i := 0; i < p.maxRetries; i++ {
response, err := p.model.Generate(p.buildPrompt(prompt))
if err != nil {
return nil, err
}
result, err := p.validateAndParse(response)
if err == nil {
return result, nil
}
log.Printf("解析失败,第%d次重试: %v", i+1, err)
}
return nil, fmt.Errorf("超过最大重试次数")
}
func (p *StructuredOutputParser) buildPrompt(userInput string) string {
schemaJSON, _ := json.Marshal(p.schema)
return fmt.Sprintf(`
请按照以下JSON格式输出结果:
%s
用户输入:%s
`, string(schemaJSON), userInput)
}
3.3 高级定制
// 自定义JSON schema校验
func validateSchema(data []byte, schema interface{}) error {
// 使用json-schema库进行校验
loader := jsonschema.NewLoader()
schemaDoc, err := loader.LoadFromBytes(data)
if err != nil {
return err
}
result, err := schemaDoc.Validate(bytes.NewReader(data))
if err != nil {
return err
}
if !result.Valid() {
return fmt.Errorf("JSON不符合schema")
}
return nil
}
四、实战演练
场景:智能客服意图识别
type IntentResult struct {
Intent string `json:"intent"`
Confidence float64 `json:"confidence"`
Entities map[string]string `json:"entities"`
}
func parseIntent(userInput string) (*IntentResult, error) {
prompt := fmt.Sprintf(`
请分析用户意图:
用户输入:%s
输出格式:
{
"intent": "意图名称",
"confidence": 置信度(0-1),
"entities": {"实体名": "实体值"}
}
`, userInput)
response, err := callLLM(prompt)
if err != nil {
return nil, err
}
var result IntentResult
err = json.Unmarshal([]byte(response), &result)
if err != nil {
return nil, err
}
return &result, nil
}
五、避坑指南与最佳实践
💡 技巧:使用JSON Schema约束输出
// 定义输出schema
var weatherSchema = `{
"type": "object",
"properties": {
"city": {"type": "string"},
"temperature": {"type": "integer", "minimum": -40, "maximum": 60},
"condition": {"type": "string", "enum": ["晴", "多云", "阴", "雨", "雪"]},
"humidity": {"type": "integer", "minimum": 0, "maximum": 100}
},
"required": ["city", "temperature", "condition"]
}`
⚠️ 警告:处理JSON解析错误
func safeParse(data string, v interface{}) error {
// 清理可能的干扰字符
cleaned := strings.TrimSpace(data)
cleaned = strings.TrimPrefix(cleaned, "```json")
cleaned = strings.TrimSuffix(cleaned, "```")
err := json.Unmarshal([]byte(cleaned), v)
if err != nil {
return fmt.Errorf("解析失败: %w, 原始数据: %s", err, data)
}
return nil
}
✅ 推荐:设置temperature为0
// 结构化输出时降低随机性
request := &LLMRequest{
Model: "gpt-4",
Temperature: 0.0, // 确定性输出
MaxTokens: 512,
}
六、综合实战演示
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
type Parser struct {
maxRetries int
}
type OutputSchema struct {
Type string `json:"type"`
Properties map[string]Schema `json:"properties"`
Required []string `json:"required"`
}
type Schema struct {
Type string `json:"type"`
}
func NewParser(maxRetries int) *Parser {
return &Parser{maxRetries: maxRetries}
}
func (p *Parser) Parse(userInput string, schema OutputSchema) (map[string]interface{}, error) {
for attempt := 0; attempt < p.maxRetries; attempt++ {
prompt := buildPrompt(userInput, schema)
response := mockLLMCall(prompt)
result, err := parseResponse(response)
if err == nil {
if validateResult(result, schema) {
return result, nil
}
log.Printf("第%d次尝试:结果不符合schema", attempt+1)
continue
}
log.Printf("第%d次尝试:解析失败 %v", attempt+1, err)
}
return nil, fmt.Errorf("超过最大重试次数")
}
func buildPrompt(userInput string, schema OutputSchema) string {
schemaJSON, _ := json.MarshalIndent(schema, "", " ")
return fmt.Sprintf(`用户输入:%s
请按照以下JSON schema输出结果:
%s
输出要求:
1. 只输出JSON,不要任何其他文本
2. 必须包含所有required字段
3. 字段类型必须匹配`, userInput, string(schemaJSON))
}
func mockLLMCall(prompt string) string {
// 模拟大模型响应
return `{
"city": "上海",
"temperature": 32,
"condition": "晴",
"humidity": 65
}`
}
func parseResponse(response string) (map[string]interface{}, error) {
response = strings.TrimSpace(response)
response = strings.TrimPrefix(response, "```json")
response = strings.TrimSuffix(response, "```")
var result map[string]interface{}
err := json.Unmarshal([]byte(response), &result)
return result, err
}
func validateResult(result map[string]interface{}, schema OutputSchema) bool {
for _, required := range schema.Required {
if _, ok := result[required]; !ok {
return false
}
}
return true
}
func main() {
schema := OutputSchema{
Type: "object",
Properties: map[string]Schema{
"city": {Type: "string"},
"temperature": {Type: "integer"},
"condition": {Type: "string"},
"humidity": {Type: "integer"},
},
Required: []string{"city", "temperature", "condition"},
}
parser := NewParser(3)
result, err := parser.Parse("今天上海天气怎么样?", schema)
if err != nil {
log.Fatal(err)
}
fmt.Printf("解析结果:\n")
for k, v := range result {
fmt.Printf(" %s: %v\n", k, v)
}
}
七、总结
结构化输出是大模型落地的关键一步。
核心要点:
- 使用JSON模式约束输出格式
- 设置temperature为0确保确定性
- 实现重试机制处理解析失败
- 使用JSON Schema进行校验
核心收获: 好的Prompt工程能让大模型变成可靠的数据处理器。
更多推荐




所有评论(0)