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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ AZURE_API_VERSION=0000-00-00-preview
LLM_MODEL_NAME=fake-llm-model
LLM_TEMPERATURE=0.7

##### LANGSMITH TRACING ####
LANGSMITH_TRACING_ENABLED=False
LANGSMITH_PROJECT=fake_project
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=FAKE_LANGSMITH_API_KEY

##### POSTGRES #########
# PG_HOST=fake-prod-postgres.example.com
PG_HOST=fake-dev-postgres.example.com
Expand Down
17 changes: 12 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ langchain-mistralai = "^1.1.2"
langchain-azure-ai = "^1.2.3"
langgraph = "^1.1.10"
mistralai = "^2.4.3"
langsmith = "^0.9.3"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
Expand Down
36 changes: 36 additions & 0 deletions src/app/api/api_v1/endpoints/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@
}


def _build_agent_trace_context(
*,
endpoint: str,
session_id: UUID | None,
thread_id: UUID,
body: models.AgentContext,
) -> models.TraceContext:
env = settings.ENV
return {
"endpoint": endpoint,
"feature": "chat_agent",
"environment": env,
"session_id": str(session_id) if session_id else None,
"thread_id": str(thread_id),
"query_length": len(body.query or ""),
"sdg_filter": body.sdg_filter,
"corpora": list(body.corpora) if body.corpora else None,
}

Comment thread
jmsevin marked this conversation as resolved.

def get_params(body: models.Context) -> models.ContextOut:
body.sources = body.sources[:7]

Expand Down Expand Up @@ -400,6 +420,12 @@ async def agent_stream_response(
try:
session_id = extract_session_cookie(request)
thread_id = _resolve_thread_id(body.thread_id)
trace_context = _build_agent_trace_context(
endpoint="/api/v1/qna/chat/agent_stream",
session_id=session_id,
thread_id=thread_id,
body=body,
)

if body.query is None:
raise EmptyQueryError()
Expand All @@ -415,6 +441,7 @@ async def agent_stream_response(
data_collection=data_collection,
session_id=session_id,
thread_id=thread_id,
trace_context=trace_context,
),
media_type="text/event-stream",
headers=SSE_HEADERS,
Expand Down Expand Up @@ -457,6 +484,13 @@ async def agent_response(
logger.info("No thread_id provided. Generating new thread_id.")
thread_id = uuid.uuid4()

trace_context = _build_agent_trace_context(
endpoint="/api/v1/qna/chat/agent",
session_id=session_id,
thread_id=thread_id,
body=body,
)

if body.query is None:
raise EmptyQueryError()

Expand All @@ -480,13 +514,15 @@ async def agent_response(
sdg_filter=body.sdg_filter,
sp=sp,
background_tasks=background_tasks,
trace_context=trace_context,
)
else:
res = await chatfactory.agent_message(
query=body.query,
corpora=body.corpora,
sdg_filter=body.sdg_filter,
sp=sp,
trace_context=trace_context,
)

all_docs = []
Expand Down
4 changes: 4 additions & 0 deletions src/app/api/api_v1/endpoints/chat_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ async def _stream_agent_with_memory(
sp: SearchService,
background_tasks: BackgroundTasks,
thread_id: UUID,
trace_context: models.TraceContext | None = None,
) -> AsyncGenerator[dict[str, Any], None]:
async with await psycopg.AsyncConnection[DictRow].connect(
db_uri,
Expand All @@ -103,6 +104,7 @@ async def _stream_agent_with_memory(
sp=sp,
background_tasks=background_tasks,
streamed_ans=True,
trace_context=trace_context,
)

async for chunk in stream:
Expand Down Expand Up @@ -153,6 +155,7 @@ async def _stream_agent_response(
data_collection: Any,
session_id: UUID | None,
thread_id: UUID,
trace_context: models.TraceContext | None = None,
) -> AsyncGenerator[str, None]:
final_content = ""
docs = None
Expand All @@ -166,6 +169,7 @@ async def _stream_agent_response(
sp=sp,
background_tasks=background_tasks,
thread_id=thread_id,
trace_context=trace_context,
)

async for chunk in stream:
Expand Down
7 changes: 7 additions & 0 deletions src/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ def get_api_version(self) -> dict:
LLM_TEMPERATURE: float
ENV: str

# LANGSMITH / LANGCHAIN TRACING
# TBD: migrate Optional/Union annotations to PEP 604 shorthand (X | Y, X | None) in a dedicated refactor.
LANGSMITH_TRACING_ENABLED: bool = False
LANGSMITH_PROJECT: Optional[str] = None
LANGSMITH_ENDPOINT: Optional[str] = None
LANGSMITH_API_KEY: Optional[str] = None

Comment thread
jmsevin marked this conversation as resolved.
# PG
PG_USER: Optional[str] = None
PG_PASSWORD: Optional[str] = None
Expand Down
45 changes: 45 additions & 0 deletions src/app/core/langsmith.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os

from src.app.core.config import Settings
from src.app.utils.logger import logger as utils_logger

logger = utils_logger(__name__)


def configure_langsmith_tracing(settings: Settings) -> None:
"""Configure LangSmith tracing for LangChain-based model calls."""

is_enabled = settings.LANGSMITH_TRACING_ENABLED

os.environ["LANGSMITH_TRACING"] = "true" if is_enabled else "false"

if not is_enabled:
logger.info("langsmith_tracing_enabled=false")
return

if settings.LANGSMITH_PROJECT:
os.environ["LANGCHAIN_PROJECT"] = settings.LANGSMITH_PROJECT
else:
logger.warning(
"langsmith_tracing_enabled=true but LANGSMITH_PROJECT is missing"
)

if settings.LANGSMITH_ENDPOINT:
os.environ["LANGCHAIN_ENDPOINT"] = settings.LANGSMITH_ENDPOINT
else:
logger.warning(
"langsmith_tracing_enabled=true but LANGSMITH_ENDPOINT is missing"
)

if settings.LANGSMITH_API_KEY:
os.environ["LANGSMITH_API_KEY"] = settings.LANGSMITH_API_KEY
else:
logger.warning(
"langsmith_tracing_enabled=true but LANGSMITH_API_KEY is missing"
)

logger.info(
"langsmith_tracing_enabled=true project=%s endpoint=%s",
settings.LANGSMITH_PROJECT,
settings.LANGSMITH_ENDPOINT,
)
2 changes: 2 additions & 0 deletions src/app/core/lifespan.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from fastapi import FastAPI
from qdrant_client import AsyncQdrantClient

from src.app.core.langsmith import configure_langsmith_tracing
from src.app.shared.infra.llm_proxy import LLMProxy
from src.app.shared.utils.dependencies import get_settings
from src.app.tutor.service.tutor import init_chat_model
Expand All @@ -13,6 +14,7 @@
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings()
configure_langsmith_tracing(settings)
await init_chat_model(settings)
app.state.qdrant = AsyncQdrantClient(
url=settings.QDRANT_HOST,
Expand Down
11 changes: 11 additions & 0 deletions src/app/models/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ class AgentContext(SDGFilter):
corpora: tuple[str, ...] | None = None


class TraceContext(TypedDict):
endpoint: str
feature: str
environment: str
session_id: str | None
thread_id: str
query_length: int
sdg_filter: list[int] | None
corpora: list[str] | None


class AgentResponse(BaseModel):
content: str | None = None
status: str | None = None
Expand Down
Loading
Loading