fix(openai): Anthropic history 표현을 정규화한다

This commit is contained in:
toki 2026-08-15 14:35:42 +09:00
parent 1f1be982c9
commit 949db240a9
2 changed files with 70 additions and 16 deletions

View file

@ -306,7 +306,7 @@ func TestLogicalRequestEndpointContinuationLineage(t *testing.T) {
// Anthropic continuation test
anthropicInitialRaw := []byte(`{
"model": "preset-model",
"system": [{"type": "text", "text": "sys"}],
"system": "sys",
"messages": [{"role": "user", "content": "hello"}],
"tools": [{"name": "artifact", "input_schema": {"type": "object", "maximum": 9007199254740992}}]
}`)
@ -317,9 +317,9 @@ func TestLogicalRequestEndpointContinuationLineage(t *testing.T) {
anthropicContinuationRaw := []byte(`{
"model": "preset-model",
"system": [{"type": "text", "text": "sys"}],
"system": [{"type": "text", "text": "sys", "cache_control":{"type":"ephemeral"}}],
"messages": [
{"role": "user", "content": "hello"},
{"role": "user", "content": [{"type":"text","text":"hello","cache_control":{"type":"ephemeral"}}]},
{"role": "assistant", "content": [{"type": "tool_use", "id": "tu_1", "name": "artifact", "input": {}}]},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_1", "content": "ok"}]}
],

View file

@ -62,7 +62,71 @@ func newAnthropicRequestLineage(raw json.RawMessage) (logicalRequestLineage, err
if _, err := validateAnthropicMessages(rawMessages); err != nil {
return logicalRequestLineage{}, err
}
return newLogicalRequestLineageFromRawFields(fields, logicalRequestEndpointAnthropic, []string{"model", "system", "messages"})
historyDigest, err := fingerprintAnthropicHistory(fields["model"], fields["system"], rawMessages)
if err != nil {
return logicalRequestLineage{}, err
}
toolsetDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, fields["tools"])
if err != nil {
return logicalRequestLineage{}, err
}
return logicalRequestLineage{Endpoint: logicalRequestEndpointAnthropic, HistoryDigest: historyDigest, ToolsetDigest: toolsetDigest}, nil
}
func fingerprintAnthropicHistory(model, system, messages json.RawMessage) (string, error) {
normalizedSystem, err := normalizeAnthropicHistoryContent(system)
if err != nil {
return "", err
}
normalizedMessages, err := normalizeAnthropicHistoryMessages(messages)
if err != nil {
return "", err
}
return fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, map[string]json.RawMessage{
"model": model, "system": normalizedSystem, "messages": normalizedMessages,
})
}
func normalizeAnthropicHistoryMessages(raw json.RawMessage) (json.RawMessage, error) {
var messages []map[string]any
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
if err := decoder.Decode(&messages); err != nil {
return nil, err
}
for _, message := range messages {
message["content"] = normalizeAnthropicHistoryContentValue(message["content"])
}
return json.Marshal(messages)
}
func normalizeAnthropicHistoryContent(raw json.RawMessage) (json.RawMessage, error) {
if len(raw) == 0 {
return raw, nil
}
var value any
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
if err := decoder.Decode(&value); err != nil {
return nil, err
}
return json.Marshal(normalizeAnthropicHistoryContentValue(value))
}
func normalizeAnthropicHistoryContentValue(value any) any {
if text, ok := value.(string); ok {
return []any{map[string]any{"type": "text", "text": text}}
}
blocks, ok := value.([]any)
if !ok {
return value
}
for _, rawBlock := range blocks {
if block, ok := rawBlock.(map[string]any); ok {
delete(block, "cache_control")
}
}
return blocks
}
type chatMessageValidation struct {
@ -532,12 +596,7 @@ func newAnthropicContinuationLineage(raw json.RawMessage) (logicalRequestContinu
return logicalRequestContinuationLineage{}, fmt.Errorf("marshal prefix messages: %w", err)
}
prefixHistory := map[string]json.RawMessage{
"model": fields["model"],
"system": fields["system"],
"messages": prefixMessagesRaw,
}
prefixHistoryDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, prefixHistory)
prefixHistoryDigest, err := fingerprintAnthropicHistory(fields["model"], fields["system"], prefixMessagesRaw)
if err != nil {
return logicalRequestContinuationLineage{}, err
}
@ -551,12 +610,7 @@ func newAnthropicContinuationLineage(raw json.RawMessage) (logicalRequestContinu
ToolsetDigest: toolsetDigest,
}
committedHistory := map[string]json.RawMessage{
"model": fields["model"],
"system": fields["system"],
"messages": fields["messages"],
}
committedHistoryDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, committedHistory)
committedHistoryDigest, err := fingerprintAnthropicHistory(fields["model"], fields["system"], fields["messages"])
if err != nil {
return logicalRequestContinuationLineage{}, err
}