diff --git a/go.mod b/go.mod index fea75e0..6783abf 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/checkmarble/llmberjack go 1.24.4 require ( + github.com/anthropics/anthropic-sdk-go v1.27.0 github.com/cockroachdb/errors v1.12.0 github.com/fatih/structs v1.1.0 github.com/h2non/gock v1.2.0 @@ -17,13 +18,17 @@ require ( require ( cloud.google.com/go v0.116.0 // indirect cloud.google.com/go/auth v0.9.3 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -43,10 +48,19 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.26.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/time v0.6.0 // indirect + google.golang.org/api v0.197.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/grpc v1.66.2 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/go.sum b/go.sum index d7803ad..5309bad 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,13 @@ cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/anthropics/anthropic-sdk-go v1.27.0 h1:0CWbmBq5ofGAjF2H6lefCNRbnaUMGiTKO+lb7RLhDbI= +github.com/anthropics/anthropic-sdk-go v1.27.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -23,16 +27,25 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -49,6 +62,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -121,11 +136,21 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -141,26 +166,30 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -173,6 +202,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= +google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genai v1.15.0 h1:zFaM+1JfGa0KCGDqrZdwVMucEu9n5AJEKkWcSPw0qro= @@ -203,6 +234,8 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/llms/anthropic/adapter_test.go b/llms/anthropic/adapter_test.go new file mode 100644 index 0000000..cc5d154 --- /dev/null +++ b/llms/anthropic/adapter_test.go @@ -0,0 +1,56 @@ +package anthropic + +import ( + "strings" + "testing" + + llmberjack "github.com/checkmarble/llmberjack" + "github.com/stretchr/testify/assert" +) + +func TestRequestAdapter(t *testing.T) { + llm, _ := llmberjack.New() + p, _ := New() + + t.Run("with model", func(t *testing.T) { + req := llmberjack.NewUntypedRequest(). + WithModel("claude-3-5-sonnet-20241022") + + messages, _, err := p.adaptRequest(llm, req, RequestOptions{}) + + assert.Nil(t, err) + assert.NotNil(t, messages) + }) + + t.Run("with user prompts", func(t *testing.T) { + req := llmberjack.NewUntypedRequest(). + WithModel("claude-3-5-sonnet-20241022"). + WithText(llmberjack.RoleUser, "user prompt", "user prompt 2"). + WithTextReader(llmberjack.RoleUser, strings.NewReader("user prompt 3")) + + messages, _, err := p.adaptRequest(llm, req, RequestOptions{}) + + assert.Nil(t, err) + assert.Greater(t, len(messages), 0) + }) +} + +func TestBackendConfiguration(t *testing.T) { + t.Run("anthropic backend", func(t *testing.T) { + p, err := New( + WithBackend(BackendAnthropic), + WithApiKey("test-key"), + ) + assert.Nil(t, err) + assert.Equal(t, BackendAnthropic, p.backend) + }) + + t.Run("vertexai backend", func(t *testing.T) { + p, err := New( + WithBackend(BackendVertexAI), + WithProject("test-project"), + ) + assert.Nil(t, err) + assert.Equal(t, BackendVertexAI, p.backend) + }) +} diff --git a/llms/anthropic/anthropic.go b/llms/anthropic/anthropic.go new file mode 100644 index 0000000..dc294f0 --- /dev/null +++ b/llms/anthropic/anthropic.go @@ -0,0 +1,386 @@ +package anthropic + +import ( + "context" + "encoding/json" + "io" + "reflect" + + llmberjack "github.com/checkmarble/llmberjack" + "github.com/checkmarble/llmberjack/internal" + "github.com/cockroachdb/errors" + "github.com/samber/lo" + + "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/option" + "github.com/anthropics/anthropic-sdk-go/vertex" +) + +type Anthropic struct { + client anthropic.Client + history llmberjack.History[anthropic.MessageParam] + + backend Backend + apiKey string + baseUrl string + project string + region string + vertexCtx context.Context + model *string +} + +func (*Anthropic) RequestOptionsType() reflect.Type { + return reflect.TypeFor[RequestOptions]() +} + +func New(opts ...Opt) (*Anthropic, error) { + llm := Anthropic{ + backend: BackendAnthropic, + region: "global", + vertexCtx: context.Background(), + } + + for _, opt := range opts { + opt(&llm) + } + + return &llm, nil +} + +func (p *Anthropic) Init(adapter internal.Adapter) error { + switch p.backend { + case BackendAnthropic: + return p.initAnthropic(adapter) + case BackendVertexAI: + return p.initVertexAI(adapter) + default: + return errors.New("unknown backend") + } +} + +func (p *Anthropic) initAnthropic(adapter internal.Adapter) error { + opts := []option.RequestOption{ + option.WithAPIKey(p.apiKey), + } + + if adapter.HttpClient() != nil { + opts = append(opts, option.WithHTTPClient(adapter.HttpClient())) + } + + if p.baseUrl != "" { + opts = append(opts, option.WithBaseURL(p.baseUrl)) + } + + client := anthropic.NewClient(opts...) + p.client = client + + return nil +} + +func (p *Anthropic) initVertexAI(adapter internal.Adapter) error { + // VertexAI requires project and region to be set + if p.project == "" { + return errors.New("project is required for VertexAI backend") + } + + // Create vertex AI client with credentials context + opts := []option.RequestOption{ + vertex.WithGoogleAuth(p.vertexCtx, p.region, p.project), + } + + // NB: I'm not passing the adapter's HTTP client to the anthropic client options, because using vertex AI the SDK does automatic + // application default credentials discovery and generates his own http client. There is scope for a future improvement on this. + + client := anthropic.NewClient(opts...) + p.client = client + + return nil +} + +func (p *Anthropic) ResetThread(threadId *llmberjack.ThreadId) { + p.history.Clear(threadId) +} + +func (p *Anthropic) CopyThread(threadId *llmberjack.ThreadId) *llmberjack.ThreadId { + return p.history.Copy(threadId) +} + +func (p *Anthropic) CloseThread(threadId *llmberjack.ThreadId) { + p.history.Close(threadId) +} + +func (p *Anthropic) ChatCompletion(ctx context.Context, adapter internal.Adapter, requester llmberjack.Requester) (*llmberjack.InnerResponse, error) { + r := requester.ToRequest() + + model, ok := lo.Coalesce(r.Model, p.model, lo.ToPtr(adapter.DefaultModel())) + if !ok { + return nil, errors.New("no model was configured") + } + + opts := internal.CastProviderOptions[RequestOptions](requester.ProviderRequestOptions(p)) + + messages, params, err := p.adaptRequest(adapter, requester, opts) + if err != nil { + return nil, errors.Wrap(err, "could not adapt request") + } + + messageParams := anthropic.MessageNewParams{ + Model: *model, + Messages: messages, + } + + // Add max tokens only if explicitly provided (Vertex AI requires > 0) + if r.MaxTokens != nil { + messageParams.MaxTokens = int64(*r.MaxTokens) + } else if p.backend == BackendVertexAI { + messageParams.MaxTokens = BackendVertexAiDefaultMaxTokens // Set a default max tokens for Vertex AI if not provided + } + + // Add system message + if params.System.Text != "" { + messageParams.System = []anthropic.TextBlockParam{params.System} + } + + // Add tools + if len(params.Tools) > 0 { + toolUnions := make([]anthropic.ToolUnionParam, len(params.Tools)) + for i, tool := range params.Tools { + toolUnions[i] = anthropic.ToolUnionParamOfTool( + tool.InputSchema, + tool.Name, + ) + // Description is already set in tool if provided + toolUnions[i].OfTool.Description = tool.Description + } + messageParams.Tools = toolUnions + } + + // Add tool choice + if params.ToolChoice != nil { + messageParams.ToolChoice = *params.ToolChoice + } + + // Add temperature + if r.Temperature != nil { + messageParams.Temperature = anthropic.Float(*r.Temperature) + } + + // Add top_p + if r.TopP != nil { + messageParams.TopP = anthropic.Float(*r.TopP) + } + + // Add top_k + if params.TopK != nil { + messageParams.TopK = anthropic.Int(int64(*params.TopK)) + } + + // Add thinking + if params.Thinking != nil && *params.Thinking > 0 { + messageParams.Thinking = anthropic.ThinkingConfigParamOfEnabled(int64(*params.Thinking)) + } + + response, err := p.client.Messages.New(ctx, messageParams) + if err != nil { + return nil, errors.Wrap(err, "LLM provider failed to generate content") + } + + responseAdapter, err := p.adaptResponse(adapter, response, requester) + if err != nil { + return nil, errors.Wrap(err, "could not adapt response") + } + + return responseAdapter, nil +} + +type requestParams struct { + System anthropic.TextBlockParam + Tools []anthropic.ToolParam + ToolChoice *anthropic.ToolChoiceUnionParam + TopK *int + Thinking *int +} + +func (p *Anthropic) adaptRequest(_ internal.Adapter, requester llmberjack.Requester, opts RequestOptions) ([]anthropic.MessageParam, *requestParams, error) { + r := requester.ToRequest() + messages := make([]anthropic.MessageParam, 0, len(r.Messages)) + + if r.ThreadId != nil { + messages = append(messages, p.history.Load(r.ThreadId)...) + } + + params := &requestParams{ + Tools: make([]anthropic.ToolParam, 0, len(r.Tools)), + } + + // Add tools + for _, tool := range r.Tools { + paramsJson, err := json.Marshal(tool.Parameters) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to encode tool parameters") + } + + var toolParams map[string]any + if err := json.Unmarshal(paramsJson, &toolParams); err != nil { + return nil, nil, errors.Wrap(err, "failed to decode tool parameters") + } + + toolParam := anthropic.ToolParam{ + Name: tool.Name, + InputSchema: anthropic.ToolInputSchemaParam{ + Type: "object", + Properties: toolParams, + }, + } + if tool.Description != "" { + toolParam.Description = anthropic.String(tool.Description) + } + params.Tools = append(params.Tools, toolParam) + } + + // System messages are handled as separate messages with RoleSystem + + // Add budget tokens for thinking if specified + if opts.BudgetTokens != nil && *opts.BudgetTokens > 0 { + params.Thinking = opts.BudgetTokens + } + + // Process messages + for _, msg := range r.Messages { + parts := make([]anthropic.ContentBlockParamUnion, 0, len(msg.Parts)) + + for _, part := range msg.Parts { + if seeker, ok := part.(io.ReadSeeker); ok { + if _, err := seeker.Seek(0, io.SeekStart); err != nil { + return nil, nil, err + } + } + + buf, err := io.ReadAll(part) + if err != nil { + return nil, nil, errors.Wrap(err, "could not read content part") + } + + switch msg.Type { + case llmberjack.TypeText: + parts = append(parts, anthropic.NewTextBlock(string(buf))) + } + } + + var msgParam anthropic.MessageParam + + switch msg.Role { + case llmberjack.RoleAi: + msgParam = anthropic.MessageParam{ + Role: anthropic.MessageParamRoleAssistant, + Content: parts, + } + + case llmberjack.RoleUser: + msgParam = anthropic.MessageParam{ + Role: anthropic.MessageParamRoleUser, + Content: parts, + } + + case llmberjack.RoleSystem: + // System messages are handled separately in Anthropic SDK + continue + + case llmberjack.RoleTool: + if msg.Tool == nil { + return nil, nil, errors.New("sent a tool response when no tool was invoked") + } + + var toolContent string + for _, part := range msg.Parts { + if seeker, ok := part.(io.ReadSeeker); ok { + _, err := seeker.Seek(0, io.SeekStart) + if err != nil { + return nil, nil, errors.Wrap(err, "could not seek content part") + } + } + buf, err := io.ReadAll(part) + if err != nil { + return nil, nil, errors.Wrap(err, "could not read content part") + } + toolContent = string(buf) + } + + msgParam = anthropic.MessageParam{ + Role: anthropic.MessageParamRoleUser, + Content: []anthropic.ContentBlockParamUnion{ + anthropic.NewToolResultBlock(msg.Tool.Id, toolContent, false), + }, + } + } + + if r.ThreadId != nil && !r.SkipSaveInput { + p.history.Save(r.ThreadId, msgParam) + } + + messages = append(messages, msgParam) + } + + return messages, params, nil +} + +func (p *Anthropic) adaptResponse(_ internal.Adapter, response *anthropic.Message, requester llmberjack.Requester) (*llmberjack.InnerResponse, error) { + resp := llmberjack.InnerResponse{ + Id: response.ID, + Model: response.Model, + Candidates: make([]llmberjack.ResponseCandidate, 1), + } + + var finishReason llmberjack.FinishReason + switch response.StopReason { + case "end_turn": + finishReason = llmberjack.FinishReasonStop + case "max_tokens": + finishReason = llmberjack.FinishReasonMaxTokens + default: + finishReason = llmberjack.FinishReason(response.StopReason) + } + + text := "" + toolCalls := make([]llmberjack.ResponseToolCall, 0) + thoughts := "" + + for _, content := range response.Content { + switch content.Type { + case "text": + textBlock := content.AsText() + text += textBlock.Text + case "tool_use": + toolBlock := content.AsToolUse() + toolCalls = append(toolCalls, llmberjack.ResponseToolCall{ + Id: toolBlock.ID, + Name: toolBlock.Name, + Parameters: toolBlock.Input, + }) + case "thinking": + thinkingBlock := content.AsThinking() + thoughts = thinkingBlock.Thinking + } + } + + resp.Candidates[0] = llmberjack.ResponseCandidate{ + Text: text, + ToolCalls: toolCalls, + FinishReason: finishReason, + Thoughts: thoughts, + SelectCandidate: func() { + req := requester.ToRequest() + + if req.ThreadId != nil && !req.SkipSaveOutput { + msg := anthropic.MessageParam{ + Role: anthropic.MessageParamRoleAssistant, + Content: []anthropic.ContentBlockParamUnion{anthropic.NewTextBlock(text)}, + } + + p.history.Save(req.ThreadId, msg) + } + }, + } + + return &resp, nil +} diff --git a/llms/anthropic/options.go b/llms/anthropic/options.go new file mode 100644 index 0000000..c69013a --- /dev/null +++ b/llms/anthropic/options.go @@ -0,0 +1,72 @@ +package anthropic + +import ( + "context" +) + +type Backend string + +const ( + BackendAnthropic Backend = "anthropic" + BackendVertexAI Backend = "vertexai" + + BackendVertexAiDefaultMaxTokens = 4096 +) + +type Opt func(*Anthropic) + +// WithBackend sets which backend to use (anthropic or vertexai). +// Defaults to BackendAnthropic. +func WithBackend(backend Backend) Opt { + return func(p *Anthropic) { + p.backend = backend + } +} + +// WithApiKey sets the API key for Anthropic. +// Required for BackendAnthropic. +func WithApiKey(apiKey string) Opt { + return func(p *Anthropic) { + p.apiKey = apiKey + } +} + +// WithBaseUrl sets the URL at which the Anthropic API is available. +// If not specified, will use Anthropic's default API endpoint. +// Only applicable for BackendAnthropic. +func WithBaseUrl(url string) Opt { + return func(p *Anthropic) { + p.baseUrl = url + } +} + +// WithDefaultModel sets the default model to use if none is specified in the request. +func WithDefaultModel(model string) Opt { + return func(p *Anthropic) { + p.model = &model + } +} + +// WithProject sets the Google Cloud Platform project ID for Vertex AI. +// Required for BackendVertexAI. +func WithProject(project string) Opt { + return func(p *Anthropic) { + p.project = project + } +} + +// WithRegion sets the Google Cloud Platform region for Vertex AI. +// Defaults to "us-central1" for Vertex AI. +func WithRegion(region string) Opt { + return func(p *Anthropic) { + p.region = region + } +} + +// WithVertexContext sets the context for Vertex AI authentication. +// Defaults to context.Background() if not specified. +func WithVertexContext(ctx context.Context) Opt { + return func(p *Anthropic) { + p.vertexCtx = ctx + } +} diff --git a/llms/anthropic/request_options.go b/llms/anthropic/request_options.go new file mode 100644 index 0000000..8275dfd --- /dev/null +++ b/llms/anthropic/request_options.go @@ -0,0 +1,10 @@ +package anthropic + +// RequestOptions contains Anthropic-specific request configuration. +type RequestOptions struct { + // BudgetTokens is the maximum number of tokens to spend on extended thinking. + // Only applicable with models that support extended thinking. + BudgetTokens *int +} + +func (RequestOptions) ProviderRequestOptions() {}