Tag LangSmith traces by deployment environment
Browse files- .env.example +3 -0
- app/agent_tracing.py +16 -0
- app/chat_service.py +6 -0
- tests/test_agent_tracing.py +33 -0
- tests/test_chat_service.py +18 -1
.env.example
CHANGED
|
@@ -22,6 +22,9 @@ OPENROUTER_API_KEY=...
|
|
| 22 |
LANGSMITH_API_KEY=...
|
| 23 |
LANGSMITH_TRACING=true
|
| 24 |
LANGSMITH_PROJECT=ai-tutor-app
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
# To update documentation/add new course
|
| 27 |
HF_TOKEN=... https://huggingface.co/settings/tokens to read or write to private DATASET repos e.g. https://huggingface.co/datasets/towardsai-tutors/ai-tutor-data/tree/main
|
|
|
|
| 22 |
LANGSMITH_API_KEY=...
|
| 23 |
LANGSMITH_TRACING=true
|
| 24 |
LANGSMITH_PROJECT=ai-tutor-app
|
| 25 |
+
# Trace origin label (set HF Space variables to hf-dev or hf-prod; defaults to
|
| 26 |
+
# huggingface-space when SPACE_HOST exists, otherwise local)
|
| 27 |
+
# AI_TUTOR_DEPLOYMENT_ENV=local
|
| 28 |
|
| 29 |
# To update documentation/add new course
|
| 30 |
HF_TOKEN=... https://huggingface.co/settings/tokens to read or write to private DATASET repos e.g. https://huggingface.co/datasets/towardsai-tutors/ai-tutor-data/tree/main
|
app/agent_tracing.py
CHANGED
|
@@ -3,6 +3,9 @@ from __future__ import annotations
|
|
| 3 |
import os
|
| 4 |
|
| 5 |
DEFAULT_LANGSMITH_PROJECT = "ai-tutor-app"
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
TRUE_VALUES = {"1", "true", "t", "yes", "y", "on"}
|
| 8 |
FALSE_VALUES = {"0", "false", "f", "no", "n", "off"}
|
|
@@ -27,6 +30,19 @@ def langsmith_tracing_enabled() -> bool:
|
|
| 27 |
return bool(os.getenv("LANGSMITH_API_KEY"))
|
| 28 |
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
def configure_langsmith_environment() -> None:
|
| 31 |
"""Apply app defaults for LangSmith without requiring code changes in deploys."""
|
| 32 |
if os.getenv("LANGSMITH_API_KEY") and os.getenv("LANGSMITH_TRACING") is None:
|
|
|
|
| 3 |
import os
|
| 4 |
|
| 5 |
DEFAULT_LANGSMITH_PROJECT = "ai-tutor-app"
|
| 6 |
+
DEFAULT_LOCAL_DEPLOYMENT_ENVIRONMENT = "local"
|
| 7 |
+
DEFAULT_HF_DEPLOYMENT_ENVIRONMENT = "huggingface-space"
|
| 8 |
+
LOCAL_DEPLOYMENT_HOST = "local"
|
| 9 |
|
| 10 |
TRUE_VALUES = {"1", "true", "t", "yes", "y", "on"}
|
| 11 |
FALSE_VALUES = {"0", "false", "f", "no", "n", "off"}
|
|
|
|
| 30 |
return bool(os.getenv("LANGSMITH_API_KEY"))
|
| 31 |
|
| 32 |
|
| 33 |
+
def langsmith_deployment_identity() -> tuple[str, str]:
|
| 34 |
+
"""Return stable environment and host labels for LangSmith traces."""
|
| 35 |
+
space_host = os.getenv("SPACE_HOST", "").strip()
|
| 36 |
+
environment = os.getenv("AI_TUTOR_DEPLOYMENT_ENV", "").strip()
|
| 37 |
+
if not environment:
|
| 38 |
+
environment = (
|
| 39 |
+
DEFAULT_HF_DEPLOYMENT_ENVIRONMENT
|
| 40 |
+
if space_host
|
| 41 |
+
else DEFAULT_LOCAL_DEPLOYMENT_ENVIRONMENT
|
| 42 |
+
)
|
| 43 |
+
return environment, space_host or LOCAL_DEPLOYMENT_HOST
|
| 44 |
+
|
| 45 |
+
|
| 46 |
def configure_langsmith_environment() -> None:
|
| 47 |
"""Apply app defaults for LangSmith without requiring code changes in deploys."""
|
| 48 |
if os.getenv("LANGSMITH_API_KEY") and os.getenv("LANGSMITH_TRACING") is None:
|
app/chat_service.py
CHANGED
|
@@ -40,6 +40,7 @@ from langgraph.graph.message import REMOVE_ALL_MESSAGES
|
|
| 40 |
from langgraph.store.memory import InMemoryStore
|
| 41 |
from langgraph.types import Command
|
| 42 |
|
|
|
|
| 43 |
from .chat_types import ChatEvent, ChatRequest, ChatTurn, SourceMatch
|
| 44 |
from .deepseek_chat import TutorChatDeepSeek
|
| 45 |
from .memory_presets import (
|
|
@@ -2651,6 +2652,7 @@ def agent_run_config(
|
|
| 2651 |
memory_preset: str = "",
|
| 2652 |
) -> dict[str, Any]:
|
| 2653 |
provider, actual_model = model_provider_and_name(request.model_name)
|
|
|
|
| 2654 |
tools = effective_tool_names(
|
| 2655 |
request.model_name,
|
| 2656 |
request.enabled_tools,
|
|
@@ -2672,10 +2674,14 @@ def agent_run_config(
|
|
| 2672 |
f"provider:{provider}",
|
| 2673 |
f"model:{actual_model}",
|
| 2674 |
f"memory:{preset}",
|
|
|
|
|
|
|
| 2675 |
*(f"tool:{tool_name}" for tool_name in tools),
|
| 2676 |
],
|
| 2677 |
"metadata": {
|
| 2678 |
"app": "ai-tutor-app",
|
|
|
|
|
|
|
| 2679 |
"thread_id": active_thread_id,
|
| 2680 |
"conversation_id": active_thread_id,
|
| 2681 |
"message_id": message_id,
|
|
|
|
| 40 |
from langgraph.store.memory import InMemoryStore
|
| 41 |
from langgraph.types import Command
|
| 42 |
|
| 43 |
+
from .agent_tracing import langsmith_deployment_identity
|
| 44 |
from .chat_types import ChatEvent, ChatRequest, ChatTurn, SourceMatch
|
| 45 |
from .deepseek_chat import TutorChatDeepSeek
|
| 46 |
from .memory_presets import (
|
|
|
|
| 2652 |
memory_preset: str = "",
|
| 2653 |
) -> dict[str, Any]:
|
| 2654 |
provider, actual_model = model_provider_and_name(request.model_name)
|
| 2655 |
+
deployment_environment, deployment_host = langsmith_deployment_identity()
|
| 2656 |
tools = effective_tool_names(
|
| 2657 |
request.model_name,
|
| 2658 |
request.enabled_tools,
|
|
|
|
| 2674 |
f"provider:{provider}",
|
| 2675 |
f"model:{actual_model}",
|
| 2676 |
f"memory:{preset}",
|
| 2677 |
+
f"environment:{deployment_environment}",
|
| 2678 |
+
f"deployment:{deployment_host}",
|
| 2679 |
*(f"tool:{tool_name}" for tool_name in tools),
|
| 2680 |
],
|
| 2681 |
"metadata": {
|
| 2682 |
"app": "ai-tutor-app",
|
| 2683 |
+
"environment": deployment_environment,
|
| 2684 |
+
"deployment_host": deployment_host,
|
| 2685 |
"thread_id": active_thread_id,
|
| 2686 |
"conversation_id": active_thread_id,
|
| 2687 |
"message_id": message_id,
|
tests/test_agent_tracing.py
CHANGED
|
@@ -7,6 +7,7 @@ from unittest.mock import patch
|
|
| 7 |
from app.agent_tracing import (
|
| 8 |
DEFAULT_LANGSMITH_PROJECT,
|
| 9 |
configure_langsmith_environment,
|
|
|
|
| 10 |
langsmith_tracing_enabled,
|
| 11 |
parse_env_bool,
|
| 12 |
)
|
|
@@ -40,6 +41,38 @@ class AgentTracingTestCase(unittest.TestCase):
|
|
| 40 |
self.assertFalse(langsmith_tracing_enabled())
|
| 41 |
self.assertNotIn("LANGSMITH_PROJECT", os.environ)
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
def test_project_is_preserved(self) -> None:
|
| 44 |
with patch.dict(
|
| 45 |
os.environ,
|
|
|
|
| 7 |
from app.agent_tracing import (
|
| 8 |
DEFAULT_LANGSMITH_PROJECT,
|
| 9 |
configure_langsmith_environment,
|
| 10 |
+
langsmith_deployment_identity,
|
| 11 |
langsmith_tracing_enabled,
|
| 12 |
parse_env_bool,
|
| 13 |
)
|
|
|
|
| 41 |
self.assertFalse(langsmith_tracing_enabled())
|
| 42 |
self.assertNotIn("LANGSMITH_PROJECT", os.environ)
|
| 43 |
|
| 44 |
+
def test_deployment_identity_defaults_to_local(self) -> None:
|
| 45 |
+
with patch.dict(os.environ, {}, clear=True):
|
| 46 |
+
self.assertEqual(langsmith_deployment_identity(), ("local", "local"))
|
| 47 |
+
|
| 48 |
+
def test_deployment_identity_detects_hugging_face_space(self) -> None:
|
| 49 |
+
with patch.dict(
|
| 50 |
+
os.environ,
|
| 51 |
+
{"SPACE_HOST": "towardsai-tutors-ai-tutor-chatbot.hf.space"},
|
| 52 |
+
clear=True,
|
| 53 |
+
):
|
| 54 |
+
self.assertEqual(
|
| 55 |
+
langsmith_deployment_identity(),
|
| 56 |
+
(
|
| 57 |
+
"huggingface-space",
|
| 58 |
+
"towardsai-tutors-ai-tutor-chatbot.hf.space",
|
| 59 |
+
),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
def test_explicit_deployment_environment_wins(self) -> None:
|
| 63 |
+
with patch.dict(
|
| 64 |
+
os.environ,
|
| 65 |
+
{
|
| 66 |
+
"AI_TUTOR_DEPLOYMENT_ENV": "hf-prod",
|
| 67 |
+
"SPACE_HOST": "towardsai-tutors-ai-tutor-chatbot.hf.space",
|
| 68 |
+
},
|
| 69 |
+
clear=True,
|
| 70 |
+
):
|
| 71 |
+
self.assertEqual(
|
| 72 |
+
langsmith_deployment_identity(),
|
| 73 |
+
("hf-prod", "towardsai-tutors-ai-tutor-chatbot.hf.space"),
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
def test_project_is_preserved(self) -> None:
|
| 77 |
with patch.dict(
|
| 78 |
os.environ,
|
tests/test_chat_service.py
CHANGED
|
@@ -413,17 +413,34 @@ class ChatServiceTestCase(unittest.TestCase):
|
|
| 413 |
enabled_tools=("web_search",),
|
| 414 |
)
|
| 415 |
|
| 416 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
|
| 418 |
self.assertEqual(config["configurable"], {"thread_id": "thread_123"})
|
| 419 |
self.assertEqual(config["run_name"], "ai-tutor-agent-turn")
|
| 420 |
self.assertIn("provider:google-genai", config["tags"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
self.assertIn("tool:retrieve_tutor_context", config["tags"])
|
| 422 |
self.assertIn("tool:run_kb_command", config["tags"])
|
| 423 |
self.assertIn("tool:google_search", config["tags"])
|
| 424 |
self.assertEqual(config["metadata"]["thread_id"], "thread_123")
|
| 425 |
self.assertEqual(config["metadata"]["conversation_id"], "thread_123")
|
| 426 |
self.assertEqual(config["metadata"]["message_id"], "message_456")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
self.assertEqual(
|
| 428 |
config["metadata"]["available_tools"],
|
| 429 |
[
|
|
|
|
| 413 |
enabled_tools=("web_search",),
|
| 414 |
)
|
| 415 |
|
| 416 |
+
with patch.dict(
|
| 417 |
+
os.environ,
|
| 418 |
+
{
|
| 419 |
+
"AI_TUTOR_DEPLOYMENT_ENV": "hf-prod",
|
| 420 |
+
"SPACE_HOST": "towardsai-tutors-ai-tutor-chatbot.hf.space",
|
| 421 |
+
},
|
| 422 |
+
):
|
| 423 |
+
config = agent_run_config(request, "thread_123", "message_456")
|
| 424 |
|
| 425 |
self.assertEqual(config["configurable"], {"thread_id": "thread_123"})
|
| 426 |
self.assertEqual(config["run_name"], "ai-tutor-agent-turn")
|
| 427 |
self.assertIn("provider:google-genai", config["tags"])
|
| 428 |
+
self.assertIn("environment:hf-prod", config["tags"])
|
| 429 |
+
self.assertIn(
|
| 430 |
+
"deployment:towardsai-tutors-ai-tutor-chatbot.hf.space",
|
| 431 |
+
config["tags"],
|
| 432 |
+
)
|
| 433 |
self.assertIn("tool:retrieve_tutor_context", config["tags"])
|
| 434 |
self.assertIn("tool:run_kb_command", config["tags"])
|
| 435 |
self.assertIn("tool:google_search", config["tags"])
|
| 436 |
self.assertEqual(config["metadata"]["thread_id"], "thread_123")
|
| 437 |
self.assertEqual(config["metadata"]["conversation_id"], "thread_123")
|
| 438 |
self.assertEqual(config["metadata"]["message_id"], "message_456")
|
| 439 |
+
self.assertEqual(config["metadata"]["environment"], "hf-prod")
|
| 440 |
+
self.assertEqual(
|
| 441 |
+
config["metadata"]["deployment_host"],
|
| 442 |
+
"towardsai-tutors-ai-tutor-chatbot.hf.space",
|
| 443 |
+
)
|
| 444 |
self.assertEqual(
|
| 445 |
config["metadata"]["available_tools"],
|
| 446 |
[
|