diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f982bf8..5873651 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI on: - pull_request_target: + pull_request: types: [opened, synchronize] push: branches: @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: ["1.18", "1.19", "1.20", "1.21"] + go-version: ["1.22", "1.23", "1.24"] steps: - uses: actions/checkout@v4 @@ -19,9 +19,10 @@ jobs: uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} + cache: false - name: Install dependencies - run: find . -name go.mod -execdir go get . \; + run: GOWORK=off find . -name go.mod -execdir go get . \; - name: Build - run: find . -name go.mod -execdir go build . \; + run: GOWORK=off find . -name go.mod -execdir go build . \; - name: Test - run: find . -name go.mod -execdir go test ./... \; + run: GOWORK=off find . -name go.mod -execdir go test ./... \; diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d9c8794 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/traceloop/go-openllmetry + +go 1.23 + +replace github.com/traceloop/go-openllmetry/traceloop-sdk => ./traceloop-sdk + +replace github.com/traceloop/go-openllmetry/semconv-ai => ./semconv-ai diff --git a/go.work b/go.work index a59fcac..7a16144 100644 --- a/go.work +++ b/go.work @@ -1,7 +1,7 @@ -go 1.21 +go 1.23 use ( + sample-app semconv-ai traceloop-sdk - sample-app ) diff --git a/go.work.sum b/go.work.sum index 9d46c9d..477cba5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,6 +1,10 @@ cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= @@ -13,6 +17,7 @@ github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEM github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= @@ -20,29 +25,42 @@ github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQ github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= diff --git a/sample-app/README.md b/sample-app/README.md new file mode 100644 index 0000000..f3c35fb --- /dev/null +++ b/sample-app/README.md @@ -0,0 +1,44 @@ +# Sample Apps + +This directory contains sample applications demonstrating the Traceloop Go OpenLLMetry SDK. + +## OpenAI SDKs + +This sample app includes two different OpenAI Go SDKs for demonstration purposes: + +- **Sashabaranov SDK** (`github.com/sashabaranov/go-openai`) - Used in `main.go` and workflow examples +- **Official OpenAI SDK** (`github.com/openai/openai-go`) - Used in `tool_calling.go` + +## Regular Sample + +Run the regular sample that demonstrates basic prompt logging: + +```bash +go run . +``` + +## Tool Calling Sample + +Run the tool calling sample that demonstrates tool calling with the OpenAI Go SDK: + +```bash +go run . tool-calling +``` + +### Environment Variables + +Set the following environment variables: + +```bash +export OPENAI_API_KEY="your-openai-api-key" +export TRACELOOP_API_KEY="your-traceloop-api-key" +export TRACELOOP_BASE_URL="https://api.traceloop.com" # Optional +``` + +### Tool Calling Features + +The tool calling sample demonstrates: +- Request tools logging with function definitions +- Response tool calls logging with execution results +- Multi-turn conversations with tool execution +- Complete traceability of tool calling interactions \ No newline at end of file diff --git a/sample-app/go.mod b/sample-app/go.mod index b7720b2..3803420 100644 --- a/sample-app/go.mod +++ b/sample-app/go.mod @@ -1,5 +1,59 @@ module github.com/traceloop/go-openllmetry/sample-app -go 1.21 +go 1.23 -require github.com/sashabaranov/go-openai v1.18.1 +require ( + github.com/joho/godotenv v1.5.1 + github.com/openai/openai-go v0.1.0-alpha.35 + github.com/sashabaranov/go-openai v1.18.1 + github.com/traceloop/go-openllmetry/traceloop-sdk v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/cenkalti/backoff v2.2.1+incompatible // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jinzhu/copier v0.4.0 // indirect + github.com/kluctl/go-embed-python v0.0.0-3.11.6-20231002-1 // indirect + github.com/kluctl/go-jinja2 v0.0.0-20240108142937-8839259d2537 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/traceloop/go-openllmetry/semconv-ai v0.0.0-20250405130248-6b2b4b41102b // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/grpc v1.60.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) + +replace github.com/traceloop/go-openllmetry/traceloop-sdk => ../traceloop-sdk + +replace github.com/traceloop/go-openllmetry/semconv-ai => ../semconv-ai diff --git a/sample-app/go.sum b/sample-app/go.sum index 9d3ae1b..0ca4068 100644 --- a/sample-app/go.sum +++ b/sample-app/go.sum @@ -1,2 +1,129 @@ +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kluctl/go-embed-python v0.0.0-3.11.6-20231002-1 h1:L+ZH/eN5gE7eh3BTye/Z8td8YjbhEs6hzybVByz2twQ= +github.com/kluctl/go-embed-python v0.0.0-3.11.6-20231002-1/go.mod h1:2/V+QZL7VyhTXtKHorARyA7UYOizVV37M8kkXMEk+Kg= +github.com/kluctl/go-jinja2 v0.0.0-20240108142937-8839259d2537 h1:oG9FYqprfbAI9kQtec4D0gPwJqLJlS+euknEVz25gp0= +github.com/kluctl/go-jinja2 v0.0.0-20240108142937-8839259d2537/go.mod h1:7FmUmt2zgHJfJE82ZNY/AHNGsGdyHBaF3OA12r4Zj+8= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/openai/openai-go v0.1.0-alpha.35 h1:GZRy9b6gKe6Fa58Fd/CSefxtAjuyuLnSiOGN9H7747o= +github.com/openai/openai-go v0.1.0-alpha.35/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sashabaranov/go-openai v1.18.1 h1:AnLoJrFaFtcUYWCtz+8V0zrlXxkiwqpWlAmCAZUnDNQ= github.com/sashabaranov/go-openai v1.18.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +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/sample-app/main.go b/sample-app/main.go index 249fc3c..363adba 100644 --- a/sample-app/main.go +++ b/sample-app/main.go @@ -3,52 +3,77 @@ package main import ( "context" "fmt" + "log" "os" "time" + "github.com/joho/godotenv" "github.com/sashabaranov/go-openai" - tlp "github.com/traceloop/go-openllmetry/traceloop-sdk" + sdk "github.com/traceloop/go-openllmetry/traceloop-sdk" ) -func workflow_example() { +func main() { + // Load environment variables from .env file + if err := godotenv.Load(); err != nil { + log.Println("No .env file found, using environment variables") + } + + if len(os.Args) > 1 && os.Args[1] == "tool-calling" { + runToolCallingExample() + return + } + + // Default to workflow example using prompt registry + workflowExample() +} + +func workflowExample() { ctx := context.Background() - traceloop, err := tlp.NewClient(ctx, tlp.Config{ - BaseURL: "api-staging.traceloop.com", - APIKey: os.Getenv("TRACELOOP_API_KEY"), + traceloop, err := sdk.NewClient(ctx, sdk.Config{ + APIKey: os.Getenv("TRACELOOP_API_KEY"), }) - defer func() { traceloop.Shutdown(ctx) }() - if err != nil { fmt.Printf("NewClient error: %v\n", err) return } + defer func() { traceloop.Shutdown(ctx) }() - request, err := traceloop.GetOpenAIChatCompletionRequest("example-prompt", map[string]interface{}{"date": time.Now().Format("01/02")}) + // Get prompt from registry + request, err := traceloop.GetOpenAIChatCompletionRequest("question_answering", map[string]interface{}{ + "date": time.Now().Format("01/02"), + "question": "What's the weather like today?", + "information": "The current weather is sunny and 75 degrees.", + }) if err != nil { fmt.Printf("GetOpenAIChatCompletionRequest error: %v\n", err) return } - var promptMsgs []tlp.Message + // Convert to our format for logging + var promptMsgs []sdk.Message for i, message := range request.Messages { - promptMsgs = append(promptMsgs, tlp.Message{ + promptMsgs = append(promptMsgs, sdk.Message{ Index: i, Content: message.Content, Role: message.Role, }) } + // Log the prompt llmSpan, err := traceloop.LogPrompt( ctx, - tlp.Prompt{ + sdk.Prompt{ Vendor: "openai", Mode: "chat", Model: request.Model, Messages: promptMsgs, }, - tlp.WorkflowAttributes{ + sdk.WorkflowAttributes{ Name: "example-workflow", + AssociationProperties: map[string]string{ + "user_id": "demo-user", + }, }, ) if err != nil { @@ -56,6 +81,7 @@ func workflow_example() { return } + // Make actual OpenAI API call client := openai.NewClient(os.Getenv("OPENAI_API_KEY")) resp, err := client.CreateChatCompletion( context.Background(), @@ -66,23 +92,29 @@ func workflow_example() { return } - var completionMsgs []tlp.Message + // Convert response to our format for logging + var completionMsgs []sdk.Message for _, choice := range resp.Choices { - completionMsgs = append(completionMsgs, tlp.Message{ + completionMsgs = append(completionMsgs, sdk.Message{ Index: choice.Index, Content: choice.Message.Content, Role: choice.Message.Role, }) } - llmSpan.LogCompletion(ctx, tlp.Completion{ + // Log the completion + err = llmSpan.LogCompletion(ctx, sdk.Completion{ Model: resp.Model, Messages: completionMsgs, - }, tlp.Usage{ + }, sdk.Usage{ TotalTokens: resp.Usage.TotalTokens, CompletionTokens: resp.Usage.CompletionTokens, PromptTokens: resp.Usage.PromptTokens, }) + if err != nil { + fmt.Printf("LogCompletion error: %v\n", err) + return + } fmt.Println(resp.Choices[0].Message.Content) -} +} \ No newline at end of file diff --git a/sample-app/tool_calling.go b/sample-app/tool_calling.go new file mode 100644 index 0000000..f654899 --- /dev/null +++ b/sample-app/tool_calling.go @@ -0,0 +1,205 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + "time" + + "github.com/openai/openai-go" + "github.com/openai/openai-go/option" + sdk "github.com/traceloop/go-openllmetry/traceloop-sdk" +) + +type WeatherParams struct { + Location string `json:"location"` + Unit string `json:"unit,omitempty"` +} + +func getWeather(location, unit string) string { + return fmt.Sprintf("The weather in %s is sunny and 72°%s", location, unit) +} + +func runToolCallingExample() { + ctx := context.Background() + + traceloop, err := sdk.NewClient(ctx, sdk.Config{ + APIKey: os.Getenv("TRACELOOP_API_KEY"), + }) + if err != nil { + log.Printf("NewClient error: %v", err) + return + } + defer func() { traceloop.Shutdown(ctx) }() + + client := openai.NewClient( + option.WithAPIKey(os.Getenv("OPENAI_API_KEY")), + ) + + userPrompt := "What's the weather like in San Francisco?" + + // Define tools + tools := []sdk.Tool{ + { + Type: "function", + Function: sdk.ToolFunction{ + Name: "get_weather", + Description: "Get the current weather for a given location", + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "location": map[string]interface{}{ + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": map[string]interface{}{ + "type": "string", + "enum": []string{"celsius", "fahrenheit"}, + "description": "The unit of temperature", + }, + }, + "required": []string{"location"}, + }, + }, + }, + } + + // Create prompt + prompt := sdk.Prompt{ + Vendor: "openai", + Mode: "chat", + Model: "gpt-4o-mini", + Messages: []sdk.Message{ + { + Index: 0, + Role: "user", + Content: userPrompt, + }, + }, + Tools: tools, + } + + workflowAttrs := sdk.WorkflowAttributes{ + Name: "tool-calling-example", + AssociationProperties: map[string]string{ + "user_id": "demo-user", + }, + } + + fmt.Printf("User: %s\n", userPrompt) + + // Log the prompt + llmSpan, err := traceloop.LogPrompt(ctx, prompt, workflowAttrs) + if err != nil { + fmt.Printf("Error logging prompt: %v\n", err) + return + } + + // Make API call to OpenAI + startTime := time.Now() + resp, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{ + Messages: openai.F([]openai.ChatCompletionMessageParamUnion{ + openai.UserMessage(userPrompt), + }), + Model: openai.F(openai.ChatModelGPT4oMini), + Tools: openai.F([]openai.ChatCompletionToolParam{ + { + Type: openai.F(openai.ChatCompletionToolTypeFunction), + Function: openai.F(openai.FunctionDefinitionParam{ + Name: openai.F("get_weather"), + Description: openai.F("Get the current weather for a given location"), + Parameters: openai.F(openai.FunctionParameters{ + "type": "object", + "properties": map[string]interface{}{ + "location": map[string]interface{}{ + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": map[string]interface{}{ + "type": "string", + "enum": []string{"celsius", "fahrenheit"}, + "description": "The unit of temperature", + }, + }, + "required": []string{"location"}, + }), + }), + }, + }), + Temperature: openai.F(0.7), + }) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + duration := time.Since(startTime) + + fmt.Printf("\nAssistant: %s\n", resp.Choices[0].Message.Content) + + // Convert response to our format + var completionMessages []sdk.Message + for _, choice := range resp.Choices { + message := sdk.Message{ + Index: int(choice.Index), + Role: string(choice.Message.Role), + Content: choice.Message.Content, + } + + // Convert tool calls if present + if len(choice.Message.ToolCalls) > 0 { + for _, toolCall := range choice.Message.ToolCalls { + message.ToolCalls = append(message.ToolCalls, sdk.ToolCall{ + ID: toolCall.ID, + Type: string(toolCall.Type), + Function: sdk.ToolCallFunction{ + Name: toolCall.Function.Name, + Arguments: toolCall.Function.Arguments, + }, + }) + } + } + completionMessages = append(completionMessages, message) + } + + // Log the completion + completion := sdk.Completion{ + Model: resp.Model, + Messages: completionMessages, + } + + usage := sdk.Usage{ + TotalTokens: int(resp.Usage.TotalTokens), + CompletionTokens: int(resp.Usage.CompletionTokens), + PromptTokens: int(resp.Usage.PromptTokens), + } + + err = llmSpan.LogCompletion(ctx, completion, usage) + if err != nil { + fmt.Printf("Error logging completion: %v\n", err) + return + } + + // If tool calls were made, execute them + if len(resp.Choices[0].Message.ToolCalls) > 0 { + fmt.Println("\nTool calls requested:") + + for _, toolCall := range resp.Choices[0].Message.ToolCalls { + if toolCall.Function.Name == "get_weather" { + fmt.Printf("Tool call: %s with args: %s\n", toolCall.Function.Name, toolCall.Function.Arguments) + + var params WeatherParams + if err := json.Unmarshal([]byte(toolCall.Function.Arguments), ¶ms); err != nil { + fmt.Printf("Error parsing arguments: %v\n", err) + continue + } + + result := getWeather(params.Location, params.Unit) + fmt.Printf("Function result: %s\n", result) + } + } + } + + fmt.Printf("\nRequest completed in %v\n", duration) +} \ No newline at end of file diff --git a/sample-app/workflow_example.go b/sample-app/workflow_example.go index c48f2d1..9ee3638 100644 --- a/sample-app/workflow_example.go +++ b/sample-app/workflow_example.go @@ -10,7 +10,7 @@ import ( tlp "github.com/traceloop/go-openllmetry/traceloop-sdk" ) -func main() { +func workflowMain() { ctx := context.Background() traceloop, err := tlp.NewClient(ctx, tlp.Config{ diff --git a/semconv-ai/go.mod b/semconv-ai/go.mod index e0bbac9..c7a4263 100644 --- a/semconv-ai/go.mod +++ b/semconv-ai/go.mod @@ -1,5 +1,5 @@ module github.com/traceloop/go-openllmetry/semconv-ai -go 1.21 +go 1.23 -require go.opentelemetry.io/otel v1.22.0 +require go.opentelemetry.io/otel v1.37.0 diff --git a/semconv-ai/go.sum b/semconv-ai/go.sum index 4d1278e..7466d77 100644 --- a/semconv-ai/go.sum +++ b/semconv-ai/go.sum @@ -1,12 +1,12 @@ 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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= 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/traceloop-sdk/go.mod b/traceloop-sdk/go.mod index c2a8598..d069289 100644 --- a/traceloop-sdk/go.mod +++ b/traceloop-sdk/go.mod @@ -1,22 +1,25 @@ module github.com/traceloop/go-openllmetry/traceloop-sdk -go 1.21 +go 1.23 require ( github.com/kluctl/go-jinja2 v0.0.0-20240108142937-8839259d2537 github.com/sashabaranov/go-openai v1.18.1 - go.opentelemetry.io/otel v1.22.0 + github.com/traceloop/go-openllmetry/semconv-ai v0.0.0-20250405130248-6b2b4b41102b + go.opentelemetry.io/otel v1.37.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 - go.opentelemetry.io/otel/trace v1.22.0 + go.opentelemetry.io/otel/trace v1.37.0 ) require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect @@ -36,13 +39,13 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jinzhu/copier v0.4.0 // indirect github.com/kluctl/go-embed-python v0.0.0-3.11.6-20231002-1 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 - go.opentelemetry.io/otel/sdk v1.22.0 + go.opentelemetry.io/otel/sdk v1.37.0 golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.33.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/traceloop-sdk/go.sum b/traceloop-sdk/go.sum index 04e776c..0f7d9f8 100644 --- a/traceloop-sdk/go.sum +++ b/traceloop-sdk/go.sum @@ -14,19 +14,22 @@ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgF github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -50,44 +53,50 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sashabaranov/go-openai v1.18.1 h1:AnLoJrFaFtcUYWCtz+8V0zrlXxkiwqpWlAmCAZUnDNQ= github.com/sashabaranov/go-openai v1.18.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/traceloop/go-openllmetry/semconv-ai v0.0.0-20250405130248-6b2b4b41102b h1:+U2PMGQGDoxvikp1nxkLaPlIDI37qcm9GEDjlibSR60= +github.com/traceloop/go-openllmetry/semconv-ai v0.0.0-20250405130248-6b2b4b41102b/go.mod h1:+e6rTO5swnV2JCuTc/fGSv8NMaKdgFatugW3DkVjv58= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= diff --git a/traceloop-sdk/sdk.go b/traceloop-sdk/sdk.go index bd9fb53..1adb423 100644 --- a/traceloop-sdk/sdk.go +++ b/traceloop-sdk/sdk.go @@ -2,6 +2,7 @@ package traceloop import ( "context" + "encoding/json" "fmt" "log" "net/http" @@ -83,9 +84,55 @@ func setMessagesAttribute(span apitrace.Span, prefix string, messages []Message) attribute.String(attrsPrefix+".content", message.Content), attribute.String(attrsPrefix+".role", message.Role), ) + + if len(message.ToolCalls) > 0 { + setToolCallsAttribute(span, attrsPrefix, message.ToolCalls) + } + } +} + + +// Tool calling attribute helpers for new types +func setToolCallsAttribute(span apitrace.Span, messagePrefix string, toolCalls []ToolCall) { + for i, toolCall := range toolCalls { + toolCallPrefix := fmt.Sprintf("%s.tool_calls.%d", messagePrefix, i) + span.SetAttributes( + attribute.String(toolCallPrefix+".id", toolCall.ID), + attribute.String(toolCallPrefix+".type", toolCall.Type), + attribute.String(toolCallPrefix+".name", toolCall.Function.Name), + attribute.String(toolCallPrefix+".arguments", toolCall.Function.Arguments), + ) + } +} + + + +func setToolsAttribute(span apitrace.Span, tools []Tool) { + if len(tools) == 0 { + return + } + + for i, tool := range tools { + prefix := fmt.Sprintf("%s.%d", string(semconvai.LLMRequestFunctions), i) + span.SetAttributes( + attribute.String(prefix+".name", tool.Function.Name), + attribute.String(prefix+".description", tool.Function.Description), + ) + + if tool.Function.Parameters != nil { + parametersJSON, err := json.Marshal(tool.Function.Parameters) + if err == nil { + span.SetAttributes( + attribute.String(prefix+".parameters", string(parametersJSON)), + ) + } else { + fmt.Printf("Failed to marshal tool parameters for %s: %v\n", tool.Function.Name, err) + } + } } } + func (instance *Traceloop) tracerName() string { if instance.config.TracerName != "" { return instance.config.TracerName @@ -98,18 +145,26 @@ func (instance *Traceloop) getTracer() apitrace.Tracer { return (*instance.tracerProvider).Tracer(instance.tracerName()) } +// New workflow-based API func (instance *Traceloop) LogPrompt(ctx context.Context, prompt Prompt, workflowAttrs WorkflowAttributes) (LLMSpan, error) { spanName := fmt.Sprintf("%s.%s", prompt.Vendor, prompt.Mode) _, span := instance.getTracer().Start(ctx, spanName) - span.SetAttributes( + attrs := []attribute.KeyValue{ semconvai.LLMVendor.String(prompt.Vendor), semconvai.LLMRequestModel.String(prompt.Model), semconvai.LLMRequestType.String(prompt.Mode), semconvai.TraceloopWorkflowName.String(workflowAttrs.Name), - ) + } + + // Add association properties if provided + for key, value := range workflowAttrs.AssociationProperties { + attrs = append(attrs, attribute.String("traceloop.association.properties."+key, value)) + } + span.SetAttributes(attrs...) setMessagesAttribute(span, "llm.prompts", prompt.Messages) + setToolsAttribute(span, prompt.Tools) return LLMSpan{ span: span, @@ -127,10 +182,10 @@ func (llmSpan *LLMSpan) LogCompletion(ctx context.Context, completion Completion setMessagesAttribute(llmSpan.span, "llm.completions", completion.Messages) defer llmSpan.span.End() - return nil } + func (instance *Traceloop) Shutdown(ctx context.Context) { if instance.tracerProvider != nil { instance.tracerProvider.Shutdown(ctx) diff --git a/traceloop-sdk/sdk_test.go b/traceloop-sdk/sdk_test.go new file mode 100644 index 0000000..5b53763 --- /dev/null +++ b/traceloop-sdk/sdk_test.go @@ -0,0 +1,169 @@ +package traceloop + +import ( + "context" + "testing" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" +) + +func TestLogPromptSpanAttributes(t *testing.T) { + // Create in-memory exporter for testing + exporter := tracetest.NewInMemoryExporter() + + // Create tracer provider with in-memory exporter + tp := trace.NewTracerProvider( + trace.WithSyncer(exporter), + ) + otel.SetTracerProvider(tp) + defer tp.Shutdown(context.Background()) + + // Create traceloop instance + tl := &Traceloop{ + config: Config{ + BaseURL: "https://api.traceloop.com", + APIKey: "test-key", + }, + tracerProvider: tp, + } + + // Create prompt with tool calling using new API + prompt := Prompt{ + Vendor: "openai", + Mode: "chat", + Model: "gpt-4o-mini", + Messages: []Message{ + { + Index: 0, + Role: "user", + Content: "What's the weather like in San Francisco?", + }, + }, + Tools: []Tool{ + { + Type: "function", + Function: ToolFunction{ + Name: "get_weather", + Description: "Get the current weather for a given location", + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "location": map[string]interface{}{ + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + }, + "required": []string{"location"}, + }, + }, + }, + }, + } + + workflowAttrs := WorkflowAttributes{ + Name: "test-workflow", + AssociationProperties: map[string]string{ + "entity_name": "test-entity", + }, + } + + // Log the prompt using new workflow API + llmSpan, err := tl.LogPrompt(context.Background(), prompt, workflowAttrs) + if err != nil { + t.Fatalf("LogPrompt failed: %v", err) + } + + // Log completion with tool calls + completion := Completion{ + Model: "gpt-4o-mini-2024-07-18", + Messages: []Message{ + { + Index: 0, + Role: "assistant", + Content: "", + ToolCalls: []ToolCall{ + { + ID: "call_YkIfypBQrmpUpxsKuS9aNdKg", + Type: "function", + Function: ToolCallFunction{ + Name: "get_weather", + Arguments: "{\"location\":\"San Francisco, CA\"}", + }, + }, + }, + }, + }, + } + + usage := Usage{ + TotalTokens: 99, + CompletionTokens: 17, + PromptTokens: 82, + } + + err = llmSpan.LogCompletion(context.Background(), completion, usage) + if err != nil { + t.Fatalf("LogCompletion failed: %v", err) + } + + // Get the recorded spans + spans := exporter.GetSpans() + if len(spans) != 1 { + t.Fatalf("Expected 1 span, got %d", len(spans)) + } + + span := spans[0] + t.Logf("Span name: %s", span.Name) + t.Logf("Total attributes: %d", len(span.Attributes)) + + // Print all attributes for debugging + attributeMap := make(map[string]interface{}) + for _, attr := range span.Attributes { + key := string(attr.Key) + value := attr.Value.AsInterface() + attributeMap[key] = value + t.Logf("Attribute: %s = %v", key, value) + } + + // Assert on specific attributes + expectedAttrs := map[string]interface{}{ + "llm.vendor": "openai", + "llm.request.model": "gpt-4o-mini", + "llm.request.type": "chat", + "llm.response.model": "gpt-4o-mini-2024-07-18", + "llm.usage.total_tokens": int64(99), + "llm.usage.completion_tokens": int64(17), + "llm.usage.prompt_tokens": int64(82), + "traceloop.workflow.name": "test-workflow", + "traceloop.association.properties.entity_name": "test-entity", + "llm.prompts.0.content": "What's the weather like in San Francisco?", + "llm.prompts.0.role": "user", + "llm.completions.0.content": "", + "llm.completions.0.role": "assistant", + "llm.completions.0.tool_calls.0.id": "call_YkIfypBQrmpUpxsKuS9aNdKg", + "llm.completions.0.tool_calls.0.type": "function", + "llm.completions.0.tool_calls.0.name": "get_weather", + "llm.completions.0.tool_calls.0.arguments": "{\"location\":\"San Francisco, CA\"}", + "llm.request.functions.0.name": "get_weather", + "llm.request.functions.0.description": "Get the current weather for a given location", + } + + for expectedKey, expectedValue := range expectedAttrs { + actualValue, exists := attributeMap[expectedKey] + if !exists { + t.Errorf("Expected attribute %s not found", expectedKey) + } else if actualValue != expectedValue { + t.Errorf("Attribute %s: expected %v, got %v", expectedKey, expectedValue, actualValue) + } + } + + // Check for JSON attributes as well + if _, exists := attributeMap["llm.prompts"]; !exists { + t.Error("Expected llm.prompts JSON attribute not found") + } + if _, exists := attributeMap["llm.completions"]; !exists { + t.Error("Expected llm.completions JSON attribute not found") + } +} \ No newline at end of file diff --git a/traceloop-sdk/tracing.go b/traceloop-sdk/tracing.go index 1c799ec..d4b3841 100644 --- a/traceloop-sdk/tracing.go +++ b/traceloop-sdk/tracing.go @@ -15,10 +15,26 @@ import ( ) func newTraceloopExporter(ctx context.Context, config Config) (*otlp.Exporter, error) { + // WithEndpoint expects host:port format, no protocol or path + endpoint := config.BaseURL + // Remove protocol if present since WithEndpoint doesn't accept it + if strings.HasPrefix(endpoint, "https://") { + endpoint = strings.TrimPrefix(endpoint, "https://") + } + if strings.HasPrefix(endpoint, "http://") { + endpoint = strings.TrimPrefix(endpoint, "http://") + } + + // Add default HTTPS port if no port specified + if !strings.Contains(endpoint, ":") { + endpoint = endpoint + ":443" + } + return otlp.New( ctx, otlphttp.NewClient( - otlphttp.WithEndpoint(config.BaseURL), + otlphttp.WithEndpoint(endpoint), + otlphttp.WithURLPath("/v1/traces"), otlphttp.WithHeaders( map[string]string{ "Authorization": fmt.Sprintf("Bearer %s", config.APIKey), diff --git a/traceloop-sdk/tracing_types.go b/traceloop-sdk/tracing_types.go index a7b5ac9..f4efa32 100644 --- a/traceloop-sdk/tracing_types.go +++ b/traceloop-sdk/tracing_types.go @@ -1,9 +1,10 @@ package traceloop type Message struct { - Index int `json:"index"` - Role string `json:"role"` - Content string `json:"content"` + Index int `json:"index"` + Role string `json:"role"` + Content string `json:"content"` + ToolCalls []ToolCall `json:"tool_calls,omitempty"` } type Prompt struct { @@ -16,6 +17,7 @@ type Prompt struct { FrequencyPenalty float32 `json:"frequency_penalty"` PresencePenalty float32 `json:"presence_penalty"` Messages []Message `json:"messages"` + Tools []Tool `json:"tools,omitempty"` } type Completion struct { @@ -33,3 +35,25 @@ type Usage struct { CompletionTokens int `json:"completion_tokens"` PromptTokens int `json:"prompt_tokens"` } + +type ToolFunction struct { + Name string `json:"name"` + Description string `json:"description"` + Parameters interface{} `json:"parameters"` +} + +type Tool struct { + Type string `json:"type"` + Function ToolFunction `json:"function,omitempty"` +} + +type ToolCall struct { + ID string `json:"id"` + Type string `json:"type"` + Function ToolCallFunction `json:"function"` +} + +type ToolCallFunction struct { + Name string `json:"name"` + Arguments string `json:"arguments"` +} diff --git a/traceloop-sdk/utils.go b/traceloop-sdk/utils.go index 32d1c23..e5398c9 100644 --- a/traceloop-sdk/utils.go +++ b/traceloop-sdk/utils.go @@ -3,12 +3,23 @@ package traceloop import ( "fmt" "net/http" + "net/url" + "strings" "github.com/cenkalti/backoff" ) func (instance *Traceloop) fetchPath(path string) (*http.Response, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://%s/%s", instance.config.BaseURL, path), nil) + baseURL := instance.config.BaseURL + if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") { + baseURL = "https://" + baseURL + } + fullURL, err := url.JoinPath(baseURL, path) + if err != nil { + fmt.Printf("Failed to join URL path: %v\n", err) + return nil, err + } + req, err := http.NewRequest(http.MethodGet, fullURL, nil) if err != nil { fmt.Printf("Failed to create request: %v\n", err) return nil, err