From db4bc9e2706ca2784be6375bb633e832427cf92a Mon Sep 17 00:00:00 2001 From: darshit-s3 <119623510+darshit-s3@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:42:15 +0530 Subject: [PATCH 1/4] Release: handling for openai NOT_GIVEN default arg value (#351) * add sentry integration * update readme * hotfix for checking package installed * Support Autogen (#242) * autogen kickstart * enhance autogen * run autogen * finish autogen * add readme * disable cache * Support genai and also add token reporting and other data points (#345) * support genai and also add token reporting * rename example file * fix --------- Co-authored-by: Karthik Kalyanaraman * fix: weaviate datetime handling for request and response (#346) * fix: weaviate datetime handling for request and response * bump version * bump version --------- Co-authored-by: Karthik Kalyanaraman * Minor bugfix to langchain instrumentation (#348) * Bugfix * bump version * fix: handling for openai NOT_GIVEN default arg value (#350) * fix: handling for openai NOT_GIVEN default arg value * style: fix formating --------- Co-authored-by: Rohit Kadhe Co-authored-by: Rohit Kadhe <113367036+rohit-kadhe@users.noreply.github.com> Co-authored-by: Ali Waleed Co-authored-by: Karthik Kalyanaraman Co-authored-by: Karthik Kalyanaraman <105607645+karthikscale3@users.noreply.github.com> --- .../instrumentation/openai/patch.py | 24 +++++++++++++------ src/langtrace_python_sdk/version.py | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/langtrace_python_sdk/instrumentation/openai/patch.py b/src/langtrace_python_sdk/instrumentation/openai/patch.py index b56418fc..bd4f6102 100644 --- a/src/langtrace_python_sdk/instrumentation/openai/patch.py +++ b/src/langtrace_python_sdk/instrumentation/openai/patch.py @@ -1,4 +1,5 @@ import json +import openai from typing import Any, Dict, List, Optional, Callable, Awaitable, Union from langtrace.trace_attributes import ( LLMSpanAttributes, @@ -40,6 +41,15 @@ ) +def filter_valid_attributes(attributes): + """Filter attributes where value is not None, not an empty string, and not openai.NOT_GIVEN.""" + return { + key: value + for key, value in attributes.items() + if value is not None and value != openai.NOT_GIVEN and value != "" + } + + def images_generate(version: str, tracer: Tracer) -> Callable: """ Wrap the `generate` method of the `Images` class to trace it. @@ -57,7 +67,7 @@ def traced_method( **get_extra_attributes(), # type: ignore } - attributes = LLMSpanAttributes(**span_attributes) + attributes = LLMSpanAttributes(**filter_valid_attributes(span_attributes)) with tracer.start_as_current_span( name=get_span_name(APIS["IMAGES_GENERATION"]["METHOD"]), @@ -118,7 +128,7 @@ async def traced_method( **get_extra_attributes(), # type: ignore } - attributes = LLMSpanAttributes(**span_attributes) + attributes = LLMSpanAttributes(**filter_valid_attributes(span_attributes)) with tracer.start_as_current_span( name=get_span_name(APIS["IMAGES_GENERATION"]["METHOD"]), @@ -181,7 +191,7 @@ def traced_method( **get_extra_attributes(), # type: ignore } - attributes = LLMSpanAttributes(**span_attributes) + attributes = LLMSpanAttributes(**filter_valid_attributes(span_attributes)) with tracer.start_as_current_span( name=APIS["IMAGES_EDIT"]["METHOD"], @@ -268,7 +278,7 @@ def traced_method( **get_extra_attributes(), # type: ignore } - attributes = LLMSpanAttributes(**span_attributes) + attributes = LLMSpanAttributes(**filter_valid_attributes(span_attributes)) span = tracer.start_span( name=get_span_name(APIS["CHAT_COMPLETION"]["METHOD"]), @@ -356,7 +366,7 @@ async def traced_method( **get_extra_attributes(), # type: ignore } - attributes = LLMSpanAttributes(**span_attributes) + attributes = LLMSpanAttributes(**filter_valid_attributes(span_attributes)) span = tracer.start_span( name=get_span_name(APIS["CHAT_COMPLETION"]["METHOD"]), @@ -438,7 +448,7 @@ def traced_method( [kwargs.get("input", "")] ) - attributes = LLMSpanAttributes(**span_attributes) + attributes = LLMSpanAttributes(**filter_valid_attributes(span_attributes)) with tracer.start_as_current_span( name=get_span_name(APIS["EMBEDDINGS_CREATE"]["METHOD"]), @@ -487,7 +497,7 @@ async def traced_method( **get_extra_attributes(), # type: ignore } - attributes = LLMSpanAttributes(**span_attributes) + attributes = LLMSpanAttributes(**filter_valid_attributes(span_attributes)) encoding_format = kwargs.get("encoding_format") if encoding_format is not None: diff --git a/src/langtrace_python_sdk/version.py b/src/langtrace_python_sdk/version.py index bff004f2..ab20ff9c 100644 --- a/src/langtrace_python_sdk/version.py +++ b/src/langtrace_python_sdk/version.py @@ -1 +1 @@ -__version__ = "2.3.14" +__version__ = "2.3.15" From 44e97ca117b3ef2a7a8c0e6974116b5de12d71cc Mon Sep 17 00:00:00 2001 From: Rohit Kadhe <113367036+rohit-kadhe@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:08:45 -0600 Subject: [PATCH 2/4] Rohit/s3 en 2819 release sdk (#353) * add sentry integration * update readme * hotfix for checking package installed * Support Autogen (#242) * autogen kickstart * enhance autogen * run autogen * finish autogen * add readme * disable cache * Support genai and also add token reporting and other data points (#345) * support genai and also add token reporting * rename example file * fix --------- Co-authored-by: Karthik Kalyanaraman * fix: weaviate datetime handling for request and response (#346) * fix: weaviate datetime handling for request and response * bump version * bump version --------- Co-authored-by: Karthik Kalyanaraman * Minor bugfix to langchain instrumentation (#348) * Bugfix * bump version * fix: handling for openai NOT_GIVEN default arg value (#350) * fix: handling for openai NOT_GIVEN default arg value * style: fix formating * fix: tool choice for groq, datetime for cohere (#352) --------- Co-authored-by: Ali Waleed Co-authored-by: Karthik Kalyanaraman Co-authored-by: darshit-s3 <119623510+darshit-s3@users.noreply.github.com> Co-authored-by: Karthik Kalyanaraman <105607645+karthikscale3@users.noreply.github.com> --- src/examples/cohere_example/rerank.py | 21 ++++- src/examples/langchain_example/__init__.py | 4 +- .../langchain_example/groq_example.py | 80 ++++++++++++++++++- .../instrumentation/cohere/patch.py | 5 +- src/langtrace_python_sdk/utils/llm.py | 3 +- src/langtrace_python_sdk/utils/misc.py | 8 ++ src/langtrace_python_sdk/version.py | 2 +- 7 files changed, 113 insertions(+), 10 deletions(-) diff --git a/src/examples/cohere_example/rerank.py b/src/examples/cohere_example/rerank.py index de1a21f5..3995feb5 100644 --- a/src/examples/cohere_example/rerank.py +++ b/src/examples/cohere_example/rerank.py @@ -1,5 +1,6 @@ import cohere from dotenv import find_dotenv, load_dotenv +from datetime import datetime from langtrace_python_sdk import langtrace @@ -16,10 +17,22 @@ # @with_langtrace_root_span("embed_create") def rerank(): docs = [ - "Carson City is the capital city of the American state of Nevada.", - "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.", - "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district.", - "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states.", + { + "text": "Carson City is the capital city of the American state of Nevada.", + "date": datetime.now(), + }, + { + "text": "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.", + "date": datetime(2020, 5, 17), + }, + { + "text": "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district.", + "date": datetime(1776, 7, 4), + }, + { + "text": "Capital punishment (the death penalty) has existed in the United States since before the United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states.", + "date": datetime(2023, 9, 14), + }, ] response = co.rerank( diff --git a/src/examples/langchain_example/__init__.py b/src/examples/langchain_example/__init__.py index ceaee24c..f9621a2c 100644 --- a/src/examples/langchain_example/__init__.py +++ b/src/examples/langchain_example/__init__.py @@ -2,7 +2,7 @@ from .basic import basic_app, rag, load_and_split from langtrace_python_sdk import with_langtrace_root_span -from .groq_example import groq_basic, groq_streaming +from .groq_example import groq_basic, groq_tool_choice, groq_streaming from .langgraph_example_tools import basic_graph_tools @@ -20,3 +20,5 @@ class GroqRunner: @with_langtrace_root_span("Groq") def run(self): groq_streaming() + groq_basic() + groq_tool_choice() diff --git a/src/examples/langchain_example/groq_example.py b/src/examples/langchain_example/groq_example.py index c16e4851..99a61761 100644 --- a/src/examples/langchain_example/groq_example.py +++ b/src/examples/langchain_example/groq_example.py @@ -1,6 +1,6 @@ +import json + from dotenv import find_dotenv, load_dotenv -from langchain_core.prompts import ChatPromptTemplate -from langchain_groq import ChatGroq from groq import Groq _ = load_dotenv(find_dotenv()) @@ -30,6 +30,82 @@ def groq_basic(): return chat_completion +def groq_tool_choice(): + + user_prompt = "What is 25 * 4 + 10?" + MODEL = "llama3-groq-70b-8192-tool-use-preview" + + def calculate(expression): + """Evaluate a mathematical expression""" + try: + result = eval(expression) + return json.dumps({"result": result}) + except: + return json.dumps({"error": "Invalid expression"}) + + messages = [ + { + "role": "system", + "content": "You are a calculator assistant. Use the calculate function to perform mathematical operations and provide the results.", + }, + { + "role": "user", + "content": user_prompt, + }, + ] + tools = [ + { + "type": "function", + "function": { + "name": "calculate", + "description": "Evaluate a mathematical expression", + "parameters": { + "type": "object", + "properties": { + "expression": { + "type": "string", + "description": "The mathematical expression to evaluate", + } + }, + "required": ["expression"], + }, + }, + } + ] + response = client.chat.completions.create( + model=MODEL, + messages=messages, + tools=tools, + tool_choice={"type": "function", "function": {"name": "calculate"}}, + max_tokens=4096, + ) + + response_message = response.choices[0].message + tool_calls = response_message.tool_calls + if tool_calls: + available_functions = { + "calculate": calculate, + } + messages.append(response_message) + for tool_call in tool_calls: + function_name = tool_call.function.name + function_to_call = available_functions[function_name] + function_args = json.loads(tool_call.function.arguments) + function_response = function_to_call( + expression=function_args.get("expression") + ) + messages.append( + { + "tool_call_id": tool_call.id, + "role": "tool", + "name": function_name, + "content": function_response, + } + ) + second_response = client.chat.completions.create(model=MODEL, messages=messages) + return second_response.choices[0].message.content + + def groq_streaming(): chat_completion = client.chat.completions.create( messages=[ diff --git a/src/langtrace_python_sdk/instrumentation/cohere/patch.py b/src/langtrace_python_sdk/instrumentation/cohere/patch.py index c165a10c..38908c3d 100644 --- a/src/langtrace_python_sdk/instrumentation/cohere/patch.py +++ b/src/langtrace_python_sdk/instrumentation/cohere/patch.py @@ -27,6 +27,7 @@ ) from langtrace.trace_attributes import Event, LLMSpanAttributes from langtrace_python_sdk.utils import set_span_attribute +from langtrace_python_sdk.utils.misc import datetime_encoder from opentelemetry.trace import SpanKind from opentelemetry.trace.status import Status, StatusCode @@ -50,7 +51,9 @@ def traced_method(wrapped, instance, args, kwargs): SpanAttributes.LLM_REQUEST_MODEL: kwargs.get("model") or "command-r-plus", SpanAttributes.LLM_URL: APIS["RERANK"]["URL"], SpanAttributes.LLM_PATH: APIS["RERANK"]["ENDPOINT"], - SpanAttributes.LLM_REQUEST_DOCUMENTS: json.dumps(kwargs.get("documents")), + SpanAttributes.LLM_REQUEST_DOCUMENTS: json.dumps( + kwargs.get("documents"), cls=datetime_encoder + ), SpanAttributes.LLM_COHERE_RERANK_QUERY: kwargs.get("query"), **get_extra_attributes(), } diff --git a/src/langtrace_python_sdk/utils/llm.py b/src/langtrace_python_sdk/utils/llm.py index 6d9647e7..e1d6fa9d 100644 --- a/src/langtrace_python_sdk/utils/llm.py +++ b/src/langtrace_python_sdk/utils/llm.py @@ -124,6 +124,7 @@ def get_llm_request_attributes(kwargs, prompts=None, model=None, operation_name= top_p = kwargs.get("p", None) or kwargs.get("top_p", None) tools = kwargs.get("tools", None) + tool_choice = kwargs.get("tool_choice", None) return { SpanAttributes.LLM_OPERATION_NAME: operation_name, SpanAttributes.LLM_REQUEST_MODEL: model @@ -141,7 +142,7 @@ def get_llm_request_attributes(kwargs, prompts=None, model=None, operation_name= SpanAttributes.LLM_FREQUENCY_PENALTY: kwargs.get("frequency_penalty"), SpanAttributes.LLM_REQUEST_SEED: kwargs.get("seed"), SpanAttributes.LLM_TOOLS: json.dumps(tools) if tools else None, - SpanAttributes.LLM_TOOL_CHOICE: kwargs.get("tool_choice"), + SpanAttributes.LLM_TOOL_CHOICE: json.dumps(tool_choice) if tool_choice else None, SpanAttributes.LLM_REQUEST_LOGPROPS: kwargs.get("logprobs"), SpanAttributes.LLM_REQUEST_LOGITBIAS: kwargs.get("logit_bias"), SpanAttributes.LLM_REQUEST_TOP_LOGPROPS: kwargs.get("top_logprobs"), diff --git a/src/langtrace_python_sdk/utils/misc.py b/src/langtrace_python_sdk/utils/misc.py index a0d20452..56924cfe 100644 --- a/src/langtrace_python_sdk/utils/misc.py +++ b/src/langtrace_python_sdk/utils/misc.py @@ -60,3 +60,11 @@ def is_serializable(value): # Convert to string representation return json.dumps(serializable_args) + + +class datetime_encoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, datetime): + return o.isoformat() + + return json.JSONEncoder.default(self, o) diff --git a/src/langtrace_python_sdk/version.py b/src/langtrace_python_sdk/version.py index ab20ff9c..e4f37b40 100644 --- a/src/langtrace_python_sdk/version.py +++ b/src/langtrace_python_sdk/version.py @@ -1 +1 @@ -__version__ = "2.3.15" +__version__ = "2.3.16" From 9b6403f282c75628a996f07cc6463b72963bfbbd Mon Sep 17 00:00:00 2001 From: Ali Waleed Date: Tue, 17 Sep 2024 13:08:15 +0300 Subject: [PATCH 3/4] fixing disable instrumentation logic --- src/langtrace_python_sdk/langtrace.py | 88 ++++++---------------- src/langtrace_python_sdk/types/__init__.py | 19 +++-- src/langtrace_python_sdk/utils/__init__.py | 43 ++++++++++- 3 files changed, 81 insertions(+), 69 deletions(-) diff --git a/src/langtrace_python_sdk/langtrace.py b/src/langtrace_python_sdk/langtrace.py index b1345d89..1fbd33c2 100644 --- a/src/langtrace_python_sdk/langtrace.py +++ b/src/langtrace_python_sdk/langtrace.py @@ -16,8 +16,7 @@ import os import sys -from typing import Optional -import importlib.util +from typing import Any, Optional from colorama import Fore from langtrace_python_sdk.constants import LANGTRACE_SDK_NAME, SENTRY_DSN from opentelemetry import trace @@ -62,7 +61,12 @@ InstrumentationMethods, InstrumentationType, ) -from langtrace_python_sdk.utils import check_if_sdk_is_outdated, get_sdk_version +from langtrace_python_sdk.utils import ( + check_if_sdk_is_outdated, + get_sdk_version, + is_package_installed, + validate_instrumentations, +) from langtrace_python_sdk.utils.langtrace_sampler import LangtraceSampler import sentry_sdk @@ -193,10 +197,10 @@ def init( def init_instrumentations( disable_instrumentations: Optional[DisableInstrumentations], - all_instrumentations: dict + all_instrumentations: dict, ): if disable_instrumentations is None: - for idx, (name, v) in enumerate(all_instrumentations.items()): + for name, v in all_instrumentations.items(): if is_package_installed(name): v.instrument() @@ -205,61 +209,19 @@ def init_instrumentations( validate_instrumentations(disable_instrumentations) for key in disable_instrumentations: - for vendor in disable_instrumentations[key]: - if key == "only": - filtered_dict = { - k: v - for k, v in all_instrumentations.items() - if k != vendor.value - } - for _, v in filtered_dict.items(): - v.instrument() - else: - filtered_dict = { - k: v - for k, v in all_instrumentations.items() - if k == vendor.value - } - - for _, v in filtered_dict.items(): - v.instrument() - - -def validate_instrumentations(disable_instrumentations): - if disable_instrumentations is not None: - for key, value in disable_instrumentations.items(): - if isinstance(value, str): - # Convert single string to list of enum values - disable_instrumentations[key] = [InstrumentationType.from_string(value)] - elif isinstance(value, list): - # Convert list of strings to list of enum values - disable_instrumentations[key] = [ - ( - InstrumentationType.from_string(item) - if isinstance(item, str) - else item - ) - for item in value - ] - # Validate all items are of enum type - if not all( - isinstance(item, InstrumentationType) - for item in disable_instrumentations[key] - ): - raise TypeError( - f"All items in {key} must be of type InstrumentationType" - ) - if ( - disable_instrumentations.get("all_except") is not None - and disable_instrumentations.get("only") is not None - ): - raise ValueError( - "Cannot specify both only and all_except in disable_instrumentations" - ) - - -def is_package_installed(package_name): - import pkg_resources - - installed_packages = {p.key for p in pkg_resources.working_set} - return package_name in installed_packages + vendors = [k.value for k in disable_instrumentations[key]] + + key = next(iter(disable_instrumentations)) + filtered_dict = {} + if key == "all_except": + filtered_dict = { + k: v for k, v in all_instrumentations.items() if k in vendors + } + elif key == "only": + filtered_dict = { + k: v for k, v in all_instrumentations.items() if k not in vendors + } + + for name, v in filtered_dict.items(): + if is_package_installed(name): + v.instrument() diff --git a/src/langtrace_python_sdk/types/__init__.py b/src/langtrace_python_sdk/types/__init__.py index 414aebff..3173efae 100644 --- a/src/langtrace_python_sdk/types/__init__.py +++ b/src/langtrace_python_sdk/types/__init__.py @@ -8,16 +8,22 @@ class InstrumentationType(Enum): ANTHROPIC = "anthropic" GROQ = "groq" MISTRAL = "mistral" - PINECONE = "pinecone" - LLAMAINDEX = "llamaindex" + PINECONE = "pinecone-client" + LLAMAINDEX = "llama-index" CHROMADB = "chromadb" QDRANT = "qdrant" LANGCHAIN = "langchain" - LANGCHAIN_CORE = "langchain_core" - LANGCHAIN_COMMUNITY = "langchain_community" + LANGCHAIN_CORE = "langchain-core" + LANGCHAIN_COMMUNITY = "langchain-community" LANGGRAPH = "langgraph" WEAVIATE = "weaviate" OLLAMA = "ollama" + AUTOGEN = "autogen" + DSPY = "dspy-ai" + CREWAI = "crewai" + GEMINI = "google-generativeai" + VERTEXAI = "google-cloud-aiplatform" + MISTRALAI = "mistralai" @staticmethod def from_string(value: str): @@ -112,7 +118,10 @@ class InstrumentationMethods(TypedDict): cohere: List[VendorMethods.CohereMethods] weaviate: List[str] + _T = TypeVar("_T") + + class NotGiven: """ A sentinel singleton class used to distinguish omitted keyword arguments @@ -139,4 +148,4 @@ def __repr__(self) -> str: NotGivenOr = Union[_T, NotGiven] -NOT_GIVEN = NotGiven() \ No newline at end of file +NOT_GIVEN = NotGiven() diff --git a/src/langtrace_python_sdk/utils/__init__.py b/src/langtrace_python_sdk/utils/__init__.py index d970ba3c..5aab83ed 100644 --- a/src/langtrace_python_sdk/utils/__init__.py +++ b/src/langtrace_python_sdk/utils/__init__.py @@ -1,4 +1,4 @@ -from langtrace_python_sdk.types import NOT_GIVEN +from langtrace_python_sdk.types import NOT_GIVEN, InstrumentationType from .sdk_version_checker import SDKVersionChecker from opentelemetry.trace import Span from langtrace.trace_attributes import SpanAttributes @@ -49,3 +49,44 @@ def check_if_sdk_is_outdated(): def get_sdk_version(): return SDKVersionChecker().get_sdk_version() + + +def validate_instrumentations(disable_instrumentations): + if disable_instrumentations is not None: + if ( + disable_instrumentations.get("all_except") is not None + and disable_instrumentations.get("only") is not None + ): + raise ValueError( + "Cannot specify both only and all_except in disable_instrumentations" + ) + + for key, value in disable_instrumentations.items(): + if isinstance(value, str): + # Convert single string to list of enum values + disable_instrumentations[key] = [InstrumentationType.from_string(value)] + elif isinstance(value, list): + # Convert list of strings to list of enum values + disable_instrumentations[key] = [ + ( + InstrumentationType.from_string(item) + if isinstance(item, str) + else item + ) + for item in value + ] + # Validate all items are of enum type + if not all( + isinstance(item, InstrumentationType) + for item in disable_instrumentations[key] + ): + raise TypeError( + f"All items in {key} must be of type InstrumentationType" + ) + + +def is_package_installed(package_name): + import pkg_resources + + installed_packages = {p.key for p in pkg_resources.working_set} + return package_name in installed_packages From 753a54c45454154158331d5b191767299acbc273 Mon Sep 17 00:00:00 2001 From: Ali Waleed Date: Tue, 17 Sep 2024 13:08:36 +0300 Subject: [PATCH 4/4] bump version --- src/langtrace_python_sdk/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/langtrace_python_sdk/version.py b/src/langtrace_python_sdk/version.py index e4f37b40..d77091b9 100644 --- a/src/langtrace_python_sdk/version.py +++ b/src/langtrace_python_sdk/version.py @@ -1 +1 @@ -__version__ = "2.3.16" +__version__ = "2.3.17"