From ed059864a67f7cffd37ae644a4627e4864761d18 Mon Sep 17 00:00:00 2001 From: Prajwal Raymond Moras Date: Wed, 10 Jun 2026 15:35:45 +0530 Subject: [PATCH 1/2] feat(sample-app): add FastAPI + LiteLLM tracing example --- packages/sample-app/pyproject.toml | 2 + .../sample_app/fastapi_litellm_example.py | 37 ++++++ packages/sample-app/uv.lock | 117 ++++++++++++------ 3 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 packages/sample-app/sample_app/fastapi_litellm_example.py diff --git a/packages/sample-app/pyproject.toml b/packages/sample-app/pyproject.toml index fe7b26742d..fc540b10ad 100644 --- a/packages/sample-app/pyproject.toml +++ b/packages/sample-app/pyproject.toml @@ -37,6 +37,8 @@ dependencies = [ "llama-index-llms-huggingface>=0.6.0,<0.7.0", "llama-index-llms-huggingface-api>=0.6.0,<0.7.0", "litellm>=1.51.0,<2", + "fastapi>=0.115.0,<1", + "uvicorn>=0.32.0,<1", "llama-index-vector-stores-chroma>=0.5.0,<0.6.0", "langchain-openai>=1.0.0,<2.0.0", "google-generativeai>=0.8.3,<0.9.0", diff --git a/packages/sample-app/sample_app/fastapi_litellm_example.py b/packages/sample-app/sample_app/fastapi_litellm_example.py new file mode 100644 index 0000000000..3e8b4e2477 --- /dev/null +++ b/packages/sample-app/sample_app/fastapi_litellm_example.py @@ -0,0 +1,37 @@ +import os + +import litellm +from dotenv import load_dotenv +from fastapi import FastAPI +from pydantic import BaseModel + +from traceloop.sdk import Traceloop +from traceloop.sdk.decorators import task, workflow + +load_dotenv() + +Traceloop.init(app_name="fastapi_litellm_example", disable_batch=True) + +app = FastAPI() + +class ChatRequest(BaseModel): + message: str + +@task(name="call_llm") +def call_llm(message: str) -> str: + response = litellm.completion( + model=os.environ.get("LLM_MODEL", "openai/gpt-4o-mini"), + messages=[{"role": "user", "content": message}], + api_base=os.environ.get("LLM_API_BASE", None), + ) + return response.choices[0].message.content + +@workflow(name="chat_workflow") +def chat_workflow(message: str) -> str: + return call_llm(message) + +@app.post("/chat") +async def chat(request: ChatRequest): + reply = chat_workflow(request.message) + return {"reply": reply} + diff --git a/packages/sample-app/uv.lock b/packages/sample-app/uv.lock index f4ca95e07b..7da7221180 100644 --- a/packages/sample-app/uv.lock +++ b/packages/sample-app/uv.lock @@ -219,6 +219,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -1143,6 +1152,22 @@ lua = [ { name = "lupa" }, ] +[[package]] +name = "fastapi" +version = "0.136.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, +] + [[package]] name = "fastavro" version = "1.12.1" @@ -3708,7 +3733,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-agno" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-agno" } dependencies = [ { name = "opentelemetry-api" }, @@ -3748,7 +3773,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-alephalpha" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-alephalpha" } dependencies = [ { name = "opentelemetry-api" }, @@ -3786,7 +3811,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-anthropic" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-anthropic" } dependencies = [ { name = "opentelemetry-api" }, @@ -3824,7 +3849,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-bedrock" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-bedrock" } dependencies = [ { name = "opentelemetry-api" }, @@ -3860,7 +3885,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-chromadb" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-chromadb" } dependencies = [ { name = "opentelemetry-api" }, @@ -3895,7 +3920,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-cohere" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-cohere" } dependencies = [ { name = "opentelemetry-api" }, @@ -3933,7 +3958,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-crewai" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-crewai" } dependencies = [ { name = "opentelemetry-api" }, @@ -3969,7 +3994,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-google-generativeai" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-google-generativeai" } dependencies = [ { name = "opentelemetry-api" }, @@ -4006,7 +4031,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-groq" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-groq" } dependencies = [ { name = "opentelemetry-api" }, @@ -4044,7 +4069,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-haystack" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-haystack" } dependencies = [ { name = "opentelemetry-api" }, @@ -4081,7 +4106,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-lancedb" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-lancedb" } dependencies = [ { name = "opentelemetry-api" }, @@ -4121,7 +4146,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-langchain" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-langchain" } dependencies = [ { name = "opentelemetry-api" }, @@ -4175,7 +4200,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-llamaindex" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-llamaindex" } dependencies = [ { name = "inflection" }, @@ -4238,7 +4263,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-marqo" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-marqo" } dependencies = [ { name = "opentelemetry-api" }, @@ -4276,7 +4301,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-mcp" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-mcp" } dependencies = [ { name = "opentelemetry-api" }, @@ -4316,7 +4341,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-milvus" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-milvus" } dependencies = [ { name = "opentelemetry-api" }, @@ -4330,7 +4355,7 @@ requires-dist = [ { name = "opentelemetry-api", specifier = ">=1.38.0,<2" }, { name = "opentelemetry-instrumentation", specifier = ">=0.59b0" }, { name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" }, - { name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.5.1,<0.6.0" }, + { name = "opentelemetry-semantic-conventions-ai", editable = "../opentelemetry-semantic-conventions-ai" }, { name = "pymilvus", marker = "extra == 'instruments'" }, ] provides-extras = ["instruments"] @@ -4352,7 +4377,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-mistralai" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-mistralai" } dependencies = [ { name = "opentelemetry-api" }, @@ -4389,7 +4414,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-ollama" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-ollama" } dependencies = [ { name = "opentelemetry-api" }, @@ -4427,7 +4452,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-openai" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-openai" } dependencies = [ { name = "opentelemetry-api" }, @@ -4464,7 +4489,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-openai-agents" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-openai-agents" } dependencies = [ { name = "opentelemetry-api" }, @@ -4503,7 +4528,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-pinecone" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-pinecone" } dependencies = [ { name = "opentelemetry-api" }, @@ -4517,7 +4542,7 @@ requires-dist = [ { name = "opentelemetry-api", specifier = ">=1.38.0,<2" }, { name = "opentelemetry-instrumentation", specifier = ">=0.59b0" }, { name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" }, - { name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.5.1,<0.6.0" }, + { name = "opentelemetry-semantic-conventions-ai", editable = "../opentelemetry-semantic-conventions-ai" }, { name = "pinecone", marker = "extra == 'instruments'", specifier = ">=5.1.0,<9" }, ] provides-extras = ["instruments"] @@ -4542,7 +4567,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-qdrant" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-qdrant" } dependencies = [ { name = "opentelemetry-api" }, @@ -4590,7 +4615,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-replicate" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-replicate" } dependencies = [ { name = "opentelemetry-api" }, @@ -4642,7 +4667,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-sagemaker" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-sagemaker" } dependencies = [ { name = "opentelemetry-api" }, @@ -4707,7 +4732,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-together" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-together" } dependencies = [ { name = "opentelemetry-api" }, @@ -4745,7 +4770,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-transformers" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-transformers" } dependencies = [ { name = "opentelemetry-api" }, @@ -4792,7 +4817,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-vertexai" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-vertexai" } dependencies = [ { name = "opentelemetry-api" }, @@ -4830,7 +4855,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-voyageai" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-voyageai" } dependencies = [ { name = "opentelemetry-api" }, @@ -4868,7 +4893,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-watsonx" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-watsonx" } dependencies = [ { name = "opentelemetry-api" }, @@ -4906,7 +4931,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-weaviate" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-weaviate" } dependencies = [ { name = "opentelemetry-api" }, @@ -4945,7 +4970,7 @@ test = [ [[package]] name = "opentelemetry-instrumentation-writer" -version = "0.60.0" +version = "0.61.0" source = { editable = "../opentelemetry-instrumentation-writer" } dependencies = [ { name = "opentelemetry-api" }, @@ -5023,15 +5048,25 @@ wheels = [ [[package]] name = "opentelemetry-semantic-conventions-ai" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } +version = "0.5.2" +source = { editable = "../opentelemetry-semantic-conventions-ai" } dependencies = [ { name = "opentelemetry-sdk" }, { name = "opentelemetry-semantic-conventions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/02/10aeacc37a38a3a8fa16ff67bec1ae3bf882539f6f9efb0f70acf802ca2d/opentelemetry_semantic_conventions_ai-0.5.1.tar.gz", hash = "sha256:153906200d8c1d2f8e09bd78dbef526916023de85ac3dab35912bfafb69ff04c", size = 26533, upload-time = "2026-03-26T14:20:38.73Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/22/41fb05f1dc5fda2c468e05a41814c20859016c85117b66c8a257cae814f6/opentelemetry_semantic_conventions_ai-0.5.1-py3-none-any.whl", hash = "sha256:25aeb22bd261543b4898a73824026d96770e5351209c7d07a0b1314762b1f6e4", size = 11250, upload-time = "2026-03-26T14:20:37.108Z" }, + +[package.metadata] +requires-dist = [ + { name = "opentelemetry-sdk", specifier = ">=1.38.0,<2" }, + { name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "autopep8", specifier = ">=2.2.0,<3" }, + { name = "pytest", specifier = ">=8.2.2,<9" }, + { name = "pytest-sugar", specifier = "==1.0.0" }, + { name = "ruff", specifier = ">=0.4.0" }, ] [[package]] @@ -6434,6 +6469,7 @@ dependencies = [ { name = "crewai" }, { name = "datasets" }, { name = "ddgs" }, + { name = "fastapi" }, { name = "fastmcp" }, { name = "google-cloud-aiplatform" }, { name = "google-generativeai" }, @@ -6478,6 +6514,7 @@ dependencies = [ { name = "tokenizers" }, { name = "traceloop-sdk" }, { name = "transformers" }, + { name = "uvicorn" }, { name = "voyageai" }, ] @@ -6502,6 +6539,7 @@ requires-dist = [ { name = "crewai", specifier = ">=1.0.0,<2" }, { name = "datasets", specifier = ">=2.21.0,<2.22.0" }, { name = "ddgs", specifier = ">=9.0.0,<10" }, + { name = "fastapi", specifier = ">=0.115.0,<1" }, { name = "fastmcp", specifier = ">=2.12.3,<3" }, { name = "google-cloud-aiplatform", specifier = ">=1.81.0,<2" }, { name = "google-generativeai", specifier = ">=0.8.3,<0.9.0" }, @@ -6546,6 +6584,7 @@ requires-dist = [ { name = "tokenizers", specifier = ">=0.22.0,<0.24.0" }, { name = "traceloop-sdk", editable = "../traceloop-sdk" }, { name = "transformers", specifier = ">=4.46.0,<5" }, + { name = "uvicorn", specifier = ">=0.32.0,<1" }, { name = "voyageai", specifier = ">=0.3.7" }, ] @@ -7090,7 +7129,7 @@ wheels = [ [[package]] name = "traceloop-sdk" -version = "0.60.0" +version = "0.61.0" source = { editable = "../traceloop-sdk" } dependencies = [ { name = "aiohttp" }, From 2003cfb5efece18d709873ccb0b2521f951bc7e1 Mon Sep 17 00:00:00 2001 From: Prajwal Raymond Moras Date: Tue, 16 Jun 2026 22:22:02 +0530 Subject: [PATCH 2/2] fix(sample-app): add ConsoleSpanExporter, error handling, and response validation --- .../sample-app/sample_app/fastapi_litellm_example.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/sample-app/sample_app/fastapi_litellm_example.py b/packages/sample-app/sample_app/fastapi_litellm_example.py index 3e8b4e2477..cb3061f574 100644 --- a/packages/sample-app/sample_app/fastapi_litellm_example.py +++ b/packages/sample-app/sample_app/fastapi_litellm_example.py @@ -2,7 +2,8 @@ import litellm from dotenv import load_dotenv -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException +from opentelemetry.sdk.trace.export import ConsoleSpanExporter from pydantic import BaseModel from traceloop.sdk import Traceloop @@ -10,7 +11,7 @@ load_dotenv() -Traceloop.init(app_name="fastapi_litellm_example", disable_batch=True) +Traceloop.init(app_name="fastapi_litellm_example", disable_batch=True, exporter=ConsoleSpanExporter()) app = FastAPI() @@ -24,6 +25,8 @@ def call_llm(message: str) -> str: messages=[{"role": "user", "content": message}], api_base=os.environ.get("LLM_API_BASE", None), ) + if not response.choices or not response.choices[0].message.content: + raise ValueError("Empty response from LLM") return response.choices[0].message.content @workflow(name="chat_workflow") @@ -32,6 +35,9 @@ def chat_workflow(message: str) -> str: @app.post("/chat") async def chat(request: ChatRequest): - reply = chat_workflow(request.message) + try: + reply = chat_workflow(request.message) + except Exception as e: + raise HTTPException(status_code=502, detail=str(e)) return {"reply": reply}