-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathanthropic.go
More file actions
135 lines (113 loc) · 3.53 KB
/
anthropic.go
File metadata and controls
135 lines (113 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package provider
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/coder/aibridge/circuitbreaker"
"github.com/coder/aibridge/config"
"github.com/coder/aibridge/intercept"
"github.com/coder/aibridge/intercept/messages"
"github.com/coder/aibridge/tracing"
"github.com/google/uuid"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
var _ Provider = &Anthropic{}
// Anthropic allows for interactions with the Anthropic API.
type Anthropic struct {
cfg config.Anthropic
bedrockCfg *config.AWSBedrock
}
const routeMessages = "/v1/messages" // https://docs.anthropic.com/en/api/messages
var anthropicOpenErrorResponse = func() []byte {
return []byte(`{"type":"error","error":{"type":"overloaded_error","message":"circuit breaker is open"}}`)
}
var anthropicIsFailure = func(statusCode int) bool {
// https://platform.claude.com/docs/en/api/errors
if statusCode == 529 {
return true
}
return circuitbreaker.DefaultIsFailure(statusCode)
}
func NewAnthropic(cfg config.Anthropic, bedrockCfg *config.AWSBedrock) *Anthropic {
if cfg.BaseURL == "" {
cfg.BaseURL = "https://api.anthropic.com/"
}
if cfg.Key == "" {
cfg.Key = os.Getenv("ANTHROPIC_API_KEY")
}
if cfg.APIDumpDir == "" {
cfg.APIDumpDir = os.Getenv("BRIDGE_DUMP_DIR")
}
if cfg.CircuitBreaker != nil {
cfg.CircuitBreaker.IsFailure = anthropicIsFailure
cfg.CircuitBreaker.OpenErrorResponse = anthropicOpenErrorResponse
}
return &Anthropic{
cfg: cfg,
bedrockCfg: bedrockCfg,
}
}
func (p *Anthropic) Name() string {
return config.ProviderAnthropic
}
func (p *Anthropic) RoutePrefix() string {
return fmt.Sprintf("/%s", p.Name())
}
func (p *Anthropic) BridgedRoutes() []string {
return []string{routeMessages}
}
func (p *Anthropic) PassthroughRoutes() []string {
return []string{
"/v1/models",
"/v1/models/", // See https://pkg.go.dev/net/http#hdr-Trailing_slash_redirection-ServeMux.
"/v1/messages/count_tokens",
"/api/event_logging/",
}
}
func (p *Anthropic) CreateInterceptor(w http.ResponseWriter, r *http.Request, tracer trace.Tracer) (_ intercept.Interceptor, outErr error) {
id := uuid.New()
_, span := tracer.Start(r.Context(), "Intercept.CreateInterceptor")
defer tracing.EndSpanErr(span, &outErr)
path := strings.TrimPrefix(r.URL.Path, p.RoutePrefix())
switch path {
case routeMessages:
payload, err := io.ReadAll(r.Body)
if err != nil {
return nil, fmt.Errorf("read body: %w", err)
}
var req messages.MessageNewParamsWrapper
if err := json.NewDecoder(bytes.NewReader(payload)).Decode(&req); err != nil {
return nil, fmt.Errorf("unmarshal request body: %w", err)
}
var interceptor intercept.Interceptor
if req.Stream {
interceptor = messages.NewStreamingInterceptor(id, &req, payload, p.cfg, p.bedrockCfg, r.Header.Clone(), r.RemoteAddr, r.Host, r.TLS != nil, tracer)
} else {
interceptor = messages.NewBlockingInterceptor(id, &req, payload, p.cfg, p.bedrockCfg, r.Header.Clone(), r.RemoteAddr, r.Host, r.TLS != nil, tracer)
}
span.SetAttributes(interceptor.TraceAttributes(r)...)
return interceptor, nil
}
span.SetStatus(codes.Error, "unknown route: "+r.URL.Path)
return nil, UnknownRoute
}
func (p *Anthropic) BaseURL() string {
return p.cfg.BaseURL
}
func (p *Anthropic) AuthHeader() string {
return "X-Api-Key"
}
func (p *Anthropic) InjectAuthHeader(headers *http.Header) {
if headers == nil {
headers = &http.Header{}
}
headers.Set(p.AuthHeader(), p.cfg.Key)
}
func (p *Anthropic) CircuitBreakerConfig() *config.CircuitBreaker {
return p.cfg.CircuitBreaker
}