-
Notifications
You must be signed in to change notification settings - Fork 15.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
py tracer fixes #5377
py tracer fixes #5377
Changes from all commits
7bf07c1
b9a20dd
e884da2
ba743ef
2a29716
33dfb4d
93ba1bb
e087809
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,24 +3,35 @@ | |
|
||
import logging | ||
import os | ||
from concurrent.futures import ThreadPoolExecutor | ||
from datetime import datetime | ||
from typing import Any, Dict, List, Optional | ||
from uuid import UUID | ||
|
||
import requests | ||
from tenacity import retry, stop_after_attempt, wait_fixed | ||
from requests.exceptions import HTTPError | ||
from tenacity import ( | ||
before_sleep_log, | ||
retry, | ||
retry_if_exception_type, | ||
stop_after_attempt, | ||
wait_exponential, | ||
) | ||
|
||
from langchain.callbacks.tracers.base import BaseTracer | ||
from langchain.callbacks.tracers.schemas import ( | ||
Run, | ||
RunCreate, | ||
RunTypeEnum, | ||
RunUpdate, | ||
TracerSession, | ||
TracerSessionCreate, | ||
) | ||
from langchain.schema import BaseMessage, messages_to_dict | ||
from langchain.utils import raise_for_status_with_text | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def get_headers() -> Dict[str, Any]: | ||
"""Get the headers for the LangChain API.""" | ||
|
@@ -34,7 +45,27 @@ def get_endpoint() -> str: | |
return os.getenv("LANGCHAIN_ENDPOINT", "http://localhost:1984") | ||
|
||
|
||
@retry(stop=stop_after_attempt(3), wait=wait_fixed(0.5)) | ||
class LangChainTracerAPIError(Exception): | ||
"""An error occurred while communicating with the LangChain API.""" | ||
|
||
|
||
class LangChainTracerUserError(Exception): | ||
"""An error occurred while communicating with the LangChain API.""" | ||
|
||
|
||
class LangChainTracerError(Exception): | ||
"""An error occurred while communicating with the LangChain API.""" | ||
|
||
|
||
retry_decorator = retry( | ||
stop=stop_after_attempt(3), | ||
wait=wait_exponential(multiplier=1, min=4, max=10), | ||
retry=retry_if_exception_type(LangChainTracerAPIError), | ||
before_sleep=before_sleep_log(logger, logging.WARNING), | ||
) | ||
|
||
|
||
@retry_decorator | ||
def _get_tenant_id( | ||
tenant_id: Optional[str], endpoint: Optional[str], headers: Optional[dict] | ||
) -> str: | ||
|
@@ -44,8 +75,24 @@ def _get_tenant_id( | |
return tenant_id_ | ||
endpoint_ = endpoint or get_endpoint() | ||
headers_ = headers or get_headers() | ||
response = requests.get(endpoint_ + "/tenants", headers=headers_) | ||
raise_for_status_with_text(response) | ||
response = None | ||
try: | ||
response = requests.get(endpoint_ + "/tenants", headers=headers_) | ||
raise_for_status_with_text(response) | ||
except HTTPError as e: | ||
if response is not None and response.status_code == 500: | ||
raise LangChainTracerAPIError( | ||
f"Failed to get tenant ID from LangChain API. {e}" | ||
) | ||
else: | ||
raise LangChainTracerUserError( | ||
f"Failed to get tenant ID from LangChain API. {e}" | ||
) | ||
except Exception as e: | ||
raise LangChainTracerError( | ||
f"Failed to get tenant ID from LangChain API. {e}" | ||
) from e | ||
|
||
tenants: List[Dict[str, Any]] = response.json() | ||
if not tenants: | ||
raise ValueError(f"No tenants found for URL {endpoint_}") | ||
|
@@ -72,6 +119,8 @@ def __init__( | |
self.example_id = example_id | ||
self.session_name = session_name or os.getenv("LANGCHAIN_SESSION", "default") | ||
self.session_extra = session_extra | ||
# set max_workers to 1 to process tasks in order | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does order matter? Can we rely on every user of the api being as strict about order as we are being here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'll fail if a child run gets posted before a parent run at least, right? (parent_run_id not found) |
||
self.executor = ThreadPoolExecutor(max_workers=1) | ||
|
||
def on_chat_model_start( | ||
self, | ||
|
@@ -108,7 +157,7 @@ def ensure_tenant_id(self) -> str: | |
self.tenant_id = tenant_id | ||
return tenant_id | ||
|
||
@retry(stop=stop_after_attempt(3), wait=wait_fixed(0.5)) | ||
@retry_decorator | ||
def ensure_session(self) -> TracerSession: | ||
"""Upsert a session.""" | ||
if self.session is not None: | ||
|
@@ -118,37 +167,124 @@ def ensure_session(self) -> TracerSession: | |
session_create = TracerSessionCreate( | ||
name=self.session_name, extra=self.session_extra, tenant_id=tenant_id | ||
) | ||
r = requests.post( | ||
url, | ||
data=session_create.json(), | ||
headers=self._headers, | ||
) | ||
raise_for_status_with_text(r) | ||
self.session = TracerSession(**r.json()) | ||
response = None | ||
try: | ||
response = requests.post( | ||
url, | ||
data=session_create.json(), | ||
headers=self._headers, | ||
) | ||
response.raise_for_status() | ||
except HTTPError as e: | ||
if response is not None and response.status_code == 500: | ||
raise LangChainTracerAPIError( | ||
f"Failed to upsert session to LangChain API. {e}" | ||
) | ||
else: | ||
raise LangChainTracerUserError( | ||
f"Failed to upsert session to LangChain API. {e}" | ||
) | ||
except Exception as e: | ||
raise LangChainTracerError( | ||
f"Failed to upsert session to LangChain API. {e}" | ||
) from e | ||
self.session = TracerSession(**response.json()) | ||
return self.session | ||
|
||
def _persist_run_nested(self, run: Run) -> None: | ||
def _persist_run(self, run: Run) -> None: | ||
"""Persist a run.""" | ||
|
||
@retry_decorator | ||
def _persist_run_single(self, run: Run) -> None: | ||
"""Persist a run.""" | ||
session = self.ensure_session() | ||
child_runs = run.child_runs | ||
if run.parent_run_id is None: | ||
run.reference_example_id = self.example_id | ||
run_dict = run.dict() | ||
del run_dict["child_runs"] | ||
run_create = RunCreate(**run_dict, session_id=session.id) | ||
response = None | ||
try: | ||
response = requests.post( | ||
f"{self._endpoint}/runs", | ||
data=run_create.json(), | ||
headers=self._headers, | ||
) | ||
raise_for_status_with_text(response) | ||
response.raise_for_status() | ||
except HTTPError as e: | ||
if response is not None and response.status_code == 500: | ||
raise LangChainTracerAPIError( | ||
f"Failed to upsert persist run to LangChain API. {e}" | ||
) | ||
else: | ||
raise LangChainTracerUserError( | ||
f"Failed to persist run to LangChain API. {e}" | ||
) | ||
except Exception as e: | ||
logging.warning(f"Failed to persist run: {e}") | ||
for child_run in child_runs: | ||
child_run.parent_run_id = run.id | ||
self._persist_run_nested(child_run) | ||
raise LangChainTracerError( | ||
f"Failed to persist run to LangChain API. {e}" | ||
) from e | ||
|
||
def _persist_run(self, run: Run) -> None: | ||
"""Persist a run.""" | ||
run.reference_example_id = self.example_id | ||
# TODO: Post first then patch | ||
self._persist_run_nested(run) | ||
@retry_decorator | ||
def _update_run_single(self, run: Run) -> None: | ||
"""Update a run.""" | ||
run_update = RunUpdate(**run.dict()) | ||
response = None | ||
try: | ||
response = requests.patch( | ||
f"{self._endpoint}/runs/{run.id}", | ||
data=run_update.json(), | ||
headers=self._headers, | ||
) | ||
response.raise_for_status() | ||
except HTTPError as e: | ||
if response is not None and response.status_code == 500: | ||
raise LangChainTracerAPIError( | ||
f"Failed to update run to LangChain API. {e}" | ||
) | ||
else: | ||
raise LangChainTracerUserError(f"Failed to run to LangChain API. {e}") | ||
except Exception as e: | ||
raise LangChainTracerError( | ||
f"Failed to update run to LangChain API. {e}" | ||
) from e | ||
|
||
def _on_llm_start(self, run: Run) -> None: | ||
"""Persist an LLM run.""" | ||
self.executor.submit(self._persist_run_single, run.copy(deep=True)) | ||
|
||
def _on_chat_model_start(self, run: Run) -> None: | ||
"""Persist an LLM run.""" | ||
self.executor.submit(self._persist_run_single, run.copy(deep=True)) | ||
|
||
def _on_llm_end(self, run: Run) -> None: | ||
"""Process the LLM Run.""" | ||
self.executor.submit(self._update_run_single, run.copy(deep=True)) | ||
|
||
def _on_llm_error(self, run: Run) -> None: | ||
"""Process the LLM Run upon error.""" | ||
self.executor.submit(self._update_run_single, run.copy(deep=True)) | ||
|
||
def _on_chain_start(self, run: Run) -> None: | ||
"""Process the Chain Run upon start.""" | ||
self.executor.submit(self._persist_run_single, run.copy(deep=True)) | ||
|
||
def _on_chain_end(self, run: Run) -> None: | ||
"""Process the Chain Run.""" | ||
self.executor.submit(self._update_run_single, run.copy(deep=True)) | ||
|
||
def _on_chain_error(self, run: Run) -> None: | ||
"""Process the Chain Run upon error.""" | ||
self.executor.submit(self._update_run_single, run.copy(deep=True)) | ||
|
||
def _on_tool_start(self, run: Run) -> None: | ||
"""Process the Tool Run upon start.""" | ||
self.executor.submit(self._persist_run_single, run.copy(deep=True)) | ||
|
||
def _on_tool_end(self, run: Run) -> None: | ||
"""Process the Tool Run.""" | ||
self.executor.submit(self._update_run_single, run.copy(deep=True)) | ||
|
||
def _on_tool_error(self, run: Run) -> None: | ||
"""Process the Tool Run upon error.""" | ||
self.executor.submit(self._update_run_single, run.copy(deep=True)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be removed, or is that another PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#5198