Zong
马到金来

交付产物二次修改

继上个月进行持续迭代,方案二配套服务流程中的“导出 -> IDE 修改 -> 导入获得编译产物”将直接优化为“根据用户需求获得编译产物”。

通过内置 Agent 能力,将 IDE 修改流程全部隐藏在服务内,让用户更轻松的进行二次修改。

经过技术调研采用 deepagents 实现 Agent 相关能力。实现重点主要是文件系统沙箱 + AI 进行 AST 修改后输出结果。

在实现基础功后,为了选用较好的模型来落地,所以对模型进行过一些调试和测试,也很容易发现不同模型的最后呈现效果差异也很大。

目前打算使用 kimi-k2.5 进行进一步测试,不过在这里将测试过程中遇到的问题可以分享一下。

对接过程中会报错:

{
  name: 'Error',
  cause: {
    status: 400,
    headers: {},
    requestID: null,
    error: {
      message: 'thinking is enabled but reasoning_content is missing in assistant tool call message at index 2',
      type: 'invalid_request_error'
    },
    type: 'invalid_request_error'
  }
}

查阅了相关资料,社区因为考虑所以并没有处理这个问题,本着想提 PR 的思路尝试看看能不能解决。但是最后发现因为对 @langchain 相关生态了解较为浅显,所以暂时放弃提 PR 的操作。

最后决定通过补丁的方式进行解决此问题。

通过 debugger 能找到具体发起模型请求的位置是 ChatOpenAICompletions 类的 _streamResponseChunks 的方法,第一参数 messages 能获得当前请求的消息内容。

通过继承此函数实现 reasoning_content 字段补充,操作下去的你就会发现并没有这么轻松,因为在真实发起请求前会调用 convertMessagesToCompletionsMessageParams 进行格式化(源码 completions.ts#L823),并且没有修改空间。

不过幸运的是所有的参数在最后执行前会调用 completionWithRetry 我们可以通过复写此函数来进行格式重定义,并且观察源码发现 options 会进行透传,那就可以借助这个参数进行字段补充。

接下来是补丁内容:

import { ChatOpenAICompletions } from '@langchain/openai';

interface HankOptionsAttr {
  reasoning_content: Array<{ index: number; reasoning_content: string }>;
}

class HankChatOpenAICompletions extends ChatOpenAICompletions {
  async *_streamResponseChunks(messages, options, runManager) {
    const humanMessageIndex = messages.findIndex(
      (message) => message.type === 'human',
    );
    const humanMessage = messages[humanMessageIndex];
    const __hank__: HankOptionsAttr = {
      reasoning_content: [],
    };
    // 步骤二:若最后的用户消息存在 reasoning_content 则表示之前收集成功
    if (
      humanMessageIndex !== -1 &&
      humanMessage?.additional_kwargs?.reasoning_content
    ) {
      // 步骤二:尝试寻找需要补充 reasoning_content 字段的 Assistant Message
      for (let i = messages.length - 1; i > humanMessageIndex; i--) {
        const message = messages[i];
        if (message.type === 'ai') {
          __hank__.reasoning_content.push({
            index: i,
            reasoning_content:
              humanMessage?.additional_kwargs?.reasoning_content,
          });
        }
      }
    }
    // 步骤三:将收集到的信息通过 options 进行透传
    options.__hank__ = __hank__;

    // 步骤一:尝试收集 reasoning_content
    let reasoning_content = '';
    const chunks = super._streamResponseChunks(messages, options, runManager);
    for await (const chunk of chunks) {
      const delta = (chunk.message.additional_kwargs.__raw_response as any)
        ?.choices?.[0]?.delta;
      if (delta?.reasoning_content) {
        reasoning_content += delta.reasoning_content;
      }
      yield chunk;
    }

    // 步骤一:若 reasoning_content 存在则将其设置到最后一个用户消息内
    if (humanMessageIndex !== -1 && reasoning_content) {
      humanMessage.additional_kwargs ??= {};
      humanMessage.additional_kwargs.reasoning_content = reasoning_content;
    }
  }

  async completionWithRetry(request, requestOptions): Promise<any> {
    try {
      // 步骤三:根据透传信息补充 reasoning_content
      const __hank__: HankOptionsAttr = requestOptions?.__hank__ ?? {};
      if (Array.isArray(__hank__.reasoning_content)) {
        __hank__.reasoning_content.forEach((hank_rc) => {
          request.messages[hank_rc.index].reasoning_content =
            hank_rc.reasoning_content;
        });
      }
    } catch {}
    return super.completionWithRetry(request, requestOptions);
  }
}

const agent = createDeepAgent({
  model: new ChatOpenAI({
    completions: new HankChatOpenAICompletions(/* ... */),
    // ...
  }),
  // ...
  backend: new SafeFileSystem(),
});

通过上述补丁能力,能够成功将此类问题解决。

让我们愉快的进行后续的迭代调试吧。