mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 20:53:15 +00:00
114 lines
3.7 KiB
Python
114 lines
3.7 KiB
Python
|
|
"""Server-side, read-only LangSmith tools.
|
||
|
|
|
||
|
|
Credentials live in team settings (encrypted at rest). The tools run in the
|
||
|
|
LangGraph server process and call the LangSmith API directly — the sandbox never
|
||
|
|
holds a LangSmith key. The surface is intentionally read-only: fetch a single
|
||
|
|
run/trace and list recent runs in a project.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import asyncio
|
||
|
|
import logging
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from langchain_core.tools import BaseTool, StructuredTool
|
||
|
|
|
||
|
|
from ..dashboard.team_credentials import LangSmithCredentials, get_langsmith_credentials
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
_MAX_LIST_RUNS = 50
|
||
|
|
|
||
|
|
|
||
|
|
def _client(creds: LangSmithCredentials):
|
||
|
|
from langsmith import Client
|
||
|
|
|
||
|
|
return Client(api_key=creds.api_key, api_url=creds.endpoint)
|
||
|
|
|
||
|
|
|
||
|
|
def _serialize_run(run: Any) -> dict[str, Any]:
|
||
|
|
def _get(name: str) -> Any:
|
||
|
|
value = getattr(run, name, None)
|
||
|
|
return str(value) if value is not None else None
|
||
|
|
|
||
|
|
return {
|
||
|
|
"id": _get("id"),
|
||
|
|
"name": getattr(run, "name", None),
|
||
|
|
"run_type": getattr(run, "run_type", None),
|
||
|
|
"status": getattr(run, "status", None),
|
||
|
|
"error": getattr(run, "error", None),
|
||
|
|
"start_time": _get("start_time"),
|
||
|
|
"end_time": _get("end_time"),
|
||
|
|
"trace_id": _get("trace_id"),
|
||
|
|
"inputs": getattr(run, "inputs", None),
|
||
|
|
"outputs": getattr(run, "outputs", None),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _make_tools(creds: LangSmithCredentials) -> list[BaseTool]:
|
||
|
|
async def langsmith_get_trace(run_id: str, load_child_runs: bool = False) -> dict[str, Any]:
|
||
|
|
"""Fetch a single LangSmith run (trace) by its run ID.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
run_id: The LangSmith run UUID.
|
||
|
|
load_child_runs: Include nested child runs when True.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dictionary with the run details, or an error message.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
run = await asyncio.to_thread(
|
||
|
|
_client(creds).read_run, run_id, load_child_runs=load_child_runs
|
||
|
|
)
|
||
|
|
except Exception as e: # noqa: BLE001
|
||
|
|
logger.warning("langsmith_get_trace failed", exc_info=True)
|
||
|
|
return {"success": False, "error": f"{type(e).__name__}: {e}"}
|
||
|
|
return {"success": True, "run": _serialize_run(run)}
|
||
|
|
|
||
|
|
async def langsmith_list_runs(
|
||
|
|
project_name: str,
|
||
|
|
limit: int = 20,
|
||
|
|
filter: str | None = None,
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""List recent LangSmith runs in a project.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
project_name: The LangSmith project (tracing project) name.
|
||
|
|
limit: Maximum runs to return (capped at 50).
|
||
|
|
filter: Optional LangSmith filter string (e.g. "eq(status, 'error')").
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dictionary with a list of runs, or an error message.
|
||
|
|
"""
|
||
|
|
capped = max(1, min(limit, _MAX_LIST_RUNS))
|
||
|
|
|
||
|
|
def _list() -> list[Any]:
|
||
|
|
return list(
|
||
|
|
_client(creds).list_runs(
|
||
|
|
project_name=project_name,
|
||
|
|
filter=filter,
|
||
|
|
limit=capped,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
try:
|
||
|
|
runs = await asyncio.to_thread(_list)
|
||
|
|
except Exception as e: # noqa: BLE001
|
||
|
|
logger.warning("langsmith_list_runs failed", exc_info=True)
|
||
|
|
return {"success": False, "error": f"{type(e).__name__}: {e}"}
|
||
|
|
return {"success": True, "runs": [_serialize_run(r) for r in runs]}
|
||
|
|
|
||
|
|
return [
|
||
|
|
StructuredTool.from_function(coroutine=langsmith_get_trace),
|
||
|
|
StructuredTool.from_function(coroutine=langsmith_list_runs),
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
async def load_langsmith_tools() -> list[BaseTool]:
|
||
|
|
"""Return read-only LangSmith tools when the team has connected LangSmith."""
|
||
|
|
creds = await get_langsmith_credentials()
|
||
|
|
if creds is None:
|
||
|
|
return []
|
||
|
|
return _make_tools(creds)
|