LangSmith observability for LLM and robot SDKs
Project description
One line. Full LangSmith observability for robot SDKs.
Quick Start: Robot Tracing
from unitree_sdk2py.go2.sport.sport_client import SportClient
from shadowdance import ShadowDance
# Your existing robot code
client = SportClient()
client.Init()
# ONE LINE - wrap with ShadowDance
client = ShadowDance(client) # <- that's all you need!
# Everything else unchanged - now fully traced
client.StandUp()
client.Move(0.3, 0, 0)
client.Damp()
Every robot command is now a traced LangSmith event with full inputs, outputs, and timing.
Connect to LLMs: Code-as-Policies
Add LLM decision-making and trace the full stack (vision → planning → execution):
from shadowdance import ShadowDance
from openai import OpenAI
# Wrap your robot (as above)
robot = SportClient()
robot = ShadowDance(robot, run_type="tool")
# Wrap your LLM (ONE LINE)
llm = OpenAI()
llm = ShadowDance(llm, run_type="llm")
# Simple code-as-policies: LLM generates robot commands
task = "move forward and stop"
prompt = f"Generate robot commands for: {task}"
response = llm.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
# Execute LLM-generated commands (traced!)
exec(response.choices[0].message.content) # e.g., robot.Move(0.3, 0, 0)
Now in LangSmith you see the full chain: LLM reasoning → generated code → robot execution.
Architecture
Modern LLM-powered robots use a layered architecture:
┌─────────────────────────────────────────┐
│ Your Agent Code │
│ ShadowDance(agent, run_type="chain") │
├─────────────────────────────────────────┤
│ LLM (OpenAI, etc.) │
│ ShadowDance(llm, run_type="llm") │
│ "pick up box" → [move, grasp, lift] │
├─────────────────────────────────────────┤
│ Robot SDK (Unitree, etc.) │
│ ShadowDance(robot, run_type="tool") │
│ Move(0.3, 0, 0), StandUp(), etc. │
└─────────────────────────────────────────┘
Wrap each layer with ShadowDance → see the full decision chain in LangSmith.
Installation
pip install shadowdance
Setup
# Load environment variables (create .env with your keys)
source .env
The .env file contains:
# LangSmith tracing
LANGCHAIN_API_KEY=...
LANGCHAIN_TRACING_V2=true
LANGCHAIN_PROJECT=shadowdance
# OpenRouter (OpenAI-compatible API)
OPENAI_API_KEY=...
OPENAI_BASE_URL=https://openrouter.ai/api/v1
# Default model for vision and planning
DEFAULT_MODEL=openrouter/hunter-alpha
Using Different Models
Change DEFAULT_MODEL in .env to use different models:
- Vision + Text:
openrouter/hunter-alpha(multimodal) - Free models: See OpenRouter's model list
Test Connection
python examples/test_openrouter.py
Examples
Full Demo: Code-as-Policies
python examples/code_as_policies.py
This demonstrates the complete Code-as-Policies approach:
- VLM analyzes image → detects white box at [0.0, 0.1, 0.72]
- LLM generates Python code →
robot.move_to(...),robot.close_gripper(...) - Safe executor runs code → robot picks up box
- ShadowDance traces everything → debug in LangSmith
Task: "Pick up the white box"
↓
Vision: white_box detected at [0.0, 0.1, 0.72]
↓
LLM: Generates 4-line Python program
↓
Robot: move_to → close_gripper → move_to (SUCCESS)
Run Types
LangSmith has different run types for better dashboard filtering:
| Run Type | Use Case | Example |
|---|---|---|
"llm" |
LLM/VLM API calls | OpenAI, Anthropic, vision models |
"tool" |
Function/tool calls | Robot commands, API wrappers |
"chain" |
Orchestration logic | Agents, multi-step workflows |
"retriever" |
Document retrieval | RAG systems, vector stores |
"embedding" |
Embedding generation | Text embeddings |
"prompt" |
Prompt formatting | Custom prompt templates |
# LLM calls
client = ShadowDance(OpenAI(), run_type="llm")
# Robot/tool calls
client = ShadowDance(SportClient(), run_type="tool")
# Agent orchestration
agent = ShadowDance(MyAgent(), run_type="chain")
Datasets & Experiments
Use ShadowDance with LangSmith datasets for robot evaluation and regression testing:
from shadowdance import ShadowDance
# Log all executions to a dataset
robot = ShadowDance(
SportClient(),
run_type="tool",
log_to_dataset="robot-tasks" # Creates dataset automatically
)
# Every command is logged as an example
robot.StandUp() # ✓ Logged with inputs, outputs, success
robot.Move(0.3, 0, 0) # ✓ Logged with duration, result
In LangSmith:
- Go to Datasets & Experiments tab
- Find
robot-tasksdataset with all executions - Create experiments to compare robot versions
- Run regression tests on code changes
Example: Evaluate robot configurations
python examples/robot_evaluation.py
This creates datasets (robot-eval-v1, robot-eval-v2) and compares task success rates across configurations.
Nested Tracing
Organize robot primitives under task runs for better visibility:
Option 1: @task Decorator (Recommended)
from shadowdance import ShadowDance, task
@task("pick_up_box") # Creates parent run
def pick_up_box():
robot = ShadowDance(SportClient())
robot.StandUp() # Nested under "pick_up_box"
robot.Move(0.3, 0, 0) # Nested
robot.Damp() # Nested
pick_up_box()
Option 2: task_context Manager
from shadowdance import ShadowDance, task_context
with task_context("move_to_kitchen"):
robot = ShadowDance(SportClient())
robot.StandUp()
robot.Move(0.5, 0, 0)
Option 3: Nested Tasks
@task("complex_manipulation")
def complex_task():
# Sub-task 1
with task_context("grasp_object"):
robot.Move(0.1, 0, 0)
robot.close_gripper(0.08)
# Sub-task 2
with task_context("lift_object"):
robot.Move(0, 0, 0.15)
In LangSmith Runs dashboard:
pick_up_box (chain)
├── StandUp (tool)
├── Move (tool)
└── Damp (tool)
move_to_kitchen (chain)
├── StandUp (tool)
└── Move (tool)
Example:
python examples/nested_tracing.py
Code-as-Policies (Full Demo)
Modern LLM robot architecture: VLM → LLM → Code → Robot
python examples/code_as_policies.py
This demonstrates the Code-as-Policies approach:
- VLM analyzes image → detects white box at [0.0, 0.1, 0.72]
- LLM generates Python code →
robot.move_to(...),robot.close_gripper(...) - Safe executor runs code → robot picks up box
- ShadowDance traces everything → debug in LangSmith
Task: "Pick up the white box"
↓
Vision: white_box detected at [0.0, 0.1, 0.72]
↓
LLM: Generates 4-line Python program
↓
Robot: move_to → close_gripper → move_to (SUCCESS)
Example output in LangSmith
Run: robot_session
└── Move(vx=0.3, vy=0, vyaw=0) 12ms ✓
└── StandUp() 8ms ✓
└── Move(vx=0, vy=0.3, vyaw=0) 11ms ✓
└── Damp() 9ms ✓
View your traces at smith.langchain.com
Testing
# Run unit tests
python test_shadowdance.py
# Run with virtual robot
python examples/with_virtual_robot.py
API
ShadowDance(client)
Wraps a client object with LangSmith tracing.
Args:
client: The Unitree SDK client object to wrap
Returns:
- A proxy object that intercepts all method calls
Example:
wrapped = ShadowDance(client)
wrapped.Move(0.3, 0, 0) # Traced as "Move" in LangSmith
File structure
./shadowdance.py # Main implementation
./test_shadowdance.py # Unit tests
./examples/
├── basic.py # Basic usage
├── error_handling.py # Error handling demo
├── virtual_robot.py # Virtual robot server
└── with_virtual_robot.py # Virtual robot + LangSmith demo
./pyproject.toml # Package configuration
./requirements.txt # Dependencies
./.env # LangSmith credentials (gitignored)
Why
The Unitree SDK has no logging, no observability, no way to know why your robot did what it did. LangSmith fixes that. This wrapper connects them with one line of code.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shadowdance-0.3.0.tar.gz.
File metadata
- Download URL: shadowdance-0.3.0.tar.gz
- Upload date:
- Size: 45.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1f08f287a6f61afe920631088ae673a9853307ff41b6f5ec30da69c81b22f0f
|
|
| MD5 |
42b1dd52f6119e78a6bb87873dfb19ae
|
|
| BLAKE2b-256 |
b5711fe1067fb0acb31461bd5a2da4b1a982559a3ff529ee4dc6bf4e17d27c82
|
File details
Details for the file shadowdance-0.3.0-py3-none-any.whl.
File metadata
- Download URL: shadowdance-0.3.0-py3-none-any.whl
- Upload date:
- Size: 8.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55a6d39b5691b177784f7fa91a234d8b715261b625392a0cc3c9c1de6ef82b0a
|
|
| MD5 |
fba9860a424afec49b034f395cf9fd41
|
|
| BLAKE2b-256 |
b78b5b04f0251aab6f59aae1bac3383ae1d0f884f6028519a03a4f529ca0125f
|