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.
- examples/crewai_example/__init__.py +0 -0
- examples/crewai_example/simple_agent/__init__.py +0 -0
- examples/crewai_example/simple_agent/agents.py +32 -0
- examples/crewai_example/simple_agent/main.py +48 -0
- examples/crewai_example/simple_agent/tasks.py +21 -0
- examples/crewai_example/trip_planner/__init__.py +0 -0
- examples/crewai_example/trip_planner/agents.py +54 -0
- examples/crewai_example/trip_planner/main.py +96 -0
- examples/crewai_example/trip_planner/tasks.py +120 -0
- examples/crewai_example/trip_planner/tools/calculator.py +16 -0
- examples/crewai_example/trip_planner/tools/search_tools.py +73 -0
- langtrace_python_sdk/instrumentation/anthropic/patch.py +6 -58
- langtrace_python_sdk/instrumentation/cohere/patch.py +5 -13
- langtrace_python_sdk/instrumentation/langchain_core/instrumentation.py +18 -3
- langtrace_python_sdk/instrumentation/ollama/patch.py +43 -110
- langtrace_python_sdk/utils/llm.py +62 -36
- langtrace_python_sdk/version.py +1 -1
- {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/METADATA +1 -1
- {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/RECORD +25 -14
- tests/anthropic/test_anthropic.py +0 -4
- tests/cohere/test_cohere_chat.py +0 -2
- tests/openai/test_chat_completion.py +0 -6
- {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/WHEEL +0 -0
- {langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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(
|
|
70
|
-
|
|
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
|
-
(
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
except Exception as err:
|
|
55
|
+
# Record the exception in the span
|
|
56
|
+
span.record_exception(err)
|
|
62
57
|
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
# Set the span status to indicate an error
|
|
59
|
+
span.set_status(Status(StatusCode.ERROR, str(err)))
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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
|
-
|
|
386
|
-
|
|
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)
|
langtrace_python_sdk/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.2.
|
|
1
|
+
__version__ = "2.2.22"
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
193
|
-
langtrace_python_sdk-2.2.
|
|
194
|
-
langtrace_python_sdk-2.2.
|
|
195
|
-
langtrace_python_sdk-2.2.
|
|
196
|
-
langtrace_python_sdk-2.2.
|
|
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)
|
tests/cohere/test_cohere_chat.py
CHANGED
|
@@ -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)
|
|
File without changes
|
{langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{langtrace_python_sdk-2.2.20.dist-info → langtrace_python_sdk-2.2.22.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|