langtrace-python-sdk 2.2.20__py3-none-any.whl → 2.2.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. examples/crewai_example/__init__.py +0 -0
  2. examples/crewai_example/simple_agent/__init__.py +0 -0
  3. examples/crewai_example/simple_agent/agents.py +32 -0
  4. examples/crewai_example/simple_agent/main.py +48 -0
  5. examples/crewai_example/simple_agent/tasks.py +21 -0
  6. examples/crewai_example/trip_planner/__init__.py +0 -0
  7. examples/crewai_example/trip_planner/agents.py +54 -0
  8. examples/crewai_example/trip_planner/main.py +96 -0
  9. examples/crewai_example/trip_planner/tasks.py +120 -0
  10. examples/crewai_example/trip_planner/tools/calculator.py +16 -0
  11. examples/crewai_example/trip_planner/tools/search_tools.py +73 -0
  12. langtrace_python_sdk/instrumentation/anthropic/patch.py +6 -58
  13. langtrace_python_sdk/instrumentation/cohere/patch.py +5 -13
  14. langtrace_python_sdk/instrumentation/langchain_core/instrumentation.py +18 -3
  15. langtrace_python_sdk/instrumentation/ollama/patch.py +43 -110
  16. langtrace_python_sdk/utils/llm.py +62 -36
  17. langtrace_python_sdk/version.py +1 -1
  18. {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/METADATA +1 -1
  19. {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/RECORD +25 -14
  20. tests/anthropic/test_anthropic.py +0 -4
  21. tests/cohere/test_cohere_chat.py +0 -2
  22. tests/openai/test_chat_completion.py +0 -6
  23. {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/WHEEL +0 -0
  24. {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/entry_points.txt +0 -0
  25. {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/licenses/LICENSE +0 -0
File without changes
File without changes
@@ -0,0 +1,32 @@
1
+ from crewai import Agent
2
+ from langchain_openai import ChatOpenAI
3
+ from langchain_anthropic import ChatAnthropic
4
+ from langchain_cohere import ChatCohere
5
+ from langchain_ollama import ChatOllama
6
+
7
+
8
+ class PoetryAgents:
9
+ def __init__(self):
10
+ self.open_ai = ChatOpenAI(
11
+ model_name="gpt-4", temperature=0.7, stream_usage=True
12
+ )
13
+ self.anthropic = ChatAnthropic(
14
+ model_name="claude-3-5-sonnet-20240620", temperature=0.7
15
+ )
16
+
17
+ self.cohere = ChatCohere(model="command-r", temperature=0.7)
18
+ self.ollama = ChatOllama(model="llama3", temperature=0.7)
19
+
20
+ def create_poet_agent(self):
21
+ return Agent(
22
+ role="Expert Poetry Writer",
23
+ backstory="""
24
+ I am an Expert in poetry writing and creative expression.
25
+ I have been writing poetry for over 10 years and have published several collections.
26
+ I have a deep understanding of various poetic forms, styles, and themes. I am here to help you create beautiful and meaningful poetry that resonates with your emotions and experiences.
27
+ """,
28
+ goal="""Create a poem that captures the essence of a given theme or emotion""",
29
+ allow_delegation=False,
30
+ verbose=True,
31
+ llm=self.ollama,
32
+ )
@@ -0,0 +1,48 @@
1
+ from crewai import Crew
2
+ from textwrap import dedent
3
+ from .agents import PoetryAgents
4
+ from .tasks import PoetryTasks
5
+ from langtrace_python_sdk import langtrace
6
+ from dotenv import load_dotenv
7
+ import agentops
8
+
9
+ load_dotenv()
10
+ agentops.init()
11
+ langtrace.init(write_spans_to_console=False, batch=False)
12
+
13
+
14
+ class PoetryCrew:
15
+ def __init__(self, topic) -> None:
16
+ self.topic = topic
17
+
18
+ def run(self):
19
+ agents = PoetryAgents()
20
+ tasks = PoetryTasks()
21
+
22
+ poetry_agent = agents.create_poet_agent()
23
+
24
+ create_poem = tasks.create_poem(poetry_agent, self.topic)
25
+
26
+ crew = Crew(agents=[poetry_agent], tasks=[create_poem], verbose=True)
27
+ res = crew.kickoff()
28
+ return res
29
+
30
+
31
+ # This is the main function that you will use to run your custom crew.
32
+ if __name__ == "__main__":
33
+ print("## Welcome to Poetry Crew")
34
+ print("-------------------------------")
35
+ topic = input(
36
+ dedent(
37
+ """
38
+ What topic do you want to write a poem on?
39
+ """
40
+ )
41
+ )
42
+
43
+ poetry_crew = PoetryCrew(topic=topic)
44
+ result = poetry_crew.run()
45
+ print("\n\n########################")
46
+ print("## Here is you poem")
47
+ print("########################\n")
48
+ print(result)
@@ -0,0 +1,21 @@
1
+ from crewai import Task
2
+ from textwrap import dedent
3
+
4
+
5
+ class PoetryTasks:
6
+ def create_poem(self, agent, topic):
7
+ return Task(
8
+ description=dedent(
9
+ f"""
10
+ **Task**: Create a Poem on {topic}
11
+ **Description**: Write a poem on the given topic that captures the essence of the theme or emotion.
12
+ The poem should be creative, expressive, and resonate with the reader's emotions and experiences.
13
+ Your poem should be well-structured, engaging, and evoke a sense of beauty and meaning.
14
+
15
+ **Parameters**:
16
+ - Topic: {topic}
17
+ """
18
+ ),
19
+ expected_output="A creative and expressive poem that captures the essence of the given topic.",
20
+ agent=agent,
21
+ )
File without changes
@@ -0,0 +1,54 @@
1
+ from crewai import Agent
2
+ from langchain_openai import ChatOpenAI
3
+ from langchain_ollama import ChatOllama
4
+ from langchain_cohere import ChatCohere
5
+ from langchain_anthropic import ChatAnthropic
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+
11
+ class TravelAgents:
12
+ def __init__(self):
13
+ self.OpenAIGPT35 = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.7)
14
+ self.OpenAIGPT4 = ChatOpenAI(model_name="gpt-4", temperature=0.7)
15
+ self.Ollama = ChatOllama(model="openhermes")
16
+ self.Cohere = ChatCohere(model="command-r")
17
+ self.Anthropic = ChatAnthropic(model="claude-3-5-sonnet")
18
+
19
+ def expert_travel_agent(self):
20
+ return Agent(
21
+ role="Expert Travel Agent",
22
+ backstory="""
23
+ I am an Expert in travel planning and itinerary creation.
24
+ I have been in the travel industry for over 10 years and have helped thousands of clients plan their dream vacations.
25
+ I have extensive knowledge of popular travel destinations, local attractions, and travel logistics. I am here to help you create a personalized travel itinerary that suits your preferences and budget.
26
+ """,
27
+ goal="""Create a 7 day travel itinerary with detailed per-day plans, include budget, packing suggestions, and local/safety tips.""",
28
+ # tools=[tool_1, tool_2],
29
+ allow_delegation=False,
30
+ verbose=True,
31
+ llm=self.OpenAIGPT4,
32
+ )
33
+
34
+ def city_selection_expert(self):
35
+ return Agent(
36
+ role="City Selection Expert",
37
+ backstory="""Expert at analyzing and selecting the best cities for travel based on data""",
38
+ goal="""Select the best cities based on weather, season, prices and traveler preferences""",
39
+ # tools=[tool_1, tool_2],
40
+ allow_delegation=False,
41
+ verbose=True,
42
+ llm=self.OpenAIGPT4,
43
+ )
44
+
45
+ def local_tour_guide(self):
46
+ return Agent(
47
+ role="Local Expert at this city",
48
+ goal="Provide the BEST insights about the selected city",
49
+ backstory="""A knowledgeable local guide with extensive information about the city, it's attractions and customs""",
50
+ # tools=[tool_1, tool_2],
51
+ allow_delegation=False,
52
+ verbose=True,
53
+ llm=self.OpenAIGPT4,
54
+ )
@@ -0,0 +1,96 @@
1
+ from crewai import Crew
2
+ from textwrap import dedent
3
+ from .agents import TravelAgents
4
+ from .tasks import TravelTasks
5
+ from langtrace_python_sdk import langtrace
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+ langtrace.init()
11
+
12
+
13
+ class TripCrew:
14
+ def __init__(self, origin, cities, date_range, interests):
15
+ self.origin = origin
16
+ self.cities = cities
17
+ self.date_range = date_range
18
+ self.interests = interests
19
+
20
+ def run(self):
21
+ # Define your custom agents and tasks in agents.py and tasks.py
22
+ agents = TravelAgents()
23
+ tasks = TravelTasks()
24
+
25
+ # Define your custom agents and tasks here
26
+ expert_travel_agent = agents.expert_travel_agent()
27
+ city_selection_expert = agents.city_selection_expert()
28
+ local_tour_guide = agents.local_tour_guide()
29
+
30
+ # Custom tasks include agent name and variables as input
31
+ plan_itinerary = tasks.plan_itinerary(
32
+ expert_travel_agent, self.cities, self.date_range, self.interests
33
+ )
34
+
35
+ identify_city = tasks.identify_city(
36
+ city_selection_expert,
37
+ self.origin,
38
+ self.cities,
39
+ self.interests,
40
+ self.date_range,
41
+ )
42
+
43
+ gather_city_info = tasks.gather_city_info(
44
+ local_tour_guide, self.cities, self.date_range, self.interests
45
+ )
46
+
47
+ # Define your custom crew here
48
+ crew = Crew(
49
+ agents=[expert_travel_agent, city_selection_expert, local_tour_guide],
50
+ tasks=[plan_itinerary, identify_city, gather_city_info],
51
+ verbose=True,
52
+ )
53
+
54
+ result = crew.kickoff()
55
+ return result
56
+
57
+
58
+ # This is the main function that you will use to run your custom crew.
59
+ if __name__ == "__main__":
60
+ print("## Welcome to Trip Planner Crew")
61
+ print("-------------------------------")
62
+ origin = input(
63
+ dedent(
64
+ """
65
+ From where will you be traveling from?
66
+ """
67
+ )
68
+ )
69
+ cities = input(
70
+ dedent(
71
+ """
72
+ What are the cities options you are interested in visiting?
73
+ """
74
+ )
75
+ )
76
+ date_range = input(
77
+ dedent(
78
+ """
79
+ What is the date range you are interested in traveling?
80
+ """
81
+ )
82
+ )
83
+ interests = input(
84
+ dedent(
85
+ """
86
+ What are some of your high level interests and hobbies?
87
+ """
88
+ )
89
+ )
90
+
91
+ trip_crew = TripCrew(origin, cities, date_range, interests)
92
+ result = trip_crew.run()
93
+ print("\n\n########################")
94
+ print("## Here is you Trip Plan")
95
+ print("########################\n")
96
+ print(result)
@@ -0,0 +1,120 @@
1
+ from crewai import Task
2
+ from textwrap import dedent
3
+
4
+ """
5
+ Creating Tasks Cheat Sheet:
6
+ - Begin with the end in mind. Identify the specific outcome your tasks are aiming to achieve.
7
+ - Break down the outcome into actionable tasks, assigning each task to the appropriate agent.
8
+ - Ensure tasks are descriptive, providing clear instructions and expected deliverables.
9
+
10
+ Goal:
11
+ - Develop a detailed itinerary, including city selection, attractions, and practical travel advice.
12
+
13
+ Key Steps for Task Creation:
14
+ 1. Identify the Desired Outcome: Define what success looks like for your project.
15
+ - A detailed 7 day travel itenerary.
16
+
17
+ 2. Task Breakdown: Divide the goal into smaller, manageable tasks that agents can execute.
18
+ - Itenerary Planning: develop a detailed plan for each day of the trip.
19
+ - City Selection: Analayze and pick the best cities to visit.
20
+ - Local Tour Guide: Find a local expert to provide insights and recommendations.
21
+
22
+ 3. Assign Tasks to Agents: Match tasks with agents based on their roles and expertise.
23
+
24
+ 4. Task Description Template:
25
+ - Use this template as a guide to define each task in your CrewAI application.
26
+ - This template helps ensure that each task is clearly defined, actionable, and aligned with the specific goals of your project.
27
+
28
+ Template:
29
+ ---------
30
+ def [task_name](self, agent, [parameters]):
31
+ return Task(description=dedent(f'''
32
+ **Task**: [Provide a concise name or summary of the task.]
33
+ **Description**: [Detailed description of what the agent is expected to do, including actionable steps and expected outcomes. This should be clear and direct, outlining the specific actions required to complete the task.]
34
+
35
+ **Parameters**:
36
+ - [Parameter 1]: [Description]
37
+ - [Parameter 2]: [Description]
38
+ ... [Add more parameters as needed.]
39
+
40
+ **Note**: [Optional section for incentives or encouragement for high-quality work. This can include tips, additional context, or motivations to encourage agents to deliver their best work.]
41
+
42
+ '''), agent=agent)
43
+
44
+ """
45
+
46
+
47
+ class TravelTasks:
48
+ def __tip_section(self):
49
+ return "If you do your BEST WORK, I'll give you a $10,000 commission!"
50
+
51
+ def plan_itinerary(self, agent, city, travel_dates, interests):
52
+ return Task(
53
+ description=dedent(
54
+ f"""
55
+ **Task**: Develop a 7-Day Travel Itinerary
56
+ **Description**: Expand the city guide into a full 7-day travel itinerary with detailed
57
+ per-day plans, including weather forecasts, places to eat, packing suggestions,
58
+ and a budget breakdown. You MUST suggest actual places to visit, actual hotels to stay,
59
+ and actual restaurants to go to. This itinerary should cover all aspects of the trip,
60
+ from arrival to departure, integrating the city guide information with practical travel logistics.
61
+
62
+ **Parameters**:
63
+ - City: {city}
64
+ - Trip Date: {travel_dates}
65
+ - Traveler Interests: {interests}
66
+
67
+ **Note**: {self.__tip_section()}
68
+ """
69
+ ),
70
+ expected_output="A detailed 7-day travel itinerary with per-day plans, budget breakdown, and practical travel advice.",
71
+ agent=agent,
72
+ )
73
+
74
+ def identify_city(self, agent, origin, cities, interests, travel_dates):
75
+ return Task(
76
+ description=dedent(
77
+ f"""
78
+ **Task**: Identify the Best City for the Trip
79
+ **Description**: Analyze and select the best city for the trip based on specific
80
+ criteria such as weather patterns, seasonal events, and travel costs.
81
+ This task involves comparing multiple cities, considering factors like current weather
82
+ conditions, upcoming cultural or seasonal events, and overall travel expenses.
83
+ Your final answer must be a detailed report on the chosen city,
84
+ including actual flight costs, weather forecast, and attractions.
85
+
86
+
87
+ **Parameters**:
88
+ - Origin: {origin}
89
+ - Cities: {cities}
90
+ - Interests: {interests}
91
+ - Travel Date: {travel_dates}
92
+
93
+ **Note**: {self.__tip_section()}
94
+ """
95
+ ),
96
+ agent=agent,
97
+ expected_output="A detailed report on the best city for the trip, including flight costs, weather forecast, and attractions.",
98
+ )
99
+
100
+ def gather_city_info(self, agent, city, travel_dates, interests):
101
+ return Task(
102
+ description=dedent(
103
+ f"""
104
+ **Task**: Gather In-depth City Guide Information
105
+ **Description**: Compile an in-depth guide for the selected city, gathering information about
106
+ key attractions, local customs, special events, and daily activity recommendations.
107
+ This guide should provide a thorough overview of what the city has to offer, including
108
+ hidden gems, cultural hotspots, must-visit landmarks, weather forecasts, and high-level costs.
109
+
110
+ **Parameters**:
111
+ - Cities: {city}
112
+ - Interests: {interests}
113
+ - Travel Date: {travel_dates}
114
+
115
+ **Note**: {self.__tip_section()}
116
+ """
117
+ ),
118
+ expected_output="An in-depth city guide with detailed information on attractions, local customs, events, and daily activity recommendations.",
119
+ agent=agent,
120
+ )
@@ -0,0 +1,16 @@
1
+ from langchain.tools import tool
2
+
3
+
4
+ class CalculatorTools:
5
+
6
+ @tool("Make a calculation")
7
+ def calculate(self, operation):
8
+ """Useful to perform any mathematical calculations,
9
+ like sum, minus, multiplication, division, etc.
10
+ The input to this tool should be a mathematical
11
+ expression, a couple examples are `200*7` or `5000/2*10`
12
+ """
13
+ try:
14
+ return eval(operation)
15
+ except SyntaxError:
16
+ return "Error: Invalid syntax in mathematical expression"
@@ -0,0 +1,73 @@
1
+ import json
2
+ import os
3
+
4
+ import requests
5
+ from langchain.tools import tool
6
+
7
+
8
+ class SearchTools:
9
+
10
+ @tool("Search the internet")
11
+ def search_internet(self, query):
12
+ """Useful to search the internet
13
+ about a a given topic and return relevant results"""
14
+ top_result_to_return = 4
15
+ url = "https://google.serper.dev/search"
16
+ payload = json.dumps({"q": query})
17
+ headers = {
18
+ "X-API-KEY": os.environ["SERPER_API_KEY"],
19
+ "content-type": "application/json",
20
+ }
21
+ response = requests.request(
22
+ "POST", url, headers=headers, data=payload, timeout=2000
23
+ )
24
+ # check if there is an organic key
25
+ if "organic" not in response.json():
26
+ return "Sorry, I couldn't find anything about that, there could be an error with you serper api key."
27
+ else:
28
+ results = response.json()["organic"]
29
+ string = []
30
+ for result in results[:top_result_to_return]:
31
+ try:
32
+ string.append(
33
+ "\n".join(
34
+ [
35
+ f"Title: {result['title']}",
36
+ f"Link: {result['link']}",
37
+ f"Snippet: {result['snippet']}",
38
+ "\n-----------------",
39
+ ]
40
+ )
41
+ )
42
+ except KeyError:
43
+ next
44
+
45
+ return "\n".join(string)
46
+
47
+
48
+ # from pydantic import BaseModel, Field
49
+ # from langchain.tools import tool
50
+
51
+ # # Define a Pydantic model for the tool's input parameters
52
+ # class CalculationInput(BaseModel):
53
+ # operation: str = Field(..., description="The mathematical operation to perform")
54
+ # factor: float = Field(..., description="A factor by which to multiply the result of the operation")
55
+
56
+ # # Use the tool decorator with the args_schema parameter pointing to the Pydantic model
57
+ # @tool("perform_calculation", args_schema=CalculationInput, return_direct=True)
58
+ # def perform_calculation(operation: str, factor: float) -> str:
59
+ # """
60
+ # Performs a specified mathematical operation and multiplies the result by a given factor.
61
+
62
+ # Parameters:
63
+ # - operation (str): A string representing a mathematical operation (e.g., "10 + 5").
64
+ # - factor (float): A factor by which to multiply the result of the operation.
65
+
66
+ # Returns:
67
+ # - A string representation of the calculation result.
68
+ # """
69
+ # # Perform the calculation
70
+ # result = eval(operation) * factor
71
+
72
+ # # Return the result as a string
73
+ # return f"The result of '{operation}' multiplied by {factor} is {result}."
@@ -17,8 +17,11 @@ limitations under the License.
17
17
  import json
18
18
 
19
19
  from langtrace.trace_attributes import Event, LLMSpanAttributes
20
- from langtrace_python_sdk.utils import set_span_attribute, silently_fail
20
+ from langtrace_python_sdk.utils import set_span_attribute
21
+ from langtrace_python_sdk.utils.silently_fail import silently_fail
22
+
21
23
  from langtrace_python_sdk.utils.llm import (
24
+ StreamWrapper,
22
25
  get_extra_attributes,
23
26
  get_langtrace_attributes,
24
27
  get_llm_request_attributes,
@@ -26,7 +29,6 @@ from langtrace_python_sdk.utils.llm import (
26
29
  get_span_name,
27
30
  is_streaming,
28
31
  set_event_completion,
29
- set_event_completion_chunk,
30
32
  set_usage_attributes,
31
33
  )
32
34
  from opentelemetry.trace import SpanKind
@@ -83,61 +85,7 @@ def messages_create(original_method, version, tracer):
83
85
  span.end()
84
86
  raise
85
87
 
86
- def handle_streaming_response(result, span):
87
- """Process and yield streaming response chunks."""
88
- result_content = []
89
- span.add_event(Event.STREAM_START.value)
90
- input_tokens = 0
91
- output_tokens = 0
92
- try:
93
- for chunk in result:
94
- if (
95
- hasattr(chunk, "message")
96
- and chunk.message is not None
97
- and hasattr(chunk.message, "model")
98
- and chunk.message.model is not None
99
- ):
100
- span.set_attribute(
101
- SpanAttributes.LLM_RESPONSE_MODEL, chunk.message.model
102
- )
103
- content = ""
104
- if hasattr(chunk, "delta") and chunk.delta is not None:
105
- content = chunk.delta.text if hasattr(chunk.delta, "text") else ""
106
- # Assuming content needs to be aggregated before processing
107
- result_content.append(content if len(content) > 0 else "")
108
-
109
- if hasattr(chunk, "message") and hasattr(chunk.message, "usage"):
110
- input_tokens += (
111
- chunk.message.usage.input_tokens
112
- if hasattr(chunk.message.usage, "input_tokens")
113
- else 0
114
- )
115
- output_tokens += (
116
- chunk.message.usage.output_tokens
117
- if hasattr(chunk.message.usage, "output_tokens")
118
- else 0
119
- )
120
-
121
- # Assuming span.add_event is part of a larger logging or event system
122
- # Add event for each chunk of content
123
- if content:
124
- set_event_completion_chunk(span, "".join(content))
125
-
126
- # Assuming this is part of a generator, yield chunk or aggregated content
127
- yield content
128
- finally:
129
-
130
- # Finalize span after processing all chunks
131
- span.add_event(Event.STREAM_END.value)
132
- set_usage_attributes(
133
- span, {"input_tokens": input_tokens, "output_tokens": output_tokens}
134
- )
135
- completion = [{"role": "assistant", "content": "".join(result_content)}]
136
- set_event_completion(span, completion)
137
-
138
- span.set_status(StatusCode.OK)
139
- span.end()
140
-
88
+ @silently_fail
141
89
  def set_response_attributes(result, span, kwargs):
142
90
  if not is_streaming(kwargs):
143
91
  if hasattr(result, "content") and result.content is not None:
@@ -174,7 +122,7 @@ def messages_create(original_method, version, tracer):
174
122
  span.end()
175
123
  return result
176
124
  else:
177
- return handle_streaming_response(result, span)
125
+ return StreamWrapper(result, span)
178
126
 
179
127
  # return the wrapped method
180
128
  return traced_method
@@ -407,15 +407,8 @@ def chat_stream(original_method, version, tracer):
407
407
  try:
408
408
  # Attempt to call the original method
409
409
  result = wrapped(*args, **kwargs)
410
- span.add_event(Event.STREAM_START.value)
411
410
  try:
412
411
  for event in result:
413
- if hasattr(event, "text") and event.text is not None:
414
- content = event.text
415
- else:
416
- content = ""
417
- set_event_completion_chunk(span, "".join(content))
418
-
419
412
  if (
420
413
  hasattr(event, "finish_reason")
421
414
  and event.finish_reason == "COMPLETE"
@@ -496,15 +489,14 @@ def chat_stream(original_method, version, tracer):
496
489
  (usage.input_tokens or 0)
497
490
  + (usage.output_tokens or 0),
498
491
  )
499
-
500
- span.set_attribute(
501
- "search_units",
502
- usage.search_units or 0,
503
- )
492
+ if usage.search_units is not None:
493
+ span.set_attribute(
494
+ "search_units",
495
+ usage.search_units or 0,
496
+ )
504
497
 
505
498
  yield event
506
499
  finally:
507
- span.add_event(Event.STREAM_END.value)
508
500
  span.set_status(StatusCode.OK)
509
501
  span.end()
510
502
 
@@ -66,8 +66,14 @@ def patch_module_classes(
66
66
  if name.startswith("_") or name in exclude_classes:
67
67
  continue
68
68
  # loop through all public methods of the class
69
- for method_name, method in inspect.getmembers(obj, predicate=inspect.isfunction):
70
- if method_name in exclude_methods or method.__qualname__.split('.')[0] != name:
69
+ for method_name, method in inspect.getmembers(
70
+ obj, predicate=inspect.isfunction
71
+ ):
72
+ if (
73
+ method_name in exclude_methods
74
+ or method.__qualname__.split(".")[0] != name
75
+ or method_name.startswith("_")
76
+ ):
71
77
  continue
72
78
  try:
73
79
  method_path = f"{name}.{method_name}"
@@ -108,6 +114,9 @@ class LangchainCoreInstrumentation(BaseInstrumentor):
108
114
  "format",
109
115
  "format_messages",
110
116
  "format_prompt",
117
+ "__or__",
118
+ "__init__",
119
+ "__repr__",
111
120
  ]
112
121
  exclude_classes = [
113
122
  "BaseChatPromptTemplate",
@@ -125,7 +134,13 @@ class LangchainCoreInstrumentation(BaseInstrumentor):
125
134
  modules_to_patch = [
126
135
  ("langchain_core.retrievers", "retriever", generic_patch, True, True),
127
136
  ("langchain_core.prompts.chat", "prompt", generic_patch, True, True),
128
- ("langchain_core.language_models.llms", "generate", generic_patch, True, True),
137
+ (
138
+ "langchain_core.language_models.llms",
139
+ "generate",
140
+ generic_patch,
141
+ True,
142
+ True,
143
+ ),
129
144
  ("langchain_core.runnables.base", "runnable", runnable_patch, True, True),
130
145
  (
131
146
  "langchain_core.runnables.passthrough",
@@ -1,6 +1,7 @@
1
1
  from langtrace_python_sdk.constants.instrumentation.ollama import APIS
2
2
  from langtrace_python_sdk.utils import set_span_attribute
3
3
  from langtrace_python_sdk.utils.llm import (
4
+ StreamWrapper,
4
5
  get_extra_attributes,
5
6
  get_langtrace_attributes,
6
7
  get_llm_request_attributes,
@@ -16,9 +17,10 @@ from opentelemetry.trace import SpanKind
16
17
  import json
17
18
  from opentelemetry.trace.status import Status, StatusCode
18
19
  from langtrace.trace_attributes import SpanAttributes
20
+ from opentelemetry.trace import Tracer
19
21
 
20
22
 
21
- def generic_patch(operation_name, version, tracer):
23
+ def generic_patch(operation_name, version, tracer: Tracer):
22
24
  def traced_method(wrapped, instance, args, kwargs):
23
25
  api = APIS[operation_name]
24
26
  service_provider = SERVICE_PROVIDERS["OLLAMA"]
@@ -35,36 +37,29 @@ def generic_patch(operation_name, version, tracer):
35
37
  }
36
38
 
37
39
  attributes = LLMSpanAttributes(**span_attributes)
38
- with tracer.start_as_current_span(
39
- name=get_span_name(f'ollama.{api["METHOD"]}'), kind=SpanKind.CLIENT
40
- ) as span:
41
- _set_input_attributes(span, kwargs, attributes)
42
-
43
- try:
44
- result = wrapped(*args, **kwargs)
45
- if result:
46
- if span.is_recording():
47
-
48
- if kwargs.get("stream"):
49
- return _handle_streaming_response(
50
- span, result, api["METHOD"]
51
- )
52
40
 
53
- _set_response_attributes(span, result)
54
- span.set_status(Status(StatusCode.OK))
41
+ span = tracer.start_span(
42
+ name=get_span_name(f'ollama.{api["METHOD"]}'), kind=SpanKind.CLIENT
43
+ )
44
+ _set_input_attributes(span, kwargs, attributes)
55
45
 
56
- span.end()
57
- return result
46
+ try:
47
+ result = wrapped(*args, **kwargs)
48
+ if kwargs.get("stream"):
49
+ return StreamWrapper(result, span)
50
+ else:
51
+ _set_response_attributes(span, result)
52
+ return result
58
53
 
59
- except Exception as err:
60
- # Record the exception in the span
61
- span.record_exception(err)
54
+ except Exception as err:
55
+ # Record the exception in the span
56
+ span.record_exception(err)
62
57
 
63
- # Set the span status to indicate an error
64
- span.set_status(Status(StatusCode.ERROR, str(err)))
58
+ # Set the span status to indicate an error
59
+ span.set_status(Status(StatusCode.ERROR, str(err)))
65
60
 
66
- # Reraise the exception to ensure it's not swallowed
67
- raise
61
+ # Reraise the exception to ensure it's not swallowed
62
+ raise
68
63
 
69
64
  return traced_method
70
65
 
@@ -82,30 +77,28 @@ def ageneric_patch(operation_name, version, tracer):
82
77
  **get_extra_attributes(),
83
78
  }
84
79
  attributes = LLMSpanAttributes(**span_attributes)
85
- with tracer.start_as_current_span(api["METHOD"], kind=SpanKind.CLIENT) as span:
86
- _set_input_attributes(span, kwargs, attributes)
87
- try:
88
- result = await wrapped(*args, **kwargs)
89
- if result:
90
- if span.is_recording():
91
- if kwargs.get("stream"):
92
- return _ahandle_streaming_response(
93
- span, result, api["METHOD"]
94
- )
95
-
96
- _set_response_attributes(span, result)
97
- span.set_status(Status(StatusCode.OK))
98
- span.end()
99
- return result
100
- except Exception as err:
101
- # Record the exception in the span
102
- span.record_exception(err)
103
-
104
- # Set the span status to indicate an error
105
- span.set_status(Status(StatusCode.ERROR, str(err)))
106
-
107
- # Reraise the exception to ensure it's not swallowed
108
- raise
80
+ span = tracer.start_span(
81
+ name=get_span_name(f'ollama.{api["METHOD"]}'), kind=SpanKind.CLIENT
82
+ )
83
+
84
+ _set_input_attributes(span, kwargs, attributes)
85
+ try:
86
+ result = await wrapped(*args, **kwargs)
87
+ if kwargs.get("stream"):
88
+ return StreamWrapper(span, result)
89
+ else:
90
+ _set_response_attributes(span, result)
91
+ span.end()
92
+ return result
93
+ except Exception as err:
94
+ # Record the exception in the span
95
+ span.record_exception(err)
96
+
97
+ # Set the span status to indicate an error
98
+ span.set_status(Status(StatusCode.ERROR, str(err)))
99
+
100
+ # Reraise the exception to ensure it's not swallowed
101
+ raise
109
102
 
110
103
  return traced_method
111
104
 
@@ -162,63 +155,3 @@ def _set_input_attributes(span, kwargs, attributes):
162
155
  SpanAttributes.LLM_PRESENCE_PENALTY,
163
156
  options.get("presence_penalty"),
164
157
  )
165
-
166
-
167
- def _handle_streaming_response(span, response, api):
168
- accumulated_tokens = None
169
- if api == "chat":
170
- accumulated_tokens = {"message": {"content": "", "role": ""}}
171
- if api == "completion" or api == "generate":
172
- accumulated_tokens = {"response": ""}
173
- span.add_event(Event.STREAM_START.value)
174
- try:
175
- for chunk in response:
176
- content = None
177
- if api == "chat":
178
- content = chunk["message"]["content"]
179
- accumulated_tokens["message"]["content"] += chunk["message"]["content"]
180
- accumulated_tokens["message"]["role"] = chunk["message"]["role"]
181
- if api == "generate":
182
- content = chunk["response"]
183
- accumulated_tokens["response"] += chunk["response"]
184
-
185
- set_event_completion_chunk(span, content)
186
-
187
- _set_response_attributes(span, chunk | accumulated_tokens)
188
- finally:
189
- # Finalize span after processing all chunks
190
- span.add_event(Event.STREAM_END.value)
191
- span.set_status(StatusCode.OK)
192
- span.end()
193
-
194
- return response
195
-
196
-
197
- async def _ahandle_streaming_response(span, response, api):
198
- accumulated_tokens = None
199
- if api == "chat":
200
- accumulated_tokens = {"message": {"content": "", "role": ""}}
201
- if api == "completion" or api == "generate":
202
- accumulated_tokens = {"response": ""}
203
-
204
- span.add_event(Event.STREAM_START.value)
205
- try:
206
- async for chunk in response:
207
- content = None
208
- if api == "chat":
209
- content = chunk["message"]["content"]
210
- accumulated_tokens["message"]["content"] += chunk["message"]["content"]
211
- accumulated_tokens["message"]["role"] = chunk["message"]["role"]
212
- if api == "generate":
213
- content = chunk["response"]
214
- accumulated_tokens["response"] += chunk["response"]
215
-
216
- set_event_completion_chunk(span, content)
217
- _set_response_attributes(span, chunk | accumulated_tokens)
218
- finally:
219
- # Finalize span after processing all chunks
220
- span.add_event(Event.STREAM_END.value)
221
- span.set_status(StatusCode.OK)
222
- span.end()
223
-
224
- return response
@@ -235,7 +235,7 @@ class StreamWrapper:
235
235
  span: Span
236
236
 
237
237
  def __init__(
238
- self, stream, span, prompt_tokens, function_call=False, tool_calls=False
238
+ self, stream, span, prompt_tokens=0, function_call=False, tool_calls=False
239
239
  ):
240
240
  self.stream = stream
241
241
  self.span = span
@@ -245,16 +245,20 @@ class StreamWrapper:
245
245
  self.result_content = []
246
246
  self.completion_tokens = 0
247
247
  self._span_started = False
248
+ self._response_model = None
248
249
  self.setup()
249
250
 
250
251
  def setup(self):
251
252
  if not self._span_started:
252
- self.span.add_event(Event.STREAM_START.value)
253
253
  self._span_started = True
254
254
 
255
255
  def cleanup(self):
256
256
  if self._span_started:
257
- self.span.add_event(Event.STREAM_END.value)
257
+ set_span_attribute(
258
+ self.span,
259
+ SpanAttributes.LLM_RESPONSE_MODEL,
260
+ self._response_model,
261
+ )
258
262
  set_span_attribute(
259
263
  self.span,
260
264
  SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
@@ -279,7 +283,6 @@ class StreamWrapper:
279
283
  }
280
284
  ],
281
285
  )
282
-
283
286
  self.span.set_status(StatusCode.OK)
284
287
  self.span.end()
285
288
  self._span_started = False
@@ -322,21 +325,26 @@ class StreamWrapper:
322
325
  self.cleanup()
323
326
  raise StopAsyncIteration
324
327
 
325
- def process_chunk(self, chunk):
328
+ def set_response_model(self, chunk):
329
+ if self._response_model:
330
+ return
331
+
332
+ # OpenAI response model is set on all chunks
326
333
  if hasattr(chunk, "model") and chunk.model is not None:
327
- set_span_attribute(
328
- self.span,
329
- SpanAttributes.LLM_RESPONSE_MODEL,
330
- chunk.model,
331
- )
334
+ self._response_model = chunk.model
335
+
336
+ # Anthropic response model is set on the first chunk message
337
+ if hasattr(chunk, "message") and chunk.message is not None:
338
+ if hasattr(chunk.message, "model") and chunk.message.model is not None:
339
+ self._response_model = chunk.message.model
332
340
 
341
+ def build_streaming_response(self, chunk):
342
+ content = []
343
+ # OpenAI
333
344
  if hasattr(chunk, "choices") and chunk.choices is not None:
334
- content = []
335
345
  if not self.function_call and not self.tool_calls:
336
346
  for choice in chunk.choices:
337
347
  if choice.delta and choice.delta.content is not None:
338
- token_counts = estimate_tokens(choice.delta.content)
339
- self.completion_tokens += token_counts
340
348
  content = [choice.delta.content]
341
349
  elif self.function_call:
342
350
  for choice in chunk.choices:
@@ -345,10 +353,6 @@ class StreamWrapper:
345
353
  and choice.delta.function_call is not None
346
354
  and choice.delta.function_call.arguments is not None
347
355
  ):
348
- token_counts = estimate_tokens(
349
- choice.delta.function_call.arguments
350
- )
351
- self.completion_tokens += token_counts
352
356
  content = [choice.delta.function_call.arguments]
353
357
  elif self.tool_calls:
354
358
  for choice in chunk.choices:
@@ -361,30 +365,52 @@ class StreamWrapper:
361
365
  and tool_call.function is not None
362
366
  and tool_call.function.arguments is not None
363
367
  ):
364
- token_counts = estimate_tokens(
365
- tool_call.function.arguments
366
- )
367
- self.completion_tokens += token_counts
368
368
  content.append(tool_call.function.arguments)
369
- set_event_completion_chunk(
370
- self.span,
371
- "".join(content) if len(content) > 0 and content[0] is not None else "",
372
- )
373
- if content:
374
- self.result_content.append(content[0])
375
369
 
376
- if hasattr(chunk, "text"):
377
- token_counts = estimate_tokens(chunk.text)
378
- self.completion_tokens += token_counts
370
+ # VertexAI
371
+ if hasattr(chunk, "text") and chunk.text is not None:
379
372
  content = [chunk.text]
380
- set_event_completion_chunk(
381
- self.span,
382
- "".join(content) if len(content) > 0 and content[0] is not None else "",
383
- )
384
373
 
385
- if content:
386
- self.result_content.append(content[0])
374
+ # Anthropic
375
+ if hasattr(chunk, "delta") and chunk.delta is not None:
376
+ content = [chunk.delta.text] if hasattr(chunk.delta, "text") else []
377
+
378
+ if isinstance(chunk, dict):
379
+ if "message" in chunk:
380
+ if "content" in chunk["message"]:
381
+ content = [chunk["message"]["content"]]
382
+ if content:
383
+ self.result_content.append(content[0])
384
+
385
+ def set_usage_attributes(self, chunk):
386
+
387
+ # Anthropic & OpenAI
388
+ if hasattr(chunk, "type") and chunk.type == "message_start":
389
+ self.prompt_tokens = chunk.message.usage.input_tokens
387
390
 
391
+ if hasattr(chunk, "usage") and chunk.usage is not None:
392
+ if hasattr(chunk.usage, "output_tokens"):
393
+ self.completion_tokens = chunk.usage.output_tokens
394
+
395
+ if hasattr(chunk.usage, "prompt_tokens"):
396
+ self.prompt_tokens = chunk.usage.prompt_tokens
397
+
398
+ if hasattr(chunk.usage, "completion_tokens"):
399
+ self.completion_tokens = chunk.usage.completion_tokens
400
+
401
+ # VertexAI
388
402
  if hasattr(chunk, "usage_metadata"):
389
403
  self.completion_tokens = chunk.usage_metadata.candidates_token_count
390
404
  self.prompt_tokens = chunk.usage_metadata.prompt_token_count
405
+
406
+ # Ollama
407
+ if isinstance(chunk, dict):
408
+ if "prompt_eval_count" in chunk:
409
+ self.prompt_tokens = chunk["prompt_eval_count"]
410
+ if "eval_count" in chunk:
411
+ self.completion_tokens = chunk["eval_count"]
412
+
413
+ def process_chunk(self, chunk):
414
+ self.set_response_model(chunk=chunk)
415
+ self.build_streaming_response(chunk=chunk)
416
+ self.set_usage_attributes(chunk=chunk)
@@ -1 +1 @@
1
- __version__ = "2.2.20"
1
+ __version__ = "2.2.22"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langtrace-python-sdk
3
- Version: 2.2.20
3
+ Version: 2.2.22
4
4
  Summary: Python SDK for LangTrace
5
5
  Project-URL: Homepage, https://github.com/Scale3-Labs/langtrace-python-sdk
6
6
  Author-email: Scale3 Labs <engineering@scale3labs.com>
@@ -9,7 +9,18 @@ examples/cohere_example/chat_stream.py,sha256=BvhUgBEuyMhyzRZ_2i_SBvO9Ndf0b7-aRD
9
9
  examples/cohere_example/embed.py,sha256=p9BJvOg09JVb8BfTCb63v3uh_wOsi_OyrCAJdXXrE6E,496
10
10
  examples/cohere_example/rerank.py,sha256=e7OU0A2FzfiQDuOmCy3Kg5LLNYGRmRIK5LqeLnTWlP4,1118
11
11
  examples/cohere_example/tools.py,sha256=a5uvS058tcwU6PJbF9EDO6LPVmPj2LoW4Vn8Web3Iq8,1656
12
+ examples/crewai_example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  examples/crewai_example/basic.py,sha256=PBu4f8yQfZO1L_22UDm_ReU9lnEcycjZcGuy5UpgDJM,1948
14
+ examples/crewai_example/simple_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ examples/crewai_example/simple_agent/agents.py,sha256=Lq_5zeftH7TV-dsDRxfxi6sfE3t2mpQ4WulFh1FAFfs,1329
16
+ examples/crewai_example/simple_agent/main.py,sha256=MJq3Xd24RIUHaSDAUpQtZb5ht5YUu5xPwgYxEY4moJk,1223
17
+ examples/crewai_example/simple_agent/tasks.py,sha256=bfx_vP59akrrjNRUbVT7GDzdZlEqTQXAtGcCOCk_UFY,796
18
+ examples/crewai_example/trip_planner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ examples/crewai_example/trip_planner/agents.py,sha256=IpzLbgrmdMa-9mj6awjJUcOumX8Cg71OzRQ1L8fIog4,2378
20
+ examples/crewai_example/trip_planner/main.py,sha256=Ju001KRksiH1Svu6p83CfTybAPq9hruJVH9p3BmtG_c,2623
21
+ examples/crewai_example/trip_planner/tasks.py,sha256=ZGRaTAgkA66IN7q9EYbJqM8xWhUTxcF4ynnqTyBcSL4,5667
22
+ examples/crewai_example/trip_planner/tools/calculator.py,sha256=bMfxJDAwbn6D26pe880S4BB3rcFeyvEyb15QR00T8kI,522
23
+ examples/crewai_example/trip_planner/tools/search_tools.py,sha256=p8qZe_bi45OjBwiwwrH0lhTaQI_ZiLThSTEEN5dWxF0,2700
13
24
  examples/dspy_example/math_problems_cot.py,sha256=Z98nB6myt8WJse2dWS6Ap7CFUhC27lBNb37R1Gg80VQ,1282
14
25
  examples/dspy_example/math_problems_cot_parallel.py,sha256=5clw-IIVA0mWm0N0xWNDMQaSY07YVYW8R1mcyCJJ1_8,1764
15
26
  examples/dspy_example/program_of_thought_basic.py,sha256=oEbtJdeKENMUbex25-zyStWwurRWW6OdP0KDs-jUkko,984
@@ -63,7 +74,7 @@ examples/weaviate_example/__init__.py,sha256=8JMDBsRSEV10HfTd-YC7xb4txBjD3la56sn
63
74
  examples/weaviate_example/query_text.py,sha256=sG8O-bXQpflBAiYpgE_M2X7GcHUlZNgl_wJW8_h-W6Q,127024
64
75
  langtrace_python_sdk/__init__.py,sha256=VZM6i71NR7pBQK6XvJWRelknuTYUhqwqE7PlicKa5Wg,1166
65
76
  langtrace_python_sdk/langtrace.py,sha256=hh3okJYyxXvC9TMm_vaFOGz-5TxxJp1zQDmZmy63aRY,7813
66
- langtrace_python_sdk/version.py,sha256=kOrmSTcDQ0cSVmIBVsgETbkmkVVEbLmdHPzkY_Y2PbA,23
77
+ langtrace_python_sdk/version.py,sha256=pX8tRH0Idy_gR6E_Dzk1LImPGfPmc0Pkc62fJ617lUk,23
67
78
  langtrace_python_sdk/constants/__init__.py,sha256=P8QvYwt5czUNDZsKS64vxm9Dc41ptGbuF1TFtAF6nv4,44
68
79
  langtrace_python_sdk/constants/exporter/langtrace_exporter.py,sha256=5MNjnAOg-4am78J3gVMH6FSwq5N8TOj72ugkhsw4vi0,46
69
80
  langtrace_python_sdk/constants/instrumentation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -85,13 +96,13 @@ langtrace_python_sdk/extensions/langtrace_filesystem.py,sha256=34fZutG28EJ66l67O
85
96
  langtrace_python_sdk/instrumentation/__init__.py,sha256=yJd3aGu4kPfm2h6oe6kiCWvzTF9awpC1UztjXF9WSO4,1391
86
97
  langtrace_python_sdk/instrumentation/anthropic/__init__.py,sha256=donrurJAGYlxrSRA3BIf76jGeUcAx9Tq8CVpah68S0Y,101
87
98
  langtrace_python_sdk/instrumentation/anthropic/instrumentation.py,sha256=-srgE8qumAn0ulQYZxMa8ch-9IBH0XgBW_rfEnGk6LI,1684
88
- langtrace_python_sdk/instrumentation/anthropic/patch.py,sha256=cVVXYFzIIHkaEzdVvq-YrEgf7-RaaKlXb9CH-9RU9vg,6858
99
+ langtrace_python_sdk/instrumentation/anthropic/patch.py,sha256=i_94sJXURVgKIUVKJ3mMqZydWtlv5BlIRqQEk8utrL4,4546
89
100
  langtrace_python_sdk/instrumentation/chroma/__init__.py,sha256=pNZ5UO8Q-d5VkXSobBf79reB6AmEl_usnnTp5Itv818,95
90
101
  langtrace_python_sdk/instrumentation/chroma/instrumentation.py,sha256=nT6PS6bsrIOO9kLV5GuUeRjMe6THHHAZGvqWBP1dYog,1807
91
102
  langtrace_python_sdk/instrumentation/chroma/patch.py,sha256=1jCbyum11ifbQFLO43eg0yW33Yc7NI_fwhRf1gspHcM,9087
92
103
  langtrace_python_sdk/instrumentation/cohere/__init__.py,sha256=sGUSLdTUyYf36Tm6L5jQflhzCqvmWrhnBOMYHjvp6Hs,95
93
104
  langtrace_python_sdk/instrumentation/cohere/instrumentation.py,sha256=YQFHZIBd7SSPD4b6Va-ZR0thf_AuBCqj5yzHLHJVWnM,2121
94
- langtrace_python_sdk/instrumentation/cohere/patch.py,sha256=YPFgNVX_DNoL4FCp5aFbcDP8z8nQTp7v78dA28XRioA,21280
105
+ langtrace_python_sdk/instrumentation/cohere/patch.py,sha256=Mv9mk3HBdMYsartyYzqsGPkY791LkEoigyShaQBM0xk,21004
95
106
  langtrace_python_sdk/instrumentation/crewai/__init__.py,sha256=_UBKfvQv7l0g2_wnmA5F6CdSAFH0atNOVPd49zsN3aM,88
96
107
  langtrace_python_sdk/instrumentation/crewai/instrumentation.py,sha256=q07x6nnig9JPxDT6ZylyIShfXWjNafKBetnNcA1UdEU,1836
97
108
  langtrace_python_sdk/instrumentation/crewai/patch.py,sha256=4W7jEIJX4SJNViPlFTBJdSkvvPVJoI76Bb5DX673Ql8,6203
@@ -111,7 +122,7 @@ langtrace_python_sdk/instrumentation/langchain_community/__init__.py,sha256=mj5R
111
122
  langtrace_python_sdk/instrumentation/langchain_community/instrumentation.py,sha256=TmMRXcaiMR99Qg7r7pT1XunCr_GOQl_Csr6leSKYyTQ,5350
112
123
  langtrace_python_sdk/instrumentation/langchain_community/patch.py,sha256=ssNM9NyRtWiGgqaOZ9zK3R-VDYx_VwNmPq1RAZ-4Wzg,5232
113
124
  langtrace_python_sdk/instrumentation/langchain_core/__init__.py,sha256=kumE_reeqgM-ZvEZ6-XxyT-F-HAdKq_v_PKvsLb4EZQ,110
114
- langtrace_python_sdk/instrumentation/langchain_core/instrumentation.py,sha256=bTZOOr049GQSQqUnLhFtQIFSXrLs7j_3uHP5IN-6rJ0,6013
125
+ langtrace_python_sdk/instrumentation/langchain_core/instrumentation.py,sha256=uiRGS3RLZGe442kzEyW2b9xJitb0EmZ8zs6Sj-cyol4,6295
115
126
  langtrace_python_sdk/instrumentation/langchain_core/patch.py,sha256=SvYTuYaVtKzoqmIz-_FIZbTCT00CItZOwjWvEOCwfDA,9552
116
127
  langtrace_python_sdk/instrumentation/langgraph/__init__.py,sha256=eitlHloY-aZ4ZuIEJx61AadEA3G7siyecP-V-lziAr8,101
117
128
  langtrace_python_sdk/instrumentation/langgraph/instrumentation.py,sha256=SUZZhWSIbcfsF1S5NtEqW8QzkRM_pKAuXB7pwk5tsOU,2526
@@ -121,7 +132,7 @@ langtrace_python_sdk/instrumentation/llamaindex/instrumentation.py,sha256=8iAg-O
121
132
  langtrace_python_sdk/instrumentation/llamaindex/patch.py,sha256=548hzPyT_k-2wmt9AArv4JzTT4j4AGKJq5Ar2bWv7o8,4615
122
133
  langtrace_python_sdk/instrumentation/ollama/__init__.py,sha256=g2zJsXnDHinXPzTc-WxDeTtHmr9gmAj3K6l_00kP8c8,82
123
134
  langtrace_python_sdk/instrumentation/ollama/instrumentation.py,sha256=jdsvkqUJAAUNLVPtAkn_rG26HXetVQXWtjn4a6eWZro,2029
124
- langtrace_python_sdk/instrumentation/ollama/patch.py,sha256=AYIT8N6LXTgd5HqjuDOtKSfqD-24a1_5bF3pd_R3HGM,8128
135
+ langtrace_python_sdk/instrumentation/ollama/patch.py,sha256=5Y_pPVh1Pt8BcxyCiJWex3oNqYTcuOo5udh1-jNsCO0,5407
125
136
  langtrace_python_sdk/instrumentation/openai/__init__.py,sha256=VPHRNCQEdkizIVP2d0Uw_a7t8XOTSTprEIB8oboJFbs,95
126
137
  langtrace_python_sdk/instrumentation/openai/instrumentation.py,sha256=A0BJHRLcZ74TNVg6I0I9M5YWvSpAtXwMmME6N5CEQ_M,2945
127
138
  langtrace_python_sdk/instrumentation/openai/patch.py,sha256=4GCYJzZdUBopEDinpTwRBFf-Enb0hdNO16LiiMKqqvY,24226
@@ -140,7 +151,7 @@ langtrace_python_sdk/instrumentation/weaviate/patch.py,sha256=qX4VQqScH2kygn5lQa
140
151
  langtrace_python_sdk/types/__init__.py,sha256=KDW6S74FDxpeBa9xoH5zVEYfmRjccCCHzlW7lTJg1TA,3194
141
152
  langtrace_python_sdk/utils/__init__.py,sha256=SwYYPIh2AzEpI3zbwowQU2zJlwRwoVdWOCcrAKnkI9g,873
142
153
  langtrace_python_sdk/utils/langtrace_sampler.py,sha256=BupNndHbU9IL_wGleKetz8FdcveqHMBVz1bfKTTW80w,1753
143
- langtrace_python_sdk/utils/llm.py,sha256=zvVhSE4TkVQCqzfnPtGKXbct66Hj9ZFJ6N2fVbNfvbw,13129
154
+ langtrace_python_sdk/utils/llm.py,sha256=hDOPyUp3kSL1g92uiHySARzBmVRMy3umGizz91vTDSI,13940
144
155
  langtrace_python_sdk/utils/misc.py,sha256=CD9NWRLxLpFd0YwlHJqzlpFNedXVWtAKGOjQWnDCo8k,838
145
156
  langtrace_python_sdk/utils/prompt_registry.py,sha256=n5dQMVLBw8aJZY8Utvf67bncc25ELf6AH9BYw8_hSzo,2619
146
157
  langtrace_python_sdk/utils/sdk_version_checker.py,sha256=FzjIWZjn53cX0LEVPdipQd1fO9lG8iGVUEVUs9Hyk6M,1713
@@ -151,14 +162,14 @@ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
162
  tests/conftest.py,sha256=0Jo6iCZTXbdvyJVhG9UpYGkLabL75378oauCzmt-Sa8,603
152
163
  tests/utils.py,sha256=8ZBYvxBH6PynipT1sqenfyjTGLhEV7SORQH1NJjnpsM,2500
153
164
  tests/anthropic/conftest.py,sha256=ht1zF3q4Xb1UiZ3BL_L-CQ3YWrd52Dqb4WTZ3f-GdG4,739
154
- tests/anthropic/test_anthropic.py,sha256=lNalVzRk52smmwflFT8tFu-Jm9y56dyEWWgWlXarJeY,2942
165
+ tests/anthropic/test_anthropic.py,sha256=5c2UELkvrVHb07t7wyHvxxAOn_M0-L8wuVT4z1FMHUU,2863
155
166
  tests/anthropic/cassettes/test_anthropic.yaml,sha256=z7YAqA_BBgI-hw7uyVMdLoIZdBYhKwl9cuTzUu9nAZs,2332
156
167
  tests/anthropic/cassettes/test_anthropic_streaming.yaml,sha256=D0w4-6dfsgrhbNLJEj8gZBV0yXfrAfA9u90Yu8Ac-TY,11675
157
168
  tests/anthropic/cassettes/test_async_anthropic_streaming.yaml,sha256=hQZPY2vwBaW3BWllLd0lcGQ73DjA8C3Ips1Hx9pA-ao,8373
158
169
  tests/chroma/conftest.py,sha256=kqb4VydCnlVpkvBX5Bu-pfnVS-ZZfju9cp6vq76tkJI,309
159
170
  tests/chroma/test_chroma.py,sha256=-KJHunvvVi1OoKKOcKCeHO1s399Gm9vJfd-EzgllQmk,1220
160
171
  tests/cohere/conftest.py,sha256=jBbyg-tut-ZJN5_5D64sGmaPIhT_nQQQAiW43kl5Rz4,621
161
- tests/cohere/test_cohere_chat.py,sha256=KJBWGXPPeTCKdch8O63-7IadmMvdZZrsUPAx0Z0aH60,4463
172
+ tests/cohere/test_cohere_chat.py,sha256=2su_sOyxr9HinhfNY51MbriVG4JK711FSFJOWHzGQTY,4377
162
173
  tests/cohere/test_cohere_embed.py,sha256=Zx2gcc7YSp7rEOWelFVcRkL8V93wGP8ZFG7exrgJCJY,1500
163
174
  tests/cohere/test_cohere_rerank.py,sha256=RC52HVdM4ZYbLDcT1hnWNKDtbNJeDvOFIllULhUN4EA,2522
164
175
  tests/cohere/cassettes/test_cohere_chat.yaml,sha256=iQIhJwmWe2AQDmdguL6L0SZOPMD6B3mhDVUKPSLodA4,3271
@@ -175,7 +186,7 @@ tests/langchain/conftest.py,sha256=f29apdevxg7AM0mPQ1LoEd-yStGruqGLTQUp29heLJo,1
175
186
  tests/langchain/test_langchain.py,sha256=BYAQY3ShJIVnLS1b-TkJ4wMKhbiPV-E4-ISTjGyPxhM,646
176
187
  tests/langchain/cassettes/test_langchain.yaml,sha256=KPBTVIYMUPFaSNpwrTDgWzsu4p3hHj_yNDoudDa-Jis,3755
177
188
  tests/openai/conftest.py,sha256=BkehS6heg-O91Nzoc4546OSiAzy8KgSgk7VCO3A11zM,700
178
- tests/openai/test_chat_completion.py,sha256=I3XqMhMYsnSE8581UN_PGL_Y3A_kwa2mhb7zhxv52NQ,5081
189
+ tests/openai/test_chat_completion.py,sha256=vkUtyF01U41J2TR8O8ygvoRXPniXI734QuQ79DgImOg,4863
179
190
  tests/openai/test_embeddings.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
180
191
  tests/openai/test_image_generation.py,sha256=u6lJCDPs2kZ_MfoaEXEszU_VTOQr7xGK7ki93E2e-wY,4902
181
192
  tests/openai/cassettes/test_async_chat_completion_streaming.yaml,sha256=z4LE2pknHrfNm9D2pKH1F_cHDAG8LIVp4SnzJBUd4t4,6719
@@ -189,8 +200,8 @@ tests/pinecone/cassettes/test_query.yaml,sha256=b5v9G3ssUy00oG63PlFUR3JErF2Js-5A
189
200
  tests/pinecone/cassettes/test_upsert.yaml,sha256=neWmQ1v3d03V8WoLl8FoFeeCYImb8pxlJBWnFd_lITU,38607
190
201
  tests/qdrant/conftest.py,sha256=9n0uHxxIjWk9fbYc4bx-uP8lSAgLBVx-cV9UjnsyCHM,381
191
202
  tests/qdrant/test_qdrant.py,sha256=pzjAjVY2kmsmGfrI2Gs2xrolfuaNHz7l1fqGQCjp5_o,3353
192
- langtrace_python_sdk-2.2.20.dist-info/METADATA,sha256=RiiPFYBgE6nH5CrpRVOgIiTw94B1xHtzTeVGYwoowmk,14705
193
- langtrace_python_sdk-2.2.20.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
194
- langtrace_python_sdk-2.2.20.dist-info/entry_points.txt,sha256=1_b9-qvf2fE7uQNZcbUei9vLpFZBbbh9LrtGw95ssAo,70
195
- langtrace_python_sdk-2.2.20.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
196
- langtrace_python_sdk-2.2.20.dist-info/RECORD,,
203
+ langtrace_python_sdk-2.2.22.dist-info/METADATA,sha256=W2LdIpHk8FHlmtYVmgri3pLmJDK9D2Dq9mpBq15XYaw,14705
204
+ langtrace_python_sdk-2.2.22.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
205
+ langtrace_python_sdk-2.2.22.dist-info/entry_points.txt,sha256=1_b9-qvf2fE7uQNZcbUei9vLpFZBbbh9LrtGw95ssAo,70
206
+ langtrace_python_sdk-2.2.22.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
207
+ langtrace_python_sdk-2.2.22.dist-info/RECORD,,
@@ -82,8 +82,4 @@ def test_anthropic_streaming(anthropic_client, exporter):
82
82
  assert attributes.get(SpanAttributes.LLM_REQUEST_MODEL) == llm_model_value
83
83
  assert attributes.get(SpanAttributes.LLM_IS_STREAMING) is True
84
84
 
85
- events = streaming_span.events
86
-
87
- assert len(events) - 4 == chunk_count
88
-
89
85
  assert_token_count(attributes)
@@ -112,7 +112,5 @@ def test_cohere_chat_streaming(cohere_client, exporter):
112
112
  events = cohere_span.events
113
113
  assert_prompt_in_events(events)
114
114
  assert_completion_in_events(events)
115
- assert events[-1].name == "stream.end"
116
- assert len(events) - 4 == chunks_count
117
115
 
118
116
  assert_token_count(attributes)
@@ -83,9 +83,6 @@ def test_chat_completion_streaming(exporter, openai_client):
83
83
  assert_completion_in_events(streaming_span.events)
84
84
  assert attributes.get(SpanAttributes.LLM_IS_STREAMING) is True
85
85
 
86
- events = streaming_span.events
87
- assert len(events) - 4 == chunk_count # -2 for start and end events
88
-
89
86
  assert_token_count(attributes)
90
87
 
91
88
 
@@ -126,7 +123,4 @@ async def test_async_chat_completion_streaming(exporter, async_openai_client):
126
123
  assert_completion_in_events(streaming_span.events)
127
124
  assert attributes.get(SpanAttributes.LLM_IS_STREAMING) is True
128
125
 
129
- events = streaming_span.events
130
- assert len(events) - 4 == chunk_count # -2 for start and end events
131
-
132
126
  assert_token_count(attributes)