Skip to content

Commit 07a1993

Browse files
authored
feat(client): add native environments (#1122)
1 parent d705771 commit 07a1993

23 files changed

+283
-106
lines changed

langfuse/api/reference.md

+18-24
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,15 @@ client.health.health()
11201120
<dl>
11211121
<dd>
11221122

1123-
Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement.
1123+
Batched ingestion for Langfuse Tracing.
1124+
If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement.
1125+
1126+
Within each batch, there can be multiple events.
1127+
Each event has a type, an id, a timestamp, metadata and a body.
1128+
Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace.
1129+
We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request.
1130+
The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App.
1131+
I.e. if you want to update a trace, you'd use the same body id, but separate event IDs.
11241132

11251133
Notes:
11261134

@@ -1141,9 +1149,7 @@ Notes:
11411149
<dd>
11421150

11431151
```python
1144-
import datetime
1145-
1146-
from langfuse import IngestionEvent_TraceCreate, TraceBody
1152+
from langfuse import IngestionEvent_ScoreCreate, ScoreBody
11471153
from langfuse.client import FernLangfuse
11481154

11491155
client = FernLangfuse(
@@ -1156,29 +1162,17 @@ client = FernLangfuse(
11561162
)
11571163
client.ingestion.batch(
11581164
batch=[
1159-
IngestionEvent_TraceCreate(
1160-
body=TraceBody(
1161-
id="string",
1162-
timestamp=datetime.datetime.fromisoformat(
1163-
"2024-01-15 09:30:00+00:00",
1164-
),
1165-
name="string",
1166-
user_id="string",
1167-
input={"key": "value"},
1168-
output={"key": "value"},
1169-
session_id="string",
1170-
release="string",
1171-
version="string",
1172-
metadata={"key": "value"},
1173-
tags=["string"],
1174-
public=True,
1165+
IngestionEvent_ScoreCreate(
1166+
id="abcdef-1234-5678-90ab",
1167+
timestamp="2022-01-01T00:00:00.000Z",
1168+
body=ScoreBody(
1169+
id="abcdef-1234-5678-90ab",
1170+
trace_id="1234-5678-90ab-cdef",
1171+
name="My Score",
1172+
value=0.9,
11751173
),
1176-
id="string",
1177-
timestamp="string",
1178-
metadata={"key": "value"},
11791174
)
11801175
],
1181-
metadata={"key": "value"},
11821176
)
11831177

11841178
```

langfuse/api/resources/commons/types/base_score.py

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ class BaseScore(pydantic_v1.BaseModel):
3333
Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue.
3434
"""
3535

36+
environment: typing.Optional[str] = pydantic_v1.Field(default=None)
37+
"""
38+
The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'.
39+
"""
40+
3641
def json(self, **kwargs: typing.Any) -> str:
3742
kwargs_with_defaults: typing.Any = {
3843
"by_alias": True,

langfuse/api/resources/commons/types/observation.py

+5
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ class Observation(pydantic_v1.BaseModel):
125125
The cost details of the observation. Key is the name of the cost metric, value is the cost in USD. The total key is the sum of all (non-total) cost metrics or the total value ingested.
126126
"""
127127

128+
environment: typing.Optional[str] = pydantic_v1.Field(default=None)
129+
"""
130+
The environment from which this observation originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'.
131+
"""
132+
128133
def json(self, **kwargs: typing.Any) -> str:
129134
kwargs_with_defaults: typing.Any = {
130135
"by_alias": True,

langfuse/api/resources/commons/types/score.py

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Score_Numeric(pydantic_v1.BaseModel):
2828
comment: typing.Optional[str] = None
2929
config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None)
3030
queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None)
31+
environment: typing.Optional[str] = None
3132
data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field(
3233
alias="dataType", default="NUMERIC"
3334
)
@@ -85,6 +86,7 @@ class Score_Categorical(pydantic_v1.BaseModel):
8586
comment: typing.Optional[str] = None
8687
config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None)
8788
queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None)
89+
environment: typing.Optional[str] = None
8890
data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field(
8991
alias="dataType", default="CATEGORICAL"
9092
)
@@ -142,6 +144,7 @@ class Score_Boolean(pydantic_v1.BaseModel):
142144
comment: typing.Optional[str] = None
143145
config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None)
144146
queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None)
147+
environment: typing.Optional[str] = None
145148
data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field(
146149
alias="dataType", default="BOOLEAN"
147150
)

langfuse/api/resources/commons/types/trace.py

+5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ class Trace(pydantic_v1.BaseModel):
7070
Public traces are accessible via url without login
7171
"""
7272

73+
environment: typing.Optional[str] = pydantic_v1.Field(default=None)
74+
"""
75+
The environment from which this trace originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'.
76+
"""
77+
7378
def json(self, **kwargs: typing.Any) -> str:
7479
kwargs_with_defaults: typing.Any = {
7580
"by_alias": True,

langfuse/api/resources/ingestion/client.py

+44-32
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,15 @@ def batch(
3131
request_options: typing.Optional[RequestOptions] = None,
3232
) -> IngestionResponse:
3333
"""
34-
Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement.
34+
Batched ingestion for Langfuse Tracing.
35+
If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement.
36+
37+
Within each batch, there can be multiple events.
38+
Each event has a type, an id, a timestamp, metadata and a body.
39+
Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace.
40+
We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request.
41+
The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App.
42+
I.e. if you want to update a trace, you'd use the same body id, but separate event IDs.
3543
3644
Notes:
3745
@@ -72,28 +80,26 @@ def batch(
7280
client.ingestion.batch(
7381
batch=[
7482
IngestionEvent_TraceCreate(
83+
id="abcdef-1234-5678-90ab",
84+
timestamp="2022-01-01T00:00:00.000Z",
7585
body=TraceBody(
76-
id="string",
86+
id="abcdef-1234-5678-90ab",
7787
timestamp=datetime.datetime.fromisoformat(
78-
"2024-01-15 09:30:00+00:00",
88+
"2022-01-01 00:00:00+00:00",
7989
),
80-
name="string",
81-
user_id="string",
82-
input={"key": "value"},
83-
output={"key": "value"},
84-
session_id="string",
85-
release="string",
86-
version="string",
87-
metadata={"key": "value"},
88-
tags=["string"],
90+
name="My Trace",
91+
user_id="1234-5678-90ab-cdef",
92+
input="My input",
93+
output="My output",
94+
session_id="1234-5678-90ab-cdef",
95+
release="1.0.0",
96+
version="1.0.0",
97+
metadata="My metadata",
98+
tags=["tag1", "tag2"],
8999
public=True,
90100
),
91-
id="string",
92-
timestamp="string",
93-
metadata={"key": "value"},
94101
)
95102
],
96-
metadata={"key": "value"},
97103
)
98104
"""
99105
_response = self._client_wrapper.httpx_client.request(
@@ -142,7 +148,15 @@ async def batch(
142148
request_options: typing.Optional[RequestOptions] = None,
143149
) -> IngestionResponse:
144150
"""
145-
Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement.
151+
Batched ingestion for Langfuse Tracing.
152+
If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement.
153+
154+
Within each batch, there can be multiple events.
155+
Each event has a type, an id, a timestamp, metadata and a body.
156+
Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace.
157+
We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request.
158+
The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App.
159+
I.e. if you want to update a trace, you'd use the same body id, but separate event IDs.
146160
147161
Notes:
148162
@@ -187,28 +201,26 @@ async def main() -> None:
187201
await client.ingestion.batch(
188202
batch=[
189203
IngestionEvent_TraceCreate(
204+
id="abcdef-1234-5678-90ab",
205+
timestamp="2022-01-01T00:00:00.000Z",
190206
body=TraceBody(
191-
id="string",
207+
id="abcdef-1234-5678-90ab",
192208
timestamp=datetime.datetime.fromisoformat(
193-
"2024-01-15 09:30:00+00:00",
209+
"2022-01-01 00:00:00+00:00",
194210
),
195-
name="string",
196-
user_id="string",
197-
input={"key": "value"},
198-
output={"key": "value"},
199-
session_id="string",
200-
release="string",
201-
version="string",
202-
metadata={"key": "value"},
203-
tags=["string"],
211+
name="My Trace",
212+
user_id="1234-5678-90ab-cdef",
213+
input="My input",
214+
output="My output",
215+
session_id="1234-5678-90ab-cdef",
216+
release="1.0.0",
217+
version="1.0.0",
218+
metadata="My metadata",
219+
tags=["tag1", "tag2"],
204220
public=True,
205221
),
206-
id="string",
207-
timestamp="string",
208-
metadata={"key": "value"},
209222
)
210223
],
211-
metadata={"key": "value"},
212224
)
213225
214226

langfuse/api/resources/ingestion/types/observation_body.py

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class ObservationBody(pydantic_v1.BaseModel):
4141
parent_observation_id: typing.Optional[str] = pydantic_v1.Field(
4242
alias="parentObservationId", default=None
4343
)
44+
environment: typing.Optional[str] = None
4445

4546
def json(self, **kwargs: typing.Any) -> str:
4647
kwargs_with_defaults: typing.Any = {

langfuse/api/resources/ingestion/types/optional_observation_body.py

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class OptionalObservationBody(pydantic_v1.BaseModel):
2525
alias="parentObservationId", default=None
2626
)
2727
version: typing.Optional[str] = None
28+
environment: typing.Optional[str] = None
2829

2930
def json(self, **kwargs: typing.Any) -> str:
3031
kwargs_with_defaults: typing.Any = {

langfuse/api/resources/ingestion/types/score_body.py

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class ScoreBody(pydantic_v1.BaseModel):
2525
id: typing.Optional[str] = None
2626
trace_id: str = pydantic_v1.Field(alias="traceId")
2727
name: str
28+
environment: typing.Optional[str] = None
2829
value: CreateScoreValue = pydantic_v1.Field()
2930
"""
3031
The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false)

langfuse/api/resources/ingestion/types/trace_body.py

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class TraceBody(pydantic_v1.BaseModel):
2121
version: typing.Optional[str] = None
2222
metadata: typing.Optional[typing.Any] = None
2323
tags: typing.Optional[typing.List[str]] = None
24+
environment: typing.Optional[str] = None
2425
public: typing.Optional[bool] = pydantic_v1.Field(default=None)
2526
"""
2627
Make trace publicly accessible via url

langfuse/api/resources/score/types/get_scores_response_data.py

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class GetScoresResponseData_Numeric(pydantic_v1.BaseModel):
3030
comment: typing.Optional[str] = None
3131
config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None)
3232
queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None)
33+
environment: typing.Optional[str] = None
3334
data_type: typing.Literal["NUMERIC"] = pydantic_v1.Field(
3435
alias="dataType", default="NUMERIC"
3536
)
@@ -88,6 +89,7 @@ class GetScoresResponseData_Categorical(pydantic_v1.BaseModel):
8889
comment: typing.Optional[str] = None
8990
config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None)
9091
queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None)
92+
environment: typing.Optional[str] = None
9193
data_type: typing.Literal["CATEGORICAL"] = pydantic_v1.Field(
9294
alias="dataType", default="CATEGORICAL"
9395
)
@@ -146,6 +148,7 @@ class GetScoresResponseData_Boolean(pydantic_v1.BaseModel):
146148
comment: typing.Optional[str] = None
147149
config_id: typing.Optional[str] = pydantic_v1.Field(alias="configId", default=None)
148150
queue_id: typing.Optional[str] = pydantic_v1.Field(alias="queueId", default=None)
151+
environment: typing.Optional[str] = None
149152
data_type: typing.Literal["BOOLEAN"] = pydantic_v1.Field(
150153
alias="dataType", default="BOOLEAN"
151154
)

langfuse/callback/langchain.py

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def __init__(
8787
sdk_integration: Optional[str] = None,
8888
sample_rate: Optional[float] = None,
8989
mask: Optional[MaskFunction] = None,
90+
environment: Optional[str] = None,
9091
) -> None:
9192
LangfuseBaseCallbackHandler.__init__(
9293
self,
@@ -113,6 +114,7 @@ def __init__(
113114
sdk_integration=sdk_integration or "langchain",
114115
sample_rate=sample_rate,
115116
mask=mask,
117+
environment=environment,
116118
)
117119

118120
self.runs = {}

0 commit comments

Comments
 (0)