Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions examples/instrument_langchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Example: Instrument a LangChain chain with automatic span capture.

Requires:
pip install layerlens[langchain] langchain-openai
export LAYERLENS_STRATIX_API_KEY="your-api-key"
export OPENAI_API_KEY="your-openai-key"
"""

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

from layerlens import Stratix
from layerlens.instrument.adapters.frameworks.langchain import LangChainCallbackHandler

client = Stratix()
handler = LangChainCallbackHandler(client)

# Build a simple chain
prompt = ChatPromptTemplate.from_template("Answer this question concisely: {question}")
llm = ChatOpenAI(model="gpt-4o")
chain = prompt | llm | StrOutputParser()

if __name__ == "__main__":
# The callback handler captures the full chain execution as a trace
result = chain.invoke(
{"question": "What is retrieval-augmented generation?"},
config={"callbacks": [handler]},
)
print(f"Answer: {result}")
46 changes: 46 additions & 0 deletions examples/instrument_openai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Example: Instrument OpenAI with automatic LLM span capture.

Requires:
pip install layerlens[openai]
export LAYERLENS_STRATIX_API_KEY="your-api-key"
export OPENAI_API_KEY="your-openai-key"
"""

import openai
from layerlens import Stratix
from layerlens.instrument import span, trace
from layerlens.instrument.adapters.providers.openai import instrument_openai

client = Stratix()
openai_client = openai.OpenAI()

# Instrument the OpenAI client — all chat.completions.create calls
# inside a @trace will generate LLM spans automatically.
instrument_openai(openai_client)


@trace(client)
def qa_agent(question: str):
"""Simple Q&A agent with a retrieval step and an LLM call."""

# Manual span for a retrieval step
with span("retrieve", kind="retriever") as s:
# In a real app, this would query a vector database
docs = ["Python is a programming language.", "It was created by Guido van Rossum."]
s.output = docs

# The OpenAI call is automatically instrumented — no span() needed
response = openai_client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"Answer using this context: {docs}"},
{"role": "user", "content": question},
],
)

return response.choices[0].message.content


if __name__ == "__main__":
answer = qa_agent("What is Python and who created it?")
print(f"Answer: {answer}")
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ classifiers = [

[project.optional-dependencies]
cli = ["click>=8.0.0"]
openai = ["openai>=1.0.0"]
anthropic = ["anthropic>=0.18.0"]
langchain = ["langchain-core>=0.1.0"]
litellm = ["litellm>=1.0.0"]

[project.urls]
Homepage = "https://github.com/LayerLens/stratix-python"
Expand Down Expand Up @@ -136,8 +140,11 @@ known-first-party = ["openai", "tests"]
"bin/**.py" = ["T201", "T203"]
"scripts/**.py" = ["T201", "T203"]
"tests/**.py" = ["T201", "T203"]
"tests/instrument/**.py" = ["T201", "T203", "ARG"]
"examples/**.py" = ["T201", "T203"]
"src/layerlens/cli/**" = ["T201", "T203"]
"src/layerlens/instrument/adapters/frameworks/langchain.py" = ["ARG002"]
"src/layerlens/instrument/adapters/frameworks/langgraph.py" = ["ARG002"]

[tool.pyright]
include = ["src", "tests"]
Expand Down
130 changes: 130 additions & 0 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,28 @@
# universal: false

-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.13.3
# via litellm
aiosignal==1.4.0
# via aiohttp
annotated-doc==0.0.4
# via typer
annotated-types==0.7.0
# via pydantic
anthropic==0.86.0
# via layerlens
anyio==4.9.0
# via anthropic
# via httpx
# via openai
async-timeout==5.0.1
# via aiohttp
attrs==26.1.0
# via aiohttp
# via jsonschema
# via referencing
backports-tarfile==1.2.0
# via jaraco-context
build==1.3.0
Expand All @@ -27,30 +45,57 @@ charset-normalizer==3.4.3
# via requests
click==8.1.8
# via layerlens
# via litellm
# via typer
coverage==7.10.2
# via pytest-cov
cryptography==46.0.5
# via secretstorage
distro==1.9.0
# via anthropic
# via openai
docstring-parser==0.17.0
# via anthropic
docutils==0.22
# via readme-renderer
exceptiongroup==1.3.0
# via anyio
# via pytest
fastuuid==0.14.0
# via litellm
filelock==3.19.1
# via huggingface-hub
frozenlist==1.8.0
# via aiohttp
# via aiosignal
fsspec==2025.10.0
# via huggingface-hub
h11==0.16.0
# via httpcore
hf-xet==1.4.2
# via huggingface-hub
httpcore==1.0.9
# via httpx
httpx==0.28.1
# via anthropic
# via huggingface-hub
# via langsmith
# via layerlens
# via litellm
# via openai
huggingface-hub==1.7.2
# via tokenizers
id==1.5.0
# via twine
idna==3.10
# via anyio
# via httpx
# via requests
# via yarl
importlib-metadata==8.7.0
# via build
# via keyring
# via litellm
# via twine
iniconfig==2.1.0
# via pytest
Expand All @@ -63,35 +108,75 @@ jaraco-functools==4.2.1
jeepney==0.9.0
# via keyring
# via secretstorage
jinja2==3.1.6
# via litellm
jiter==0.13.0
# via anthropic
# via openai
jsonpatch==1.33
# via langchain-core
jsonpointer==3.0.0
# via jsonpatch
jsonschema==4.25.1
# via litellm
jsonschema-specifications==2025.9.1
# via jsonschema
keyring==25.6.0
# via twine
langchain-core==0.3.83
# via layerlens
langsmith==0.4.37
# via langchain-core
litellm==1.82.6
# via layerlens
markdown-it-py==3.0.0
# via rich
markupsafe==3.0.3
# via jinja2
mdurl==0.1.2
# via markdown-it-py
more-itertools==10.7.0
# via jaraco-classes
# via jaraco-functools
multidict==6.7.1
# via aiohttp
# via yarl
mypy==1.17.0
mypy-extensions==1.1.0
# via mypy
nh3==0.3.0
# via readme-renderer
nodeenv==1.9.1
# via pyright
openai==2.29.0
# via layerlens
# via litellm
orjson==3.11.5
# via langsmith
packaging==25.0
# via build
# via huggingface-hub
# via langchain-core
# via langsmith
# via pytest
# via twine
pathspec==0.12.1
# via mypy
pluggy==1.6.0
# via pytest
# via pytest-cov
propcache==0.4.1
# via aiohttp
# via yarl
pycparser==2.23
# via cffi
pydantic==2.11.7
# via anthropic
# via langchain-core
# via langsmith
# via layerlens
# via litellm
# via openai
pydantic-core==2.33.2
# via pydantic
pygments==2.19.2
Expand All @@ -104,42 +189,87 @@ pyright==1.1.399
pytest==8.4.1
# via pytest-cov
pytest-cov==6.2.1
python-dotenv==1.2.1
# via litellm
pyyaml==6.0.3
# via huggingface-hub
# via langchain-core
readme-renderer==44.0
# via twine
referencing==0.36.2
# via jsonschema
# via jsonschema-specifications
regex==2026.1.15
# via tiktoken
requests==2.32.5
# via id
# via langsmith
# via requests-toolbelt
# via tiktoken
# via twine
requests-toolbelt==1.0.0
# via langsmith
# via twine
rfc3986==2.0.0
# via twine
rich==14.1.0
# via twine
# via typer
rpds-py==0.27.1
# via jsonschema
# via referencing
ruff==0.12.7
secretstorage==3.3.3
# via keyring
shellingham==1.5.4
# via typer
sniffio==1.3.1
# via anthropic
# via anyio
# via openai
tenacity==9.1.2
# via langchain-core
tiktoken==0.12.0
# via litellm
tokenizers==0.22.2
# via litellm
tomli==2.2.1
# via build
# via coverage
# via mypy
# via pytest
tqdm==4.67.3
# via huggingface-hub
# via openai
twine==6.1.0
typer==0.23.2
# via huggingface-hub
typing-extensions==4.14.1
# via aiosignal
# via anthropic
# via anyio
# via cryptography
# via exceptiongroup
# via huggingface-hub
# via langchain-core
# via multidict
# via mypy
# via openai
# via pydantic
# via pydantic-core
# via pyright
# via referencing
# via typing-inspection
typing-inspection==0.4.1
# via pydantic
urllib3==2.5.0
# via requests
# via twine
uuid-utils==0.14.1
# via langchain-core
yarl==1.22.0
# via aiohttp
zipp==3.23.0
# via importlib-metadata
zstandard==0.25.0
# via langsmith
Loading
Loading