Skip to content

Commit 405d65b

Browse files
authored
Merge branch 'main' into hassieb/lfe-8749-bug-custom-httpx_client-is-not-applied-to-clientasync_api-in
2 parents 9f5b8c8 + d935f02 commit 405d65b

12 files changed

Lines changed: 188 additions & 16 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Claude Review on Maintainer PRs
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- opened
7+
- ready_for_review
8+
9+
jobs:
10+
comment:
11+
if: github.event.pull_request.draft == false
12+
runs-on: ubuntu-latest
13+
permissions:
14+
issues: write
15+
pull-requests: write
16+
steps:
17+
- name: Check author permission and existing review request
18+
id: check
19+
uses: actions/github-script@v7
20+
with:
21+
script: |
22+
const owner = context.repo.owner;
23+
const repo = context.repo.repo;
24+
const issue_number = context.payload.pull_request.number;
25+
const username = context.payload.pull_request.user.login;
26+
27+
let permission = "none";
28+
try {
29+
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
30+
owner,
31+
repo,
32+
username,
33+
});
34+
permission = data.permission;
35+
} catch (error) {
36+
if (error.status !== 404) {
37+
throw error;
38+
}
39+
}
40+
41+
const canWrite = ["write", "admin"].includes(permission);
42+
const comments = await github.paginate(github.rest.issues.listComments, {
43+
owner,
44+
repo,
45+
issue_number,
46+
per_page: 100,
47+
});
48+
const hasReviewRequest = comments.some(
49+
(comment) => comment.body?.trim() === "@claude review",
50+
);
51+
52+
core.info(
53+
`PR #${issue_number} by ${username}: permission=${permission}, hasReviewRequest=${hasReviewRequest}`,
54+
);
55+
56+
core.setOutput("should_comment", canWrite && !hasReviewRequest ? "true" : "false");
57+
58+
- name: Add Claude review comment
59+
if: steps.check.outputs.should_comment == 'true'
60+
uses: actions/github-script@v7
61+
with:
62+
script: |
63+
await github.rest.issues.createComment({
64+
owner: context.repo.owner,
65+
repo: context.repo.repo,
66+
issue_number: context.payload.pull_request.number,
67+
body: "@claude review",
68+
});

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
## Installation
1313

1414
> [!IMPORTANT]
15-
> The SDK was rewritten in v3 and released in June 2025. Refer to the [v3 migration guide](https://langfuse.com/docs/sdk/python/sdk-v3#upgrade-from-v2) for instructions on updating your code.
15+
> The SDK was rewritten in v4 and released in March 2026. Refer to the [v4 migration guide](https://langfuse.com/docs/observability/sdk/upgrade-path/python-v3-to-v4) for instructions on updating your code.
1616
1717
```
1818
pip install langfuse

langfuse/_client/observe.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any:
327327
langfuse_span_or_generation.update(output=result)
328328

329329
return result
330-
except Exception as e:
330+
except (Exception, asyncio.CancelledError) as e:
331331
langfuse_span_or_generation.update(
332332
level="ERROR", status_message=str(e) or type(e).__name__
333333
)
@@ -445,7 +445,7 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
445445
langfuse_span_or_generation.update(output=result)
446446

447447
return result
448-
except Exception as e:
448+
except (Exception, asyncio.CancelledError) as e:
449449
langfuse_span_or_generation.update(
450450
level="ERROR", status_message=str(e) or type(e).__name__
451451
)
@@ -586,7 +586,7 @@ def __next__(self) -> Any:
586586

587587
raise # Re-raise StopIteration
588588

589-
except Exception as e:
589+
except (Exception, asyncio.CancelledError) as e:
590590
self.span.update(
591591
level="ERROR", status_message=str(e) or type(e).__name__
592592
).end()
@@ -653,7 +653,7 @@ async def __anext__(self) -> Any:
653653
self.span.update(output=output).end()
654654

655655
raise # Re-raise StopAsyncIteration
656-
except Exception as e:
656+
except (Exception, asyncio.CancelledError) as e:
657657
self.span.update(
658658
level="ERROR", status_message=str(e) or type(e).__name__
659659
).end()

langfuse/api/dataset_run_items/client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def create(
3838
observation_id: typing.Optional[str] = OMIT,
3939
trace_id: typing.Optional[str] = OMIT,
4040
dataset_version: typing.Optional[dt.datetime] = OMIT,
41+
created_at: typing.Optional[dt.datetime] = OMIT,
4142
request_options: typing.Optional[RequestOptions] = None,
4243
) -> DatasetRunItem:
4344
"""
@@ -66,6 +67,9 @@ def create(
6667
If provided, the experiment will use dataset items as they existed at or before this timestamp.
6768
If not provided, uses the latest version of dataset items.
6869
70+
created_at : typing.Optional[dt.datetime]
71+
Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp.
72+
6973
request_options : typing.Optional[RequestOptions]
7074
Request-specific configuration.
7175
@@ -98,6 +102,7 @@ def create(
98102
observation_id=observation_id,
99103
trace_id=trace_id,
100104
dataset_version=dataset_version,
105+
created_at=created_at,
101106
request_options=request_options,
102107
)
103108
return _response.data
@@ -185,6 +190,7 @@ async def create(
185190
observation_id: typing.Optional[str] = OMIT,
186191
trace_id: typing.Optional[str] = OMIT,
187192
dataset_version: typing.Optional[dt.datetime] = OMIT,
193+
created_at: typing.Optional[dt.datetime] = OMIT,
188194
request_options: typing.Optional[RequestOptions] = None,
189195
) -> DatasetRunItem:
190196
"""
@@ -213,6 +219,9 @@ async def create(
213219
If provided, the experiment will use dataset items as they existed at or before this timestamp.
214220
If not provided, uses the latest version of dataset items.
215221
222+
created_at : typing.Optional[dt.datetime]
223+
Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp.
224+
216225
request_options : typing.Optional[RequestOptions]
217226
Request-specific configuration.
218227
@@ -253,6 +262,7 @@ async def main() -> None:
253262
observation_id=observation_id,
254263
trace_id=trace_id,
255264
dataset_version=dataset_version,
265+
created_at=created_at,
256266
request_options=request_options,
257267
)
258268
return _response.data

langfuse/api/dataset_run_items/raw_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def create(
3535
observation_id: typing.Optional[str] = OMIT,
3636
trace_id: typing.Optional[str] = OMIT,
3737
dataset_version: typing.Optional[dt.datetime] = OMIT,
38+
created_at: typing.Optional[dt.datetime] = OMIT,
3839
request_options: typing.Optional[RequestOptions] = None,
3940
) -> HttpResponse[DatasetRunItem]:
4041
"""
@@ -63,6 +64,9 @@ def create(
6364
If provided, the experiment will use dataset items as they existed at or before this timestamp.
6465
If not provided, uses the latest version of dataset items.
6566
67+
created_at : typing.Optional[dt.datetime]
68+
Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp.
69+
6670
request_options : typing.Optional[RequestOptions]
6771
Request-specific configuration.
6872
@@ -81,6 +85,7 @@ def create(
8185
"observationId": observation_id,
8286
"traceId": trace_id,
8387
"datasetVersion": dataset_version,
88+
"createdAt": created_at,
8489
},
8590
request_options=request_options,
8691
omit=OMIT,
@@ -298,6 +303,7 @@ async def create(
298303
observation_id: typing.Optional[str] = OMIT,
299304
trace_id: typing.Optional[str] = OMIT,
300305
dataset_version: typing.Optional[dt.datetime] = OMIT,
306+
created_at: typing.Optional[dt.datetime] = OMIT,
301307
request_options: typing.Optional[RequestOptions] = None,
302308
) -> AsyncHttpResponse[DatasetRunItem]:
303309
"""
@@ -326,6 +332,9 @@ async def create(
326332
If provided, the experiment will use dataset items as they existed at or before this timestamp.
327333
If not provided, uses the latest version of dataset items.
328334
335+
created_at : typing.Optional[dt.datetime]
336+
Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp.
337+
329338
request_options : typing.Optional[RequestOptions]
330339
Request-specific configuration.
331340
@@ -344,6 +353,7 @@ async def create(
344353
"observationId": observation_id,
345354
"traceId": trace_id,
346355
"datasetVersion": dataset_version,
356+
"createdAt": created_at,
347357
},
348358
request_options=request_options,
349359
omit=OMIT,

langfuse/api/dataset_run_items/types/create_dataset_run_item_request.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ class CreateDatasetRunItemRequest(UniversalBaseModel):
4646
If not provided, uses the latest version of dataset items.
4747
"""
4848

49+
created_at: typing_extensions.Annotated[
50+
typing.Optional[dt.datetime], FieldMetadata(alias="createdAt")
51+
] = pydantic.Field(default=None)
52+
"""
53+
Optional timestamp to set the createdAt field of the dataset run item. If not provided or null, defaults to current timestamp.
54+
"""
55+
4956
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(
5057
extra="allow", frozen=True
5158
)

langfuse/openai.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,34 @@ def wrapper(wrapped: Any, instance: Any, args: Any, kwargs: Any) -> Any:
246246
return _with_langfuse
247247

248248

249+
def _extract_responses_prompt(kwargs: Any) -> Any:
250+
input_value = kwargs.get("input", None)
251+
instructions = kwargs.get("instructions", None)
252+
253+
if isinstance(input_value, NotGiven):
254+
input_value = None
255+
256+
if isinstance(instructions, NotGiven):
257+
instructions = None
258+
259+
if instructions is None:
260+
return input_value
261+
262+
if input_value is None:
263+
return {"instructions": instructions}
264+
265+
if isinstance(input_value, str):
266+
return [
267+
{"role": "system", "content": instructions},
268+
{"role": "user", "content": input_value},
269+
]
270+
271+
if isinstance(input_value, list):
272+
return [{"role": "system", "content": instructions}, *input_value]
273+
274+
return {"instructions": instructions, "input": input_value}
275+
276+
249277
def _extract_chat_prompt(kwargs: Any) -> Any:
250278
"""Extracts the user input from prompts. Returns an array of messages or dict with messages and functions"""
251279
prompt = {}
@@ -403,7 +431,7 @@ def _get_langfuse_data_from_kwargs(resource: OpenAiDefinition, kwargs: Any) -> A
403431
if resource.type == "completion":
404432
prompt = kwargs.get("prompt", None)
405433
elif resource.object == "Responses" or resource.object == "AsyncResponses":
406-
prompt = kwargs.get("input", None)
434+
prompt = _extract_responses_prompt(kwargs)
407435
elif resource.type == "chat":
408436
prompt = _extract_chat_prompt(kwargs)
409437
elif resource.type == "embedding":
@@ -539,8 +567,8 @@ def _parse_usage(usage: Optional[Any] = None) -> Any:
539567
for tokens_details in [
540568
"prompt_tokens_details",
541569
"completion_tokens_details",
542-
"input_token_details",
543-
"output_token_details",
570+
"input_tokens_details",
571+
"output_tokens_details",
544572
]:
545573
if tokens_details in usage_dict and usage_dict[tokens_details] is not None:
546574
tokens_details_dict = (

langfuse/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""@private"""
22

3-
__version__ = "4.0.0"
3+
__version__ = "4.0.1"

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22
name = "langfuse"
33

4-
version = "4.0.0"
4+
version = "4.0.1"
55
description = "A client library for accessing langfuse"
66
authors = ["langfuse <developers@langfuse.com>"]
77
license = "MIT"
@@ -14,7 +14,7 @@ pydantic = "^2"
1414
backoff = ">=1.10.0"
1515
openai = { version = ">=0.27.8", optional = true }
1616
wrapt = "^1.14"
17-
packaging = ">=23.2,<26.0"
17+
packaging = ">=23.2,<27.0"
1818
opentelemetry-api = "^1.33.1"
1919
opentelemetry-sdk = "^1.33.1"
2020
opentelemetry-exporter-otlp-proto-http = "^1.33.1"

0 commit comments

Comments
 (0)