diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 476413d5..7762f8d3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -96,6 +96,7 @@ jobs:
- JavaScript/langchain
- JavaScript/openai-automated
- JavaScript/openai-manual
+ - JavaScript/spans
defaults:
run:
diff --git a/JavaScript/spans/README.md b/JavaScript/spans/README.md
new file mode 100644
index 00000000..5063713d
--- /dev/null
+++ b/JavaScript/spans/README.md
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ 📚
+ Documentation
+
+ •
+
+ 🖥️
+ Application
+
+ •
+
+ 🏠
+ Home
+
+
+
+## Getting started
+
+- Sign up for an Autoblocks account at https://app.autoblocks.ai
+- Grab your Autoblocks ingestion key from https://app.autoblocks.ai/settings/api-keys
+- Create a file named `.env` in this folder and include the following environment variables:
+
+```
+AUTOBLOCKS_INGESTION_KEY=
+```
+
+## Creating spans
+
+This example shows how you can establish parent / child relationships between your events by sending the `spanId` and `parentSpanId` properties.
+
+## Install dependencies
+
+```
+npm install
+```
+
+## Run the script
+
+```
+npm run start
+```
+
+## View the trace tree
+
+Go to the [explore page](https://app.autoblocks.ai/explore) and find the trace, then switch to the Trace Tree view. You should see something like this:
+
+![rag-span](https://github.com/autoblocksai/autoblocks-examples/assets/7498009/e5a0f0d9-8460-49a6-a8aa-d707d14323a6)
+
+Within the RAG span, we made two embeddings calls: these are both children of the RAG span.
+
+![embedding-span](https://github.com/autoblocksai/autoblocks-examples/assets/7498009/7f9c4b4a-8704-4f7c-97b0-fd9acfc01932)
+
+Then there is the LLM span at the end, which is not a child of the RAG span because we ended the RAG span before starting the LLM span:
+
+![llm-span](https://github.com/autoblocksai/autoblocks-examples/assets/7498009/682d5d57-b343-4d5a-851a-e4aa9acef867)
diff --git a/JavaScript/spans/package-lock.json b/JavaScript/spans/package-lock.json
new file mode 100644
index 00000000..22766e62
--- /dev/null
+++ b/JavaScript/spans/package-lock.json
@@ -0,0 +1,224 @@
+{
+ "name": "spans",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "spans",
+ "version": "0.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@autoblocks/client": "^0.0.15",
+ "dotenv-cli": "^7.3.0"
+ }
+ },
+ "node_modules/@autoblocks/client": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/@autoblocks/client/-/client-0.0.15.tgz",
+ "integrity": "sha512-rYUyyMO3+XdLLHFH7LlRz/zxSY4rLHUOCoVefnJvZKxud2INAeSfURDQ2nmJouv1zZREY9HedOY8PBa9g8NFvg==",
+ "dependencies": {
+ "axios": "^1.4.0",
+ "zod": "^3.21.4"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
+ "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
+ "node_modules/dotenv-cli": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.3.0.tgz",
+ "integrity": "sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw==",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "dotenv": "^16.3.0",
+ "dotenv-expand": "^10.0.0",
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "dotenv": "cli.js"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
+ "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.22.2",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz",
+ "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/JavaScript/spans/package.json b/JavaScript/spans/package.json
new file mode 100644
index 00000000..c13b870c
--- /dev/null
+++ b/JavaScript/spans/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "spans",
+ "version": "0.0.0",
+ "description": "Establish parent / child relationships between your events with the spanId and parentSpanId properties",
+ "type": "module",
+ "main": "src/index.js",
+ "scripts": {
+ "start": "dotenv -e .env -- node ./src/index.js"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "@autoblocks/client": "^0.0.15",
+ "dotenv-cli": "^7.3.0"
+ }
+}
diff --git a/JavaScript/spans/src/index.js b/JavaScript/spans/src/index.js
new file mode 100644
index 00000000..be8b2bb8
--- /dev/null
+++ b/JavaScript/spans/src/index.js
@@ -0,0 +1,85 @@
+import crypto from 'crypto';
+import { AutoblocksTracer } from '@autoblocks/client';
+
+const tracer = new AutoblocksTracer(process.env.AUTOBLOCKS_INGESTION_KEY, {
+ traceId: crypto.randomUUID(),
+ properties: {
+ provider: 'openai',
+ },
+});
+
+const spanStack = [];
+
+function startSpan() {
+ spanStack.push(crypto.randomUUID());
+ setSpanIds();
+}
+
+function endSpan() {
+ if (spanStack.length > 0) {
+ spanStack.pop();
+ setSpanIds();
+ }
+}
+
+function setSpanIds() {
+ let spanId = undefined;
+ let parentSpanId = undefined;
+
+ if (spanStack.length >= 2) {
+ spanId = spanStack[spanStack.length - 1];
+ parentSpanId = spanStack[spanStack.length - 2];
+ } else if (spanStack.length === 1) {
+ spanId = spanStack[0];
+ }
+
+ tracer.updateProperties({ spanId, parentSpanId });
+}
+
+const spanFunction = async (fn) => {
+ startSpan();
+ await fn();
+ endSpan();
+};
+
+async function makeEmbeddingRequest() {
+ await tracer.sendEvent('ai.embedding.request');
+
+ // Make embedding request...
+
+ await tracer.sendEvent('ai.embedding.response');
+}
+
+async function startRAGPipeline() {
+ await tracer.sendEvent('ai.rag.start');
+
+ // Simulate making multiple embedding requests within a RAG pipeline
+ await spanFunction(makeEmbeddingRequest);
+ await spanFunction(makeEmbeddingRequest);
+
+ await tracer.sendEvent('ai.rag.end');
+}
+
+async function makeLLMRequest() {
+ // Here we would use the RAG response to generate a prompt for the LLM
+
+ await tracer.sendEvent('ai.completion.request', {
+ properties: {
+ temperature: 0.5,
+ topP: 1,
+ },
+ });
+
+ await tracer.sendEvent('ai.completion.response', {
+ properties: {
+ totalTokens: 123,
+ },
+ });
+}
+
+async function run() {
+ await spanFunction(startRAGPipeline);
+ await spanFunction(makeLLMRequest);
+}
+
+run();
diff --git a/README.md b/README.md
index 7d0edebb..b994ed4a 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@
| [novel-ai-text-editor](/JavaScript/novel-ai-text-editor) | A Next.js app that uses [Novel](https://github.com/steven-tey/novel) and Autoblocks to power an AI-enabled text editor |
| [openai-automated](/JavaScript/openai-automated) | Automatic tracing of openai calls |
| [openai-manual](/JavaScript/openai-manual) | Manual tracing of openai calls |
+| [spans](/JavaScript/spans) | Establish parent / child relationships between your events with the spanId and parentSpanId properties |