Add vendor assessment agent

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-04-22 22:36:14 +02:00
parent 25c590ffe6
commit 509d0c88b1
108 changed files with 9445 additions and 645 deletions

View File

@@ -152,6 +152,28 @@ func buildParams(req *llm.ChatCompletionRequest) (anthropic.MessageNewParams, er
if req.ToolChoice != nil {
params.ToolChoice = buildToolChoice(req.ToolChoice)
}
if req.Thinking != nil && req.Thinking.Enabled {
params.Thinking = anthropic.ThinkingConfigParamOfEnabled(int64(req.Thinking.BudgetTokens))
}
if req.ResponseFormat != nil {
switch req.ResponseFormat.Type {
case llm.ResponseFormatJSONSchema:
if req.ResponseFormat.JSONSchema == nil {
return anthropic.MessageNewParams{}, fmt.Errorf("cannot apply JSON schema output format: schema is nil")
}
var schema map[string]any
if err := json.Unmarshal(req.ResponseFormat.JSONSchema.Schema, &schema); err != nil {
return anthropic.MessageNewParams{}, fmt.Errorf("cannot unmarshal JSON schema for output format: %w", err)
}
params.OutputConfig = anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{Schema: schema},
}
case llm.ResponseFormatJSONObject:
return anthropic.MessageNewParams{}, fmt.Errorf("anthropic does not support json_object response format without a schema; use json_schema instead")
case llm.ResponseFormatText:
// default behaviour, nothing to set
}
}
return params, nil
}
@@ -194,12 +216,21 @@ func buildMessages(messages []llm.Message) []anthropic.MessageParam {
out = append(out, anthropic.NewUserMessage(blocks...))
case llm.RoleAssistant:
var blocks []anthropic.ContentBlockParamUnion
if text := msg.Text(); text != "" {
blocks = append(blocks, anthropic.NewTextBlock(text))
for _, p := range msg.Parts {
switch part := p.(type) {
case llm.ThinkingPart:
blocks = append(blocks, anthropic.NewThinkingBlock(part.Signature, part.Text))
case llm.TextPart:
if part.Text != "" {
blocks = append(blocks, anthropic.NewTextBlock(part.Text))
}
}
}
for _, tc := range msg.ToolCalls {
var input any
_ = json.Unmarshal([]byte(tc.Function.Arguments), &input)
if err := json.Unmarshal([]byte(tc.Function.Arguments), &input); err != nil || input == nil {
input = map[string]any{}
}
blocks = append(blocks, anthropic.NewToolUseBlock(tc.ID, input, tc.Function.Name))
}
out = append(out, anthropic.NewAssistantMessage(blocks...))
@@ -295,6 +326,12 @@ func mapResponse(msg *anthropic.Message) *llm.ChatCompletionResponse {
for _, block := range msg.Content {
switch block.Type {
case "thinking":
tb := block.AsThinking()
resp.Message.Parts = append(resp.Message.Parts, llm.ThinkingPart{
Text: tb.Thinking,
Signature: tb.Signature,
})
case "text":
resp.Message.Parts = append(resp.Message.Parts, llm.TextPart{Text: block.Text})
case "tool_use":
@@ -326,6 +363,15 @@ func mapStopReason(reason anthropic.StopReason) llm.FinishReason {
}
func mapError(err error) error {
// The Anthropic SDK refuses non-streaming requests client-side when
// the expected response time exceeds 10 minutes (large max_tokens or
// model-specific non-streaming token limits). It returns a plain
// fmt.Errorf, not an *anthropic.Error, so we must match on the
// message before attempting the type assertion.
if err != nil && strings.Contains(err.Error(), "streaming is required") {
return &llm.ErrStreamingRequired{Err: err}
}
var apiErr *anthropic.Error
if !errors.As(err, &apiErr) {
return err
@@ -361,7 +407,9 @@ type anthropicStream struct {
stream *ssestream.Stream[anthropic.MessageStreamEventUnion]
current llm.ChatCompletionStreamEvent
// Track tool call indices for mapping content_block_start events.
toolCallIndex int
toolCallIndex int
inToolUse bool
thinkingSignature string
}
func (s *anthropicStream) Next() bool {
@@ -396,7 +444,9 @@ func (s *anthropicStream) mapStreamEvent(event *anthropic.MessageStreamEventUnio
switch event.Type {
case "content_block_start":
cb := event.ContentBlock
if cb.Type == "tool_use" {
switch cb.Type {
case "tool_use":
s.inToolUse = true
tu := cb.AsToolUse()
return llm.ChatCompletionStreamEvent{
Delta: llm.MessageDelta{
@@ -407,6 +457,8 @@ func (s *anthropicStream) mapStreamEvent(event *anthropic.MessageStreamEventUnio
}},
},
}, true
case "thinking":
return llm.ChatCompletionStreamEvent{}, false
}
return llm.ChatCompletionStreamEvent{}, false
@@ -417,6 +469,15 @@ func (s *anthropicStream) mapStreamEvent(event *anthropic.MessageStreamEventUnio
return llm.ChatCompletionStreamEvent{
Delta: llm.MessageDelta{Content: delta.Text},
}, true
case "thinking_delta":
return llm.ChatCompletionStreamEvent{
Delta: llm.MessageDelta{Thinking: delta.Thinking},
}, true
case "signature_delta":
s.thinkingSignature = delta.Signature
return llm.ChatCompletionStreamEvent{
Delta: llm.MessageDelta{ThinkingSignature: delta.Signature},
}, true
case "input_json_delta":
return llm.ChatCompletionStreamEvent{
Delta: llm.MessageDelta{
@@ -430,8 +491,9 @@ func (s *anthropicStream) mapStreamEvent(event *anthropic.MessageStreamEventUnio
return llm.ChatCompletionStreamEvent{}, false
case "content_block_stop":
if event.ContentBlock.Type == "tool_use" {
if s.inToolUse {
s.toolCallIndex++
s.inToolUse = false
}
return llm.ChatCompletionStreamEvent{}, false