Skip to content

fix(adk): improve instruction formatting error handling#688

Merged
shentongmartin merged 1 commit intomainfrom
fix/instruction_render
Jan 30, 2026
Merged

fix(adk): improve instruction formatting error handling#688
shentongmartin merged 1 commit intomainfrom
fix/instruction_render

Conversation

@shentongmartin
Copy link
Contributor

@shentongmartin shentongmartin commented Jan 15, 2026

Instruction 格式化问题修复

问题

defaultGenModelInput 在 SessionValues 存在时会静默应用 FString 格式化,导致包含 JSON 的 Instruction 解析失败。

具体场景

// 用户的 Instruction 包含 JSON 示例
instruction := `Output in this format: {"name": "value"}`

// 用户使用 SessionValues 存储状态(非格式化用途)
r.Run(ctx, msgs, adk.WithSessionValues(map[string]any{"user_id": "123"}))

期望:Instruction 原样传递给模型
实际:FString 尝试解析 {name}{value} 作为变量,失败并报错

问题根源

// chatmodel.go: defaultGenModelInput
vs := GetSessionValues(ctx)
if len(vs) > 0 {
    ct := prompt.FromMessages(schema.FString, sp)
    ms, err := ct.Format(ctx, vs)  // 静默触发格式化
    if err != nil {
        return nil, err  // 原错误信息不清晰
    }
}

这个设计假设 SessionValues 的唯一用途是 Instruction 格式化,但实际上用户可能:

  • 用 SessionValues 存储运行时状态
  • Instruction 中的花括号是字面量(如 JSON 示例)

DeepAgent 的额外问题

DeepAgent 封装了 ChatModelAgent,但没有暴露 GenModelInput 配置,导致用户无法绕过默认行为。同样,Handlers 配置也未暴露,用户无法通过 BeforeAgent 自定义格式化逻辑。

解决方案

1. 改进错误信息

在 defaultGenModelInput 中,将原本的通用错误信息改为详细说明:

return nil, fmt.Errorf("defaultGenModelInput: failed to format instruction using FString template. "+
    "This formatting is triggered automatically when SessionValues are present. "+
    "If your instruction contains literal curly braces (e.g., JSON), or you are using "+
    "SessionValues for purposes other than instruction formatting, provide a custom GenModelInput "+
    "to bypass this default behavior: %w", err)

关键洞察:错误信息应该解释"为什么会发生"和"如何解决",而不仅仅是"发生了什么"。

2. DeepAgent 新增 DisableDefaultInstructionFormatting 配置

在 Config 中新增配置项:

// DisableDefaultInstructionFormatting disables the default FString template formatting on the Instruction.
//
// By default, when SessionValues are present, the Instruction is formatted using FString
// (e.g., "{name}" is replaced with the value of "name" from SessionValues).
//
// Set to true if:
//   - Your Instruction contains curly braces that should be literal (e.g., JSON examples)
//   - You are using SessionValues for purposes other than instruction formatting
//   - You want to handle instruction formatting yourself via a BeforeAgent handler
DisableDefaultInstructionFormatting bool

实现方式:提供一个不做格式化的 genModelInputWithoutFormatting:

func genModelInputWithoutFormatting(_ context.Context, instruction string, input *adk.AgentInput) ([]adk.Message, error) {
    msgs := make([]adk.Message, 0, len(input.Messages)+1)
    if instruction != "" {
        msgs = append(msgs, schema.SystemMessage(instruction))
    }
    msgs = append(msgs, input.Messages...)
    return msgs, nil
}

3. DeepAgent 新增 Handlers 配置

在 Config 中暴露 Handlers:

// Handlers configures interface-based handlers for extending agent behavior.
// Unlike Middlewares (struct-based), Handlers allow users to:
//   - Add custom methods to their handler implementations
//   - Return modified context from handler methods
//   - Centralize configuration in struct fields instead of closures
Handlers []adk.HandlerMiddleware

这允许用户通过 BeforeAgent 实现自定义格式化逻辑。

决策记录

考虑过的方案:添加 InstructionFormatType 配置

type Config struct {
    InstructionFormatType schema.FormatType  // FString, GoTemplate, None
}

拒绝原因

  1. 与自定义 GenModelInput 功能重叠
  2. 如果用户同时设置 InstructionFormatTypeGenModelInput,行为不明确
  3. 增加 API 复杂度,但只解决一个特定问题

最终选择DisableDefaultInstructionFormatting 是一个简单的开关,语义明确:

  • false(默认):使用默认的 FString 格式化
  • true:禁用默认格式化,Instruction 原样传递

为什么同时暴露 Handlers

DisableDefaultInstructionFormatting 只能禁用格式化,但用户可能需要自定义格式化逻辑(如使用 Go template 或其他模板引擎)。通过暴露 Handlers,用户可以在 BeforeAgent 中实现任意格式化:

handler := &customHandler{
    fn: func(ctx context.Context, runCtx *adk.AgentContext) (context.Context, *adk.AgentContext, error) {
        vs := adk.GetSessionValues(ctx)
        if user, ok := vs["user"].(string); ok {
            runCtx.Instruction = strings.ReplaceAll(runCtx.Instruction, "{{ user }}", user)
        }
        return ctx, runCtx, nil
    },
}

agent, _ := deep.New(ctx, &deep.Config{
    DisableDefaultInstructionFormatting: true,
    Handlers: []adk.HandlerMiddleware{handler},
})

总结

问题 解决方案
错误信息不清晰 详细说明触发原因和解决方法
DeepAgent 无法禁用默认格式化 新增 DisableDefaultInstructionFormatting 配置
DeepAgent 无法自定义格式化 暴露 Handlers 配置,支持 BeforeAgent 自定义逻辑

solves: #685
solves: #677

@codecov
Copy link

codecov bot commented Jan 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.98%. Comparing base (a0a346a) to head (2a3d177).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #688      +/-   ##
==========================================
+ Coverage   81.71%   81.98%   +0.27%     
==========================================
  Files         129      132       +3     
  Lines       12423    12952     +529     
==========================================
+ Hits        10151    10619     +468     
- Misses       1516     1562      +46     
- Partials      756      771      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timandy
Copy link

timandy commented Jan 19, 2026

这里是否只是 adk 模块的配置, 如果我使用 adk 和 compose 或者 手写 template , 等多种组合; 有没有办法一处配置多处默认使用全局设置.

@shentongmartin
Copy link
Contributor Author

这里是否只是 adk 模块的配置, 如果我使用 adk 和 compose 或者 手写 template , 等多种组合; 有没有办法一处配置多处默认使用全局设置.

@timandy “默认使用全局设置”,是指全局的一个 instruction,还是指全局的一个 DisableDefaultTemplateFormatting 配置。

@shentongmartin shentongmartin force-pushed the refactor/agent_middleware branch from ec65a64 to 65fc1cf Compare January 19, 2026 03:49
@timandy
Copy link

timandy commented Jan 21, 2026

这里是否只是 adk 模块的配置, 如果我使用 adk 和 compose 或者 手写 template , 等多种组合; 有没有办法一处配置多处默认使用全局设置.

@timandy “默认使用全局设置”,是指全局的一个 instruction,还是指全局的一个 DisableDefaultTemplateFormatting 配置。

@shentongmartin 我指的全局的 FormatType , 例如 全局使用 schema.Jinja2 或 FString; 因为当前业务开发, 输入输出大概率包含 json, 而 FString 对 json 不太友好, 建议默认设置为 Jinja2, 包括 cozeLoop 网站的 prompt 都使用 Jinja2 进行格式化

  1. 保留各个接口指定格式化的参数, func FromMessages(formatType schema.FormatType, templates ...schema.MessagesTemplate) *DefaultChatTemplate
  2. 增加不带 参数 formatType schema.FormatType 的 "重载" 实现; 当然现阶段有些接口没有带这个参数, 直接写死了 FormatType 例如 adk 模块的 instruction 直接用了FString , 这时候这些接口应该使用全局默认的
  3. adk 模块针 Agent 增加字段以单独指定 FormatType, 以覆盖全局设置;

总结起来就是有两个级别, 一个是 实例级别的FormatType 设置, 这个优先级最高; 第二个是 全局的 FormatType, 这个优先级低;

@timandy
Copy link

timandy commented Jan 21, 2026

我现在为了使用 Jinja2 格式化方式, 非常的繁琐:

//1. 先定义一个函数 copy 自 defaultGenModelInput
func GenModelInput(ctx context.Context, instruction string, input *adk.AgentInput) ([]adk.Message, error) {
	msgs := make([]adk.Message, 0, len(input.Messages)+1)

	if instruction != "" {
		sp := schema.SystemMessage(instruction)

		vs := adk.GetSessionValues(ctx)
		if len(vs) > 0 {
			ct := prompt.FromMessages(schema.Jinja2, sp) //只改了这里
			ms, err := ct.Format(ctx, vs)
			if err != nil {
				return nil, err
			}

			sp = ms[0]
		}

		msgs = append(msgs, sp)
	}

	msgs = append(msgs, input.Messages...)

	return msgs, nil
}
 //2. 指定  GenModelInput, 而且每个 Agent 都要指定一次 GenModelInput, 漏了就容易炸;
// 希望不指定 GenModelInput 的请款下, 默认的 defaultGenModelInput 优先使用 Agent 自身的 FormatType(待增加), 
// 如果FormatType也未指定, 则使用全局的 FormatType
	agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
		Name:             "courseware_extractor",
		Description:      "从教学课件提取内容, 并匹配数据库中的字典信息",
		Instruction:      utils.GetPrompt("teach_element.extract.system"),
		Model:            chatModel,
		GenModelInput:    GenModelInput, //每次都要指定
	})

//构建消息的时候每次都要带 这个格式参数 FormatType
//希望能增加一个FromMessages 的重载函数, 减少这个参数, 默认使用全局的FormatType .
	template := prompt.FromMessages( schema.Jinja2, //每次都要指定
		&schema.Message{
			Role:    schema.User,
			Content: "请根据以上规则,对我提供的文档内容进行分析,并返回符合要求的 JSON 结果。",
			UserInputMultiContent: []schema.MessageInputPart{
				utils.CreateMessagePartText("请根据以上规则,对我提供的文档内容进行分析,并返回符合要求的 JSON 结果。"),
				utils.CreateMessagePartFileAuto(path),
			},
		})

@jaxxjj
Copy link

jaxxjj commented Jan 22, 2026

是的 默认jinjia2 会比较好?prompt里面带 {} 代码示例就不work

@timandy
Copy link

timandy commented Jan 22, 2026

我怎么感觉这个被拒绝的方案使用起来更简单呢:
拒绝原因:

与自定义 GenModelInput 功能重叠 (一个是简单api, 一个是深层定制)
如果用户同时设置 InstructionFormatType 和 GenModelInput,行为不明确
增加 API 复杂度,但只解决一个特定问题

@shentongmartin shentongmartin force-pushed the refactor/agent_middleware branch 7 times, most recently from dfcd2d9 to 8171279 Compare January 28, 2026 07:05
Base automatically changed from refactor/agent_middleware to alpha/08 January 28, 2026 12:34
@shentongmartin shentongmartin force-pushed the fix/instruction_render branch 2 times, most recently from 1b81064 to 0b9d7cb Compare January 30, 2026 06:54
Change-Id: I31a1f38c02378ac46624b39d90749a2630409472
@shentongmartin shentongmartin changed the base branch from alpha/08 to main January 30, 2026 06:57
@shentongmartin shentongmartin merged commit f2c03c3 into main Jan 30, 2026
16 checks passed
@shentongmartin shentongmartin deleted the fix/instruction_render branch January 30, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants