From c4c79da0711ab095895e1c79959e78748ad51f1b Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sat, 19 Aug 2023 13:59:52 -0700 Subject: [PATCH 01/58] Updated usage of metadata so that both part and doc level metadata is returned properly as a single meta-data dict Updated tests --- .../langchain/vectorstores/vectara.py | 26 ++++--- .../vectorstores/test_vectara.py | 77 ++++++++++++------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/libs/langchain/langchain/vectorstores/vectara.py b/libs/langchain/langchain/vectorstores/vectara.py index f263ebeac84..f1c2bc6ca9e 100644 --- a/libs/langchain/langchain/vectorstores/vectara.py +++ b/libs/langchain/langchain/vectorstores/vectara.py @@ -202,12 +202,12 @@ class Vectara(VectorStore): doc_metadata: optional metadata for the document This function indexes all the input text strings in the Vectara corpus as a - single Vectara document, where each input text is considered a "part" and the - metadata are associated with each part. + single Vectara document, where each input text is considered a "section" and the + metadata are associated with each section. if 'doc_metadata' is provided, it is associated with the Vectara document. Returns: - List of ids from adding the texts into the vectorstore. + document ID of the document added """ doc_hash = md5() @@ -307,21 +307,27 @@ class Vectara(VectorStore): result = response.json() responses = result["responseSet"][0]["response"] - vectara_default_metadata = ["lang", "len", "offset"] + documents = result["responseSet"][0]["document"] + + metadatas = [] + for x in responses: + md = { m["name"]: m["value"] for m in x["metadata"] } + doc_num = x['documentIndex'] + doc_md = { m["name"]: m["value"] for m in documents[doc_num]['metadata'] } + md.update(doc_md) + metadatas.append(md) + docs = [ ( Document( page_content=x["text"], - metadata={ - m["name"]: m["value"] - for m in x["metadata"] - if m["name"] not in vectara_default_metadata - }, + metadata=md, ), x["score"], ) - for x in responses + for x,md in zip(responses,metadatas) ] + return docs def similarity_search( diff --git a/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py b/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py index 57338e7f994..3b2decfc2f9 100644 --- a/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py +++ b/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py @@ -5,12 +5,14 @@ from langchain.docstore.document import Document from langchain.vectorstores.vectara import Vectara from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings -# For this test to run properly, please setup as follows -# 1. Create a corpus in Vectara, with a filter attribute called "test_num". -# 2. Create an API_KEY for this corpus with permissions for query and indexing -# 3. Setup environment variables: +# +# For this test to run properly, please setup as follows: +# 1. Create a Vectara account: sign up at https://console.vectara.com/signup +# 2. Create a corpus in your Vectara account, with a filter attribute called "test_num". +# 3. Create an API_KEY for this corpus with permissions for query and indexing +# 4. Setup environment variables: # VECTARA_API_KEY, VECTARA_CORPUS_ID and VECTARA_CUSTOMER_ID - +# def get_abbr(s: str) -> str: words = s.split(" ") # Split the string into words @@ -21,38 +23,52 @@ def get_abbr(s: str) -> str: def test_vectara_add_documents() -> None: """Test end to end construction and search.""" - # start with some initial texts - texts = ["grounded generation", "retrieval augmented generation", "data privacy"] - docsearch: Vectara = Vectara.from_texts( - texts, - embedding=FakeEmbeddings(), - metadatas=[ - {"abbr": "gg", "test_num": "1"}, - {"abbr": "rag", "test_num": "1"}, - {"abbr": "dp", "test_num": "1"}, - ], + # create a new Vectara instance + docsearch: Vectara = Vectara() + + # start with some initial texts, added with add_texts + texts1 = ["grounded generation", "retrieval augmented generation", "data privacy"] + md = [{"abbr": get_abbr(t)} for t in texts1] + doc_id1 = docsearch.add_texts( + texts1, + metadatas=md, doc_metadata={"test_num": "1"}, ) - # then add some additional documents - new_texts = ["large language model", "information retrieval", "question answering"] - docsearch.add_documents( - [Document(page_content=t, metadata={"abbr": get_abbr(t)}) for t in new_texts], - doc_metadata={"test_num": "1"}, + # then add some additional documents, now with add_documents + texts2 = ["large language model", "information retrieval", "question answering"] + doc_id2 = docsearch.add_documents( + [Document(page_content=t, metadata={"abbr": get_abbr(t)}) for t in texts2], + doc_metadata={"test_num": "2"}, ) + doc_ids = doc_id1 + doc_id2 - # finally do a similarity search to see if all works okay - output = docsearch.similarity_search( + # test without filter + output1 = docsearch.similarity_search( "large language model", k=2, n_sentence_context=0, + ) + assert len(output1) == 2 + assert output1[0].page_content == "large language model" + assert output1[0].metadata['abbr'] == "llm" + assert output1[1].page_content == "information retrieval" + assert output1[1].metadata['abbr'] == "ir" + + # test with metadata filter (doc level) + # since the query does not match test_num=1 directly we get RAG as the matching result + output2 = docsearch.similarity_search( + "large language model", + k=1, + n_sentence_context=0, filter="doc.test_num = 1", ) - assert output[0].page_content == "large language model" - assert output[0].metadata == {"abbr": "llm"} - assert output[1].page_content == "information retrieval" - assert output[1].metadata == {"abbr": "ir"} + assert len(output2) == 1 + assert output2[0].page_content == "retrieval augmented generation" + assert output2[0].metadata['abbr'] == "rag" + for doc_id in doc_ids: + docsearch._delete_doc(doc_id) def test_vectara_from_files() -> None: """Test end to end construction and search.""" @@ -73,8 +89,9 @@ def test_vectara_from_files() -> None: urllib.request.urlretrieve(url, name) files_list.append(name) - docsearch: Vectara = Vectara.from_files( - files=files_list, + docsearch: Vectara = Vectara() + doc_ids = docsearch.add_files( + files_list=files_list, embedding=FakeEmbeddings(), metadatas=[{"url": url, "test_num": "2"} for url in urls], ) @@ -101,7 +118,6 @@ def test_vectara_from_files() -> None: n_sentence_context=1, filter="doc.test_num = 2", ) - print(output[0].page_content) assert output[0].page_content == ( """\ Note the use of “hybrid” in 3) above is different from that used sometimes in the literature, \ @@ -114,3 +130,6 @@ This classification scheme, however, misses a key insight gained in deep learnin models can greatly improve the training of DNNs and other deep discriminative models via better regularization.\ """ # noqa: E501 ) + + for doc_id in doc_ids: + docsearch._delete_doc(doc_id) From 90fd840fb17e6733aa240af51828c99695c5cd53 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sat, 19 Aug 2023 16:51:53 -0700 Subject: [PATCH 02/58] fixed formatting --- libs/langchain/langchain/vectorstores/vectara.py | 8 ++++---- .../tests/integration_tests/vectorstores/test_vectara.py | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/libs/langchain/langchain/vectorstores/vectara.py b/libs/langchain/langchain/vectorstores/vectara.py index f1c2bc6ca9e..cd8ee9c9fad 100644 --- a/libs/langchain/langchain/vectorstores/vectara.py +++ b/libs/langchain/langchain/vectorstores/vectara.py @@ -311,9 +311,9 @@ class Vectara(VectorStore): metadatas = [] for x in responses: - md = { m["name"]: m["value"] for m in x["metadata"] } - doc_num = x['documentIndex'] - doc_md = { m["name"]: m["value"] for m in documents[doc_num]['metadata'] } + md = {m["name"]: m["value"] for m in x["metadata"]} + doc_num = x["documentIndex"] + doc_md = {m["name"]: m["value"] for m in documents[doc_num]["metadata"]} md.update(doc_md) metadatas.append(md) @@ -325,7 +325,7 @@ class Vectara(VectorStore): ), x["score"], ) - for x,md in zip(responses,metadatas) + for x, md in zip(responses, metadatas) ] return docs diff --git a/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py b/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py index 3b2decfc2f9..5ed1d17343a 100644 --- a/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py +++ b/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py @@ -14,6 +14,7 @@ from tests.integration_tests.vectorstores.fake_embeddings import FakeEmbeddings # VECTARA_API_KEY, VECTARA_CORPUS_ID and VECTARA_CUSTOMER_ID # + def get_abbr(s: str) -> str: words = s.split(" ") # Split the string into words first_letters = [word[0] for word in words] # Extract the first letter of each word @@ -51,9 +52,9 @@ def test_vectara_add_documents() -> None: ) assert len(output1) == 2 assert output1[0].page_content == "large language model" - assert output1[0].metadata['abbr'] == "llm" + assert output1[0].metadata["abbr"] == "llm" assert output1[1].page_content == "information retrieval" - assert output1[1].metadata['abbr'] == "ir" + assert output1[1].metadata["abbr"] == "ir" # test with metadata filter (doc level) # since the query does not match test_num=1 directly we get RAG as the matching result @@ -65,11 +66,12 @@ def test_vectara_add_documents() -> None: ) assert len(output2) == 1 assert output2[0].page_content == "retrieval augmented generation" - assert output2[0].metadata['abbr'] == "rag" + assert output2[0].metadata["abbr"] == "rag" for doc_id in doc_ids: docsearch._delete_doc(doc_id) + def test_vectara_from_files() -> None: """Test end to end construction and search.""" From e92e199ec1e6a042a57fcbcea9ee022fc5432486 Mon Sep 17 00:00:00 2001 From: Ofer Mendelevitch Date: Sat, 19 Aug 2023 16:59:50 -0700 Subject: [PATCH 03/58] fixed lint issue --- .../tests/integration_tests/vectorstores/test_vectara.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py b/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py index 5ed1d17343a..8fa3cd7f40d 100644 --- a/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py +++ b/libs/langchain/tests/integration_tests/vectorstores/test_vectara.py @@ -57,7 +57,7 @@ def test_vectara_add_documents() -> None: assert output1[1].metadata["abbr"] == "ir" # test with metadata filter (doc level) - # since the query does not match test_num=1 directly we get RAG as the matching result + # since the query does not match test_num=1 directly we get "RAG" as the result output2 = docsearch.similarity_search( "large language model", k=1, From 00baddf34cb128f6d87b9bf5eb94e514ccbf832d Mon Sep 17 00:00:00 2001 From: Leonid Kuligin Date: Mon, 28 Aug 2023 15:38:56 +0200 Subject: [PATCH 04/58] fixed enterprise search returning an empty array --- .../retrievers/google_cloud_enterprise_search.py | 12 +++++++++--- .../test_google_cloud_enterprise_search.py | 3 +++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/libs/langchain/langchain/retrievers/google_cloud_enterprise_search.py b/libs/langchain/langchain/retrievers/google_cloud_enterprise_search.py index 3570047509f..4e9c478d2b6 100644 --- a/libs/langchain/langchain/retrievers/google_cloud_enterprise_search.py +++ b/libs/langchain/langchain/retrievers/google_cloud_enterprise_search.py @@ -114,7 +114,13 @@ class GoogleCloudEnterpriseSearchRetriever(BaseRetriever): def __init__(self, **data: Any) -> None: """Initializes private fields.""" - from google.cloud.discoveryengine_v1beta import SearchServiceClient + try: + from google.cloud.discoveryengine_v1beta import SearchServiceClient + except ImportError: + raise ImportError( + "google.cloud.discoveryengine is not installed." + "Please install it with pip install google-cloud-discoveryengine" + ) super().__init__(**data) self._client = SearchServiceClient(credentials=self.credentials) @@ -137,7 +143,7 @@ class GoogleCloudEnterpriseSearchRetriever(BaseRetriever): document_dict = MessageToDict( result.document._pb, preserving_proto_field_name=True ) - derived_struct_data = document_dict.get("derived_struct_data", None) + derived_struct_data = document_dict.get("derived_struct_data") if not derived_struct_data: continue @@ -150,7 +156,7 @@ class GoogleCloudEnterpriseSearchRetriever(BaseRetriever): else "extractive_segments" ) - for chunk in getattr(derived_struct_data, chunk_type, []): + for chunk in derived_struct_data.get(chunk_type, []): doc_metadata["source"] = derived_struct_data.get("link", "") if chunk_type == "extractive_answers": diff --git a/libs/langchain/tests/integration_tests/retrievers/test_google_cloud_enterprise_search.py b/libs/langchain/tests/integration_tests/retrievers/test_google_cloud_enterprise_search.py index 47f576ac296..86c80cfa278 100644 --- a/libs/langchain/tests/integration_tests/retrievers/test_google_cloud_enterprise_search.py +++ b/libs/langchain/tests/integration_tests/retrievers/test_google_cloud_enterprise_search.py @@ -24,6 +24,9 @@ def test_google_cloud_enterprise_search_get_relevant_documents() -> None: """Test the get_relevant_documents() method.""" retriever = GoogleCloudEnterpriseSearchRetriever() documents = retriever.get_relevant_documents("What are Alphabet's Other Bets?") + assert len(documents) > 0 for doc in documents: assert isinstance(doc, Document) assert doc.page_content + assert doc.metadata["id"] + assert doc.metadata["source"] From 9aaa0fdce084028632c305cadae6390e4e0d2ed6 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski Date: Mon, 28 Aug 2023 14:20:48 +0000 Subject: [PATCH 05/58] Use unified Python setup steps for release workflow. --- .github/workflows/_release.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 5ddf79ca7c2..1d7668978ba 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -31,13 +31,15 @@ jobs: working-directory: ${{ inputs.working-directory }} steps: - uses: actions/checkout@v3 - - name: Install poetry - run: pipx install "poetry==$POETRY_VERSION" - - name: Set up Python 3.10 - uses: actions/setup-python@v4 + + - name: Set up Python + Poetry ${{ env.POETRY_VERSION }} + uses: "./.github/actions/poetry_setup" with: python-version: "3.10" - cache: "poetry" + poetry-version: ${{ env.POETRY_VERSION }} + working-directory: ${{ inputs.working-directory }} + cache-key: release + - name: Build project for distribution run: poetry build - name: Check Version From 97741d41c5fc75a2bedb5706f94d89d87ea74e1e Mon Sep 17 00:00:00 2001 From: hughcrt Date: Mon, 28 Aug 2023 19:24:50 +0200 Subject: [PATCH 06/58] Add LLMonitorCallbackHandler --- .../integrations/callbacks/llmonitor.md | 63 ++++ .../langchain/langchain/callbacks/__init__.py | 2 + .../langchain/callbacks/llmonitor_callback.py | 319 ++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 docs/extras/integrations/callbacks/llmonitor.md create mode 100644 libs/langchain/langchain/callbacks/llmonitor_callback.py diff --git a/docs/extras/integrations/callbacks/llmonitor.md b/docs/extras/integrations/callbacks/llmonitor.md new file mode 100644 index 00000000000..57b1ec7c952 --- /dev/null +++ b/docs/extras/integrations/callbacks/llmonitor.md @@ -0,0 +1,63 @@ +# LLMonitor + +[LLMonitor](https://llmonitor.com) is an open-source observability platform that provides cost tracking, user tracking and powerful agent tracing. + + + +## Setup +Create an account on [llmonitor.com](https://llmonitor.com), create an `App`, and then copy the associated `tracking id`. +Once you have it, set it as an environment variable by running: +```bash +export LLMONITOR_APP_ID="..." +``` + +If you'd prefer not to set an environment variable, you can pass the key directly when initializing the callback handler: +```python +from langchain.callbacks import LLMonitorCallbackHandler + +handler = LLMonitorCallbackHandler(app_id="...") +``` + +## Usage with LLM/Chat models +```python +from langchain.llms import OpenAI +from langchain.chat_models import ChatOpenAI +from langchain.callbacks import LLMonitorCallbackHandler + +handler = LLMonitorCallbackHandler(app_id="...") + +llm = OpenAI( + callbacks=[handler], +) + +chat = ChatOpenAI( + callbacks=[handler], + metadata={"userId": "123"}, # you can assign user ids to models in the metadata +) +``` + + +## Usage with agents +```python +from langchain.agents import load_tools, initialize_agent, AgentType +from langchain.llms import OpenAI +from langchain.callbacks import LLMonitorCallbackHandler + +handler = LLMonitorCallbackHandler(app_id="...") + +llm = OpenAI(temperature=0) +tools = load_tools(["serpapi", "llm-math"], llm=llm) +agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION) +agent.run( + "Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?", + callbacks=[handler], + metadata={ + "agentName": "Leo DiCaprio's girlfriend", # you can assign a custom agent in the metadata + }, +) +``` + +## Support +For any question or issue with integration you can reach out to the LLMonitor team on [Discord](http://discord.com/invite/8PafSG58kK) or via [email](mailto:vince@llmonitor.com). diff --git a/libs/langchain/langchain/callbacks/__init__.py b/libs/langchain/langchain/callbacks/__init__.py index 8398741be34..12e18d52c7e 100644 --- a/libs/langchain/langchain/callbacks/__init__.py +++ b/libs/langchain/langchain/callbacks/__init__.py @@ -19,6 +19,7 @@ from langchain.callbacks.flyte_callback import FlyteCallbackHandler from langchain.callbacks.human import HumanApprovalCallbackHandler from langchain.callbacks.infino_callback import InfinoCallbackHandler from langchain.callbacks.labelstudio_callback import LabelStudioCallbackHandler +from langchain.callbacks.llmonitor_callback import LLMonitorCallbackHandler from langchain.callbacks.manager import ( get_openai_callback, tracing_enabled, @@ -53,6 +54,7 @@ __all__ = [ "HumanApprovalCallbackHandler", "InfinoCallbackHandler", "MlflowCallbackHandler", + "LLMonitorCallbackHandler", "OpenAICallbackHandler", "StdOutCallbackHandler", "AsyncIteratorCallbackHandler", diff --git a/libs/langchain/langchain/callbacks/llmonitor_callback.py b/libs/langchain/langchain/callbacks/llmonitor_callback.py new file mode 100644 index 00000000000..99ca1e92905 --- /dev/null +++ b/libs/langchain/langchain/callbacks/llmonitor_callback.py @@ -0,0 +1,319 @@ +import os +import traceback +from datetime import datetime +from typing import Any, Dict, List, Literal, Optional, Union +from uuid import UUID + +import requests + +from langchain.callbacks.base import BaseCallbackHandler +from langchain.schema.agent import AgentAction, AgentFinish +from langchain.schema.messages import BaseMessage +from langchain.schema.output import LLMResult + +DEFAULT_API_URL = "https://app.llmonitor.com" + + +def _parse_lc_role(role: str) -> Literal["user", "ai", "system", "function"] | None: + if role == "human": + return "user" + elif role == "ai": + return "ai" + elif role == "system": + return "system" + elif role == "function": + return "function" + else: + return None + + +def _serialize_lc_message(message: BaseMessage) -> Dict[str, Any]: + return {"text": message.content, "role": _parse_lc_role(message.type)} + + +class LLMonitorCallbackHandler(BaseCallbackHandler): + """Initializes the `LLMonitorCallbackHandler`. + #### Parameters: + - `app_id`: The app id of the app you want to report to. Defaults to `None`, which means that `LLMONITOR_APP_ID` will be used. + - `api_url`: The url of the LLMonitor API. Defaults to `None`, which means that either `LLMONITOR_API_URL` environment variable or `https://app.llmonitor.com` will be used. + + #### Raises: + - `ValueError`: if `app_id` is not provided either as an argument or as an environment variable. + - `ConnectionError`: if the connection to the API fails. + + + #### Example: + ```python + from langchain.llms import OpenAI + from langchain.callbacks import LLMonitorCallbackHandler + + llmonitor_callback = LLMonitorCallbackHandler() + llm = OpenAI(callbacks=[llmonitor_callback], metadata={"userId": "user-123"}) + llm.predict("Hello, how are you?") + ``` + """ + + __api_url: str + __app_id: str + + def __init__(self, app_id: str | None = None, api_url: str | None = None) -> None: + super().__init__() + + self.__api_url = api_url or os.getenv("LLMONITOR_API_URL") or DEFAULT_API_URL + + _app_id = app_id or os.getenv("LLMONITOR_APP_ID") + if _app_id is None: + raise ValueError( + "app_id must be provided either as an argument or as an environment variable" + ) + self.__app_id = _app_id + + try: + res = requests.get(f"{self.__api_url}/api/app/{self.__app_id}") + if not res.ok: + raise ConnectionError() + except Exception as e: + raise ConnectionError( + f"Could not connect to the LLMonitor API at {self.__api_url}" + ) from e + + def __send_event(self, event: Dict[str, Any]) -> None: + headers = {"Content-Type": "application/json"} + event = {**event, "app": self.__app_id, "timestamp": str(datetime.utcnow())} + data = {"events": event} + requests.post(headers=headers, url=f"{self.__api_url}/api/report", json=data) + + def on_llm_start( + self, + serialized: Dict[str, Any], + prompts: List[str], + *, + run_id: UUID, + parent_run_id: UUID | None = None, + tags: List[str] | None = None, + metadata: Dict[str, Any] | None = None, + **kwargs: Any, + ) -> None: + event = { + "event": "start", + "type": "llm", + "userId": (metadata or {}).get("userId"), + "runId": str(run_id), + "parentRunId": str(parent_run_id) if parent_run_id else None, + "input": prompts[0], + "name": kwargs.get("invocation_params", {}).get("model_name"), + "tags": tags, + "metadata": metadata, + } + self.__send_event(event) + + def on_chat_model_start( + self, + serialized: Dict[str, Any], + messages: List[List[BaseMessage]], + *, + run_id: UUID, + parent_run_id: UUID | None = None, + tags: List[str] | None = None, + metadata: Dict[str, Any] | None = None, + **kwargs: Any, + ) -> Any: + event = { + "event": "start", + "type": "llm", + "userId": (metadata or {}).get("userId"), + "runId": str(run_id), + "parentRunId": str(parent_run_id) if parent_run_id else None, + "input": [_serialize_lc_message(message[0]) for message in messages], + "name": kwargs.get("invocation_params", {}).get("model_name"), + "tags": tags, + "metadata": metadata, + } + self.__send_event(event) + + def on_llm_end( + self, + response: LLMResult, + *, + run_id: UUID, + parent_run_id: UUID | None = None, + **kwargs: Any, + ) -> None: + token_usage = (response.llm_output or {}).get("token_usage", {}) + + event = { + "event": "end", + "type": "llm", + "runId": str(run_id), + "parent_run_id": str(parent_run_id) if parent_run_id else None, + "output": {"text": response.generations[0][0].text, "role": "ai"}, + "tokensUsage": { + "prompt": token_usage.get("prompt_tokens", 0), + "completion": token_usage.get("completion_tokens", 0), + }, + } + self.__send_event(event) + + def on_llm_error( + self, + error: Exception | KeyboardInterrupt, + *, + run_id: UUID, + parent_run_id: UUID | None = None, + **kwargs: Any, + ) -> Any: + event = { + "event": "error", + "type": "llm", + "runId": str(run_id), + "parent_run_id": str(parent_run_id) if parent_run_id else None, + "error": {"message": str(error), "stack": traceback.format_exc()}, + } + self.__send_event(event) + + def on_tool_start( + self, + serialized: Dict[str, Any], + input_str: str, + *, + run_id: UUID, + parent_run_id: UUID | None = None, + tags: List[str] | None = None, + metadata: Dict[str, Any] | None = None, + **kwargs: Any, + ) -> None: + event = { + "event": "start", + "type": "tool", + "userId": (metadata or {}).get("userId"), + "runId": str(run_id), + "parentRunId": str(parent_run_id) if parent_run_id else None, + "name": serialized.get("name"), + "input": input_str, + "tags": tags, + "metadata": metadata, + } + self.__send_event(event) + + def on_tool_end( + self, + output: str, + *, + run_id: UUID, + parent_run_id: UUID | None = None, + tags: List[str] | None = None, + **kwargs: Any, + ) -> None: + event = { + "event": "end", + "type": "tool", + "runId": str(run_id), + "parent_run_id": str(parent_run_id) if parent_run_id else None, + "output": output, + } + self.__send_event(event) + + def on_chain_start( + self, + serialized: Dict[str, Any], + inputs: Dict[str, Any], + *, + run_id: UUID, + parent_run_id: UUID | None = None, + tags: List[str] | None = None, + metadata: Dict[str, Any] | None = None, + **kwargs: Any, + ) -> Any: + name = serialized.get("id", [None, None, None, None])[3] + type = "chain" + + agentName = (metadata or {}).get("agentName") + if agentName is not None: + type = "agent" + name = agentName + if name == "AgentExecutor" or name == "PlanAndExecute": + type = "agent" + event = { + "event": "start", + "type": type, + "userId": (metadata or {}).get("userId"), + "runId": str(run_id), + "parentRunId": str(parent_run_id) if parent_run_id else None, + "input": inputs.get("input", inputs), + "tags": tags, + "metadata": metadata, + "name": serialized.get("id", [None, None, None, None])[3], + } + + self.__send_event(event) + + def on_chain_end( + self, + outputs: Dict[str, Any], + *, + run_id: UUID, + parent_run_id: UUID | None = None, + **kwargs: Any, + ) -> Any: + event = { + "event": "end", + "type": "chain", + "runId": str(run_id), + "output": outputs.get("output", outputs), + } + self.__send_event(event) + + def on_chain_error( + self, + error: Exception | KeyboardInterrupt, + *, + run_id: UUID, + parent_run_id: UUID | None = None, + **kwargs: Any, + ) -> Any: + event = { + "event": "error", + "type": "chain", + "runId": str(run_id), + "parent_run_id": str(parent_run_id) if parent_run_id else None, + "error": {"message": str(error), "stack": traceback.format_exc()}, + } + self.__send_event(event) + + def on_agent_action( + self, + action: AgentAction, + *, + run_id: UUID, + parent_run_id: UUID | None = None, + **kwargs: Any, + ) -> Any: + event = { + "event": "start", + "type": "tool", + "runId": str(run_id), + "parentRunId": str(parent_run_id) if parent_run_id else None, + "name": action.tool, + "input": action.tool_input, + } + self.__send_event(event) + + def on_agent_finish( + self, + finish: AgentFinish, + *, + run_id: UUID, + parent_run_id: UUID | None = None, + **kwargs: Any, + ) -> Any: + event = { + "event": "end", + "type": "agent", + "runId": str(run_id), + "parentRunId": str(parent_run_id) if parent_run_id else None, + "output": finish.return_values, + } + self.__send_event(event) + + +__all__ = ["LLMonitorCallbackHandler"] From 3a4d4c940c71d67affecfd21503943648f75175f Mon Sep 17 00:00:00 2001 From: hughcrt Date: Mon, 28 Aug 2023 19:26:33 +0200 Subject: [PATCH 07/58] Change video width --- docs/extras/integrations/callbacks/llmonitor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extras/integrations/callbacks/llmonitor.md b/docs/extras/integrations/callbacks/llmonitor.md index 57b1ec7c952..daec3dad816 100644 --- a/docs/extras/integrations/callbacks/llmonitor.md +++ b/docs/extras/integrations/callbacks/llmonitor.md @@ -2,7 +2,7 @@ [LLMonitor](https://llmonitor.com) is an open-source observability platform that provides cost tracking, user tracking and powerful agent tracing. -