実用時の対話や巨大なファイルを扱う際、この 400 不整合エラーによってエージェントが異常終了するのを防ぐため、配布コード(nano-code)の各プロバイダ(src/providers/openai.ts、src/providers/anthropic.ts、src/providers/google.ts)内では、メッセージ変換の直前に自動クリーンアップ処理(cleanMessages)を適用する実装をデフォルトで有効化しています。
これにより、書籍通りのシンプルな思考ループを維持したまま、実用時にも安定して動作するようになっています。各プロバイダでは、変換処理の直前に cleanMessages を呼び出すよう変更されています。
各プロバイダでの具体的な実装箇所
以下は、各プロバイダでデフォルトで適用されている cleanMessages の組み込みコードの構成です。
1. OpenAI プロバイダ (src/providers/openai.ts)
// convertMessages 内で cleanMessages を適用するように変更します
function convertMessages(messages: Message[]) {
- return messages.map((m) => {
+ const cleaned = cleanMessages(messages);
+ return cleaned.map((m) => {
// (中身は変更なし)
});
}
2. Anthropic プロバイダ (src/providers/anthropic.ts)
// convertMessages 内で cleanMessages を適用するように変更します
function convertMessages(messages: Message[]) {
- return messages
+ const cleaned = cleanMessages(messages);
+ return cleaned
.filter((m) => m.role !== 'system')
.map((m) => {
// (中身は変更なし)
});
}
3. Google プロバイダ (src/providers/google.ts)
// convertMessages 内で cleanMessages を適用するように変更します
function convertMessages(messages: Message[]) {
- return messages
+ const cleaned = cleanMessages(messages);
+ return cleaned
.filter((m) => m.role !== 'system')
.map((m) => {
// (中身は変更なし)
});
}
※ 各ファイルの末尾には、以下の通り親子関係の不整合をダミーメッセージの自動挿入等で補完する cleanMessages が定義されています。
function cleanMessages(messages: Message[]): Message[] {
const existingToolCallIds = new Set(
messages
.filter((m) => m.role === 'tool')
.map((m) => (m as any).toolCallId)
);
const finalMessages: Message[] = [];
for (const msg of messages) {
if (msg.role === 'tool') {
let foundAssistant = false;
for (let j = finalMessages.length - 1; j >= 0; j--) {
const prev = finalMessages[j];
if (
prev &&
prev.role === 'assistant' &&
'toolCalls' in prev &&
prev.toolCalls
) {
if (
prev.toolCalls.some(
(tc: any) => tc.toolCallId === msg.toolCallId
)
) {
foundAssistant = true;
break;
}
}
}
if (foundAssistant) {
finalMessages.push(msg);
}
} else if (
msg.role === 'assistant' &&
'toolCalls' in msg &&
msg.toolCalls
) {
const validToolCalls = msg.toolCalls.filter((tc: any) =>
existingToolCallIds.has(tc.toolCallId)
);
if (validToolCalls.length > 0) {
finalMessages.push({
role: 'assistant',
content: msg.content,
toolCalls: validToolCalls,
} as Message);
} else {
finalMessages.push({
role: 'assistant',
content: msg.content,
} as Message);
}
} else {
finalMessages.push(msg);
}
}
return finalMessages;
}