From 4e4756766646accdabad3ef521c581086728a436 Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Fri, 25 Oct 2024 12:40:53 -0700 Subject: [PATCH 1/8] Add multipart feedback ingestion --- .../langsmith/_internal/_background_thread.py | 3 +- python/langsmith/client.py | 193 ++++++++++++------ python/langsmith/schemas.py | 2 + python/tests/integration_tests/test_client.py | 30 ++- python/tests/unit_tests/test_client.py | 2 +- 5 files changed, 146 insertions(+), 84 deletions(-) diff --git a/python/langsmith/_internal/_background_thread.py b/python/langsmith/_internal/_background_thread.py index 525a3513c..432a63b04 100644 --- a/python/langsmith/_internal/_background_thread.py +++ b/python/langsmith/_internal/_background_thread.py @@ -69,9 +69,10 @@ def _tracing_thread_handle_batch( ) -> None: create = [it.item for it in batch if it.action == "create"] update = [it.item for it in batch if it.action == "update"] + feedback = [it.item for it in batch if it.action == "feedback"] try: if use_multipart: - client.multipart_ingest_runs(create=create, update=update, pre_sampled=True) + client.multipart_ingest(create=create, update=update, feedback=feedback, pre_sampled=True) else: client.batch_ingest_runs(create=create, update=update, pre_sampled=True) except Exception: diff --git a/python/langsmith/client.py b/python/langsmith/client.py index 4a1601c44..4fc90a0ba 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -1122,6 +1122,34 @@ def _run_transform( return run_create + def _feedback_transform( + self, + feedback: Union[ls_schemas.Feedback, dict], + ) -> dict: + """Transform the given feedback object into a dictionary representation. + + Args: + feedback (Union[ls_schemas.Feedback, dict]): The feedback object to transform. + update (bool, optional): Whether the payload is for an "update" event. + copy (bool, optional): Whether to deepcopy feedback inputs/outputs. + attachments_collector (Optional[dict[str, ls_schemas.Attachments]]): + A dictionary to collect attachments. If not passed, attachments + will be dropped. + + Returns: + dict: The transformed feedback object as a dictionary. + """ + if hasattr(feedback, "dict") and callable(getattr(feedback, "dict")): + feedback_create: dict = feedback.dict() # type: ignore + else: + feedback_create = cast(dict, feedback) + if "id" not in feedback_create: + feedback_create["id"] = uuid.uuid4() + elif isinstance(feedback_create["id"], str): + feedback_create["id"] = uuid.UUID(feedback_create["id"]) + + return feedback_create + @staticmethod def _insert_runtime_env(runs: Sequence[dict]) -> None: runtime_env = ls_env.get_runtime_environment() @@ -1408,7 +1436,7 @@ def _post_batch_ingest_runs(self, body: bytes, *, _context: str): except Exception: logger.warning(f"Failed to batch ingest runs: {repr(e)}") - def multipart_ingest_runs( + def multipart_ingest( self, create: Optional[ Sequence[Union[ls_schemas.Run, ls_schemas.RunLikeDict, Dict]] @@ -1416,6 +1444,7 @@ def multipart_ingest_runs( update: Optional[ Sequence[Union[ls_schemas.Run, ls_schemas.RunLikeDict, Dict]] ] = None, + feedback: Optional[Sequence[Union[ls_schemas.Feedback, Dict]]] = None, *, pre_sampled: bool = False, ) -> None: @@ -1442,7 +1471,7 @@ def multipart_ingest_runs( - The run objects MUST contain the dotted_order and trace_id fields to be accepted by the API. """ - if not create and not update: + if not create and not update and not feedback: return # transform and convert to dicts all_attachments: Dict[str, ls_schemas.Attachments] = {} @@ -1454,6 +1483,7 @@ def multipart_ingest_runs( self._run_transform(run, update=True, attachments_collector=all_attachments) for run in update or EMPTY_SEQ ] + feedback_dicts = [self._feedback_transform(f) for f in feedback or EMPTY_SEQ] # require trace_id and dotted_order if create_dicts: for run in create_dicts: @@ -1491,7 +1521,7 @@ def multipart_ingest_runs( if not pre_sampled: create_dicts = self._filter_for_sampling(create_dicts) update_dicts = self._filter_for_sampling(update_dicts, patch=True) - if not create_dicts and not update_dicts: + if not create_dicts and not update_dicts and not feedback_dicts: return # insert runtime environment self._insert_runtime_env(create_dicts) @@ -1499,14 +1529,28 @@ def multipart_ingest_runs( # send the runs in multipart requests acc_context: List[str] = [] acc_parts: MultipartParts = [] - for event, payloads in (("post", create_dicts), ("patch", update_dicts)): + for event, payloads in ( + ("post", create_dicts), + ("patch", update_dicts), + ("feedback", feedback_dicts), + ): for payload in payloads: # collect fields to be sent as separate parts - fields = [ - ("inputs", payload.pop("inputs", None)), - ("outputs", payload.pop("outputs", None)), - ("events", payload.pop("events", None)), - ] + fields = [] + if create_dicts or update_dicts: + fields.extend( + [ + ("inputs", payload.pop("inputs", None)), + ("outputs", payload.pop("outputs", None)), + ("events", payload.pop("events", None)), + ] + ) + if feedback: + fields.extend( + [ + ("feedback", payload.pop("feedback", None)), + ] + ) # encode the main run payload payloadb = _dumps_json(payload) acc_parts.append( @@ -4241,66 +4285,83 @@ def create_feedback( f" endpoint: {sorted(kwargs)}", DeprecationWarning, ) - if not isinstance(feedback_source_type, ls_schemas.FeedbackSourceType): - feedback_source_type = ls_schemas.FeedbackSourceType(feedback_source_type) - if feedback_source_type == ls_schemas.FeedbackSourceType.API: - feedback_source: ls_schemas.FeedbackSourceBase = ( - ls_schemas.APIFeedbackSource(metadata=source_info) + try: + if not isinstance(feedback_source_type, ls_schemas.FeedbackSourceType): + feedback_source_type = ls_schemas.FeedbackSourceType( + feedback_source_type + ) + if feedback_source_type == ls_schemas.FeedbackSourceType.API: + feedback_source: ls_schemas.FeedbackSourceBase = ( + ls_schemas.APIFeedbackSource(metadata=source_info) + ) + elif feedback_source_type == ls_schemas.FeedbackSourceType.MODEL: + feedback_source = ls_schemas.ModelFeedbackSource(metadata=source_info) + else: + raise ValueError(f"Unknown feedback source type {feedback_source_type}") + feedback_source.metadata = ( + feedback_source.metadata if feedback_source.metadata is not None else {} ) - elif feedback_source_type == ls_schemas.FeedbackSourceType.MODEL: - feedback_source = ls_schemas.ModelFeedbackSource(metadata=source_info) - else: - raise ValueError(f"Unknown feedback source type {feedback_source_type}") - feedback_source.metadata = ( - feedback_source.metadata if feedback_source.metadata is not None else {} - ) - if source_run_id is not None and "__run" not in feedback_source.metadata: - feedback_source.metadata["__run"] = {"run_id": str(source_run_id)} - if feedback_source.metadata and "__run" in feedback_source.metadata: - # Validate that the linked run ID is a valid UUID - # Run info may be a base model or dict. - _run_meta: Union[dict, Any] = feedback_source.metadata["__run"] - if hasattr(_run_meta, "dict") and callable(_run_meta): - _run_meta = _run_meta.dict() - if "run_id" in _run_meta: - _run_meta["run_id"] = str( - _as_uuid( - feedback_source.metadata["__run"]["run_id"], - "feedback_source.metadata['__run']['run_id']", + if source_run_id is not None and "__run" not in feedback_source.metadata: + feedback_source.metadata["__run"] = {"run_id": str(source_run_id)} + if feedback_source.metadata and "__run" in feedback_source.metadata: + # Validate that the linked run ID is a valid UUID + # Run info may be a base model or dict. + _run_meta: Union[dict, Any] = feedback_source.metadata["__run"] + if hasattr(_run_meta, "dict") and callable(_run_meta): + _run_meta = _run_meta.dict() + if "run_id" in _run_meta: + _run_meta["run_id"] = str( + _as_uuid( + feedback_source.metadata["__run"]["run_id"], + "feedback_source.metadata['__run']['run_id']", + ) ) + feedback_source.metadata["__run"] = _run_meta + feedback = ls_schemas.FeedbackCreate( + id=_ensure_uuid(feedback_id), + # If run_id is None, this is interpreted as session-level + # feedback. + run_id=_ensure_uuid(run_id, accept_null=True), + trace_id=_ensure_uuid(run_id, accept_null=True), + key=key, + score=score, + value=value, + correction=correction, + comment=comment, + feedback_source=feedback_source, + created_at=datetime.datetime.now(datetime.timezone.utc), + modified_at=datetime.datetime.now(datetime.timezone.utc), + feedback_config=feedback_config, + session_id=_ensure_uuid(project_id, accept_null=True), + comparative_experiment_id=_ensure_uuid( + comparative_experiment_id, accept_null=True + ), + feedback_group_id=_ensure_uuid(feedback_group_id, accept_null=True), + ) + + feedback_block = _dumps_json(feedback.dict(exclude_none=True)) + use_multipart = (self.info.batch_ingest_config or {}).get( + "use_multipart_endpoint", False + ) + + if use_multipart and self.tracing_queue is not None: + self.tracing_queue.put( + TracingQueueItem(str(feedback.id), "feedback", feedback) ) - feedback_source.metadata["__run"] = _run_meta - feedback = ls_schemas.FeedbackCreate( - id=_ensure_uuid(feedback_id), - # If run_id is None, this is interpreted as session-level - # feedback. - run_id=_ensure_uuid(run_id, accept_null=True), - key=key, - score=score, - value=value, - correction=correction, - comment=comment, - feedback_source=feedback_source, - created_at=datetime.datetime.now(datetime.timezone.utc), - modified_at=datetime.datetime.now(datetime.timezone.utc), - feedback_config=feedback_config, - session_id=_ensure_uuid(project_id, accept_null=True), - comparative_experiment_id=_ensure_uuid( - comparative_experiment_id, accept_null=True - ), - feedback_group_id=_ensure_uuid(feedback_group_id, accept_null=True), - ) - feedback_block = _dumps_json(feedback.dict(exclude_none=True)) - self.request_with_retries( - "POST", - "/feedback", - request_kwargs={ - "data": feedback_block, - }, - stop_after_attempt=stop_after_attempt, - retry_on=(ls_utils.LangSmithNotFoundError,), - ) - return ls_schemas.Feedback(**feedback.dict()) + else: + self.request_with_retries( + "POST", + "/feedback", + request_kwargs={ + "data": feedback_block, + }, + stop_after_attempt=stop_after_attempt, + retry_on=(ls_utils.LangSmithNotFoundError,), + ) + return ls_schemas.Feedback(**feedback.dict()) + except Exception as e: + logger.error("Error creating feedback", exc_info=True) + raise e def update_feedback( self, diff --git a/python/langsmith/schemas.py b/python/langsmith/schemas.py index 8ad12b3d0..3f93b4363 100644 --- a/python/langsmith/schemas.py +++ b/python/langsmith/schemas.py @@ -440,6 +440,8 @@ class FeedbackBase(BaseModel): """The time the feedback was last modified.""" run_id: Optional[UUID] """The associated run ID this feedback is logged for.""" + trace_id: Optional[UUID] + """The associated trace ID this feedback is logged for.""" key: str """The metric name, tag, or aspect to provide feedback on.""" score: SCORE_TYPE = None diff --git a/python/tests/integration_tests/test_client.py b/python/tests/integration_tests/test_client.py index ad4b2cd93..d13d9639e 100644 --- a/python/tests/integration_tests/test_client.py +++ b/python/tests/integration_tests/test_client.py @@ -684,9 +684,7 @@ def test_batch_ingest_runs( }, ] if use_multipart_endpoint: - langchain_client.multipart_ingest_runs( - create=runs_to_create, update=runs_to_update - ) + langchain_client.multipart_ingest(create=runs_to_create, update=runs_to_update) else: langchain_client.batch_ingest_runs(create=runs_to_create, update=runs_to_update) runs = [] @@ -744,7 +742,7 @@ def test_batch_ingest_runs( """ -def test_multipart_ingest_runs_empty( +def test_multipart_ingest_empty( langchain_client: Client, caplog: pytest.LogCaptureFixture ) -> None: runs_to_create: list[dict] = [] @@ -752,17 +750,17 @@ def test_multipart_ingest_runs_empty( # make sure no warnings logged with caplog.at_level(logging.WARNING, logger="langsmith.client"): - langchain_client.multipart_ingest_runs( + langchain_client.multipart_ingest( create=runs_to_create, update=runs_to_update ) assert not caplog.records -def test_multipart_ingest_runs_create_then_update( +def test_multipart_ingest_create_then_update( langchain_client: Client, caplog: pytest.LogCaptureFixture ) -> None: - _session = "__test_multipart_ingest_runs_create_then_update" + _session = "__test_multipart_ingest_create_then_update" trace_a_id = uuid4() current_time = datetime.datetime.now(datetime.timezone.utc).strftime( @@ -783,7 +781,7 @@ def test_multipart_ingest_runs_create_then_update( # make sure no warnings logged with caplog.at_level(logging.WARNING, logger="langsmith.client"): - langchain_client.multipart_ingest_runs(create=runs_to_create, update=[]) + langchain_client.multipart_ingest(create=runs_to_create, update=[]) assert not caplog.records @@ -796,15 +794,15 @@ def test_multipart_ingest_runs_create_then_update( } ] with caplog.at_level(logging.WARNING, logger="langsmith.client"): - langchain_client.multipart_ingest_runs(create=[], update=runs_to_update) + langchain_client.multipart_ingest(create=[], update=runs_to_update) assert not caplog.records -def test_multipart_ingest_runs_update_then_create( +def test_multipart_ingest_update_then_create( langchain_client: Client, caplog: pytest.LogCaptureFixture ) -> None: - _session = "__test_multipart_ingest_runs_update_then_create" + _session = "__test_multipart_ingest_update_then_create" trace_a_id = uuid4() current_time = datetime.datetime.now(datetime.timezone.utc).strftime( @@ -822,7 +820,7 @@ def test_multipart_ingest_runs_update_then_create( # make sure no warnings logged with caplog.at_level(logging.WARNING, logger="langsmith.client"): - langchain_client.multipart_ingest_runs(create=[], update=runs_to_update) + langchain_client.multipart_ingest(create=[], update=runs_to_update) assert not caplog.records @@ -839,15 +837,15 @@ def test_multipart_ingest_runs_update_then_create( ] with caplog.at_level(logging.WARNING, logger="langsmith.client"): - langchain_client.multipart_ingest_runs(create=runs_to_create, update=[]) + langchain_client.multipart_ingest(create=runs_to_create, update=[]) assert not caplog.records -def test_multipart_ingest_runs_create_wrong_type( +def test_multipart_ingest_create_wrong_type( langchain_client: Client, caplog: pytest.LogCaptureFixture ) -> None: - _session = "__test_multipart_ingest_runs_create_then_update" + _session = "__test_multipart_ingest_create_then_update" trace_a_id = uuid4() current_time = datetime.datetime.now(datetime.timezone.utc).strftime( @@ -868,7 +866,7 @@ def test_multipart_ingest_runs_create_wrong_type( # make sure no warnings logged with caplog.at_level(logging.WARNING, logger="langsmith.client"): - langchain_client.multipart_ingest_runs(create=runs_to_create, update=[]) + langchain_client.multipart_ingest(create=runs_to_create, update=[]) # this should 422 assert len(caplog.records) == 1, "Should get 1 warning for 422, not retried" diff --git a/python/tests/unit_tests/test_client.py b/python/tests/unit_tests/test_client.py index a3f15671b..29655c84d 100644 --- a/python/tests/unit_tests/test_client.py +++ b/python/tests/unit_tests/test_client.py @@ -1060,7 +1060,7 @@ def test_batch_ingest_run_splits_large_batches( for run_id in patch_ids ] if use_multipart_endpoint: - client.multipart_ingest_runs(create=posts, update=patches) + client.multipart_ingest(create=posts, update=patches) # multipart endpoint should only send one request expected_num_requests = 1 # count the number of POST requests From 488d581d9e57ee0dea87d0cd7f7a6d1bd1e93c16 Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Fri, 25 Oct 2024 15:07:58 -0700 Subject: [PATCH 2/8] Remove check as not necessary --- python/langsmith/client.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/python/langsmith/client.py b/python/langsmith/client.py index 4fc90a0ba..c6f2fa00d 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -1536,21 +1536,12 @@ def multipart_ingest( ): for payload in payloads: # collect fields to be sent as separate parts - fields = [] - if create_dicts or update_dicts: - fields.extend( - [ - ("inputs", payload.pop("inputs", None)), - ("outputs", payload.pop("outputs", None)), - ("events", payload.pop("events", None)), - ] - ) - if feedback: - fields.extend( - [ - ("feedback", payload.pop("feedback", None)), - ] - ) + fields = [ + ("inputs", payload.pop("inputs", None)), + ("outputs", payload.pop("outputs", None)), + ("events", payload.pop("events", None)), + ("feedback", payload.pop("feedback", None)), + ] # encode the main run payload payloadb = _dumps_json(payload) acc_parts.append( From d5a25ea0ccc9dc09f7e64f054f08bc45127c3e64 Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Fri, 25 Oct 2024 17:22:39 -0700 Subject: [PATCH 3/8] Lint --- python/langsmith/_internal/_background_thread.py | 4 +++- python/tests/integration_tests/test_client.py | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/langsmith/_internal/_background_thread.py b/python/langsmith/_internal/_background_thread.py index 432a63b04..3a468643f 100644 --- a/python/langsmith/_internal/_background_thread.py +++ b/python/langsmith/_internal/_background_thread.py @@ -72,7 +72,9 @@ def _tracing_thread_handle_batch( feedback = [it.item for it in batch if it.action == "feedback"] try: if use_multipart: - client.multipart_ingest(create=create, update=update, feedback=feedback, pre_sampled=True) + client.multipart_ingest( + create=create, update=update, feedback=feedback, pre_sampled=True + ) else: client.batch_ingest_runs(create=create, update=update, pre_sampled=True) except Exception: diff --git a/python/tests/integration_tests/test_client.py b/python/tests/integration_tests/test_client.py index d13d9639e..22ac1735b 100644 --- a/python/tests/integration_tests/test_client.py +++ b/python/tests/integration_tests/test_client.py @@ -750,9 +750,7 @@ def test_multipart_ingest_empty( # make sure no warnings logged with caplog.at_level(logging.WARNING, logger="langsmith.client"): - langchain_client.multipart_ingest( - create=runs_to_create, update=runs_to_update - ) + langchain_client.multipart_ingest(create=runs_to_create, update=runs_to_update) assert not caplog.records From 484b79b49c076f9777d261158ae3bf654262365b Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Mon, 28 Oct 2024 13:37:53 -0700 Subject: [PATCH 4/8] Add trace_id param --- python/langsmith/client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python/langsmith/client.py b/python/langsmith/client.py index c6f2fa00d..389db09d9 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -4150,6 +4150,7 @@ def _submit_feedback(**kwargs): ), feedback_source_type=ls_schemas.FeedbackSourceType.MODEL, project_id=project_id, + trace_id=run.trace_id if run else None, ) return results @@ -4220,6 +4221,7 @@ def create_feedback( project_id: Optional[ID_TYPE] = None, comparative_experiment_id: Optional[ID_TYPE] = None, feedback_group_id: Optional[ID_TYPE] = None, + trace_id: Optional[ID_TYPE] = None, **kwargs: Any, ) -> ls_schemas.Feedback: """Create a feedback in the LangSmith API. @@ -4313,7 +4315,7 @@ def create_feedback( # If run_id is None, this is interpreted as session-level # feedback. run_id=_ensure_uuid(run_id, accept_null=True), - trace_id=_ensure_uuid(run_id, accept_null=True), + trace_id=_ensure_uuid(trace_id, accept_null=True), key=key, score=score, value=value, @@ -4335,7 +4337,11 @@ def create_feedback( "use_multipart_endpoint", False ) - if use_multipart and self.tracing_queue is not None: + if ( + use_multipart + and self.tracing_queue is not None + and feedback.trace_id is not None + ): self.tracing_queue.put( TracingQueueItem(str(feedback.id), "feedback", feedback) ) From cc415b5764ad05195991329abb39d4fe119421b1 Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Mon, 28 Oct 2024 14:46:23 -0700 Subject: [PATCH 5/8] Add to docs --- python/langsmith/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/langsmith/client.py b/python/langsmith/client.py index 389db09d9..6e3c787c3 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -4231,6 +4231,8 @@ def create_feedback( run_id : str or UUID The ID of the run to provide feedback for. Either the run_id OR the project_id must be provided. + trace_id : str or UUID + The trace ID of the run to provide feedback for. This is optional. key : str The name of the metric or 'aspect' this feedback is about. score : float or int or bool or None, default=None From 8314d6ad76ab1b36cf505d20d11075b78d9a9fa5 Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Mon, 28 Oct 2024 15:42:50 -0700 Subject: [PATCH 6/8] Add feedback tests --- python/tests/integration_tests/test_client.py | 26 ++++++++++++++++++- python/tests/unit_tests/test_client.py | 14 +++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/python/tests/integration_tests/test_client.py b/python/tests/integration_tests/test_client.py index 22ac1735b..059111e25 100644 --- a/python/tests/integration_tests/test_client.py +++ b/python/tests/integration_tests/test_client.py @@ -684,7 +684,20 @@ def test_batch_ingest_runs( }, ] if use_multipart_endpoint: - langchain_client.multipart_ingest(create=runs_to_create, update=runs_to_update) + feedback = [ + { + "run_id": run["id"], + "trace_id": run["trace_id"], + "key": "test_key", + "score": 0.9, + "value": "test_value", + "comment": "test_comment", + } + for run in runs_to_create + ] + langchain_client.multipart_ingest( + create=runs_to_create, update=runs_to_update, feedback=feedback + ) else: langchain_client.batch_ingest_runs(create=runs_to_create, update=runs_to_update) runs = [] @@ -724,6 +737,17 @@ def test_batch_ingest_runs( assert run3.inputs == {"input1": 1, "input2": 2} assert run3.error == "error" + if use_multipart_endpoint: + feedbacks = list( + langchain_client.list_feedback(run_ids=[run.id for run in runs]) + ) + assert len(feedbacks) == 3 + for feedback in feedbacks: + assert feedback.key == "test_key" + assert feedback.score == 0.9 + assert feedback.value == "test_value" + assert feedback.comment == "test_comment" + """ Multipart partitions: diff --git a/python/tests/unit_tests/test_client.py b/python/tests/unit_tests/test_client.py index 29655c84d..d5e4b5cde 100644 --- a/python/tests/unit_tests/test_client.py +++ b/python/tests/unit_tests/test_client.py @@ -1059,8 +1059,20 @@ def test_batch_ingest_run_splits_large_batches( } for run_id in patch_ids ] + if use_multipart_endpoint: - client.multipart_ingest(create=posts, update=patches) + feedback = [ + { + "run_id": run_id, + "trace_id": run_id, + "key": "test_key", + "score": 0.9, + "value": "test_value", + "comment": "test_comment", + } + for run_id in run_ids + ] + client.multipart_ingest(create=posts, update=patches, feedback=feedback) # multipart endpoint should only send one request expected_num_requests = 1 # count the number of POST requests From 1aeb0810c13af6b8f929c82ea2b05720a0a3f3c0 Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Mon, 28 Oct 2024 18:05:37 -0700 Subject: [PATCH 7/8] boolean check --- python/langsmith/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/langsmith/client.py b/python/langsmith/client.py index 6e3c787c3..b09833d4f 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -1471,7 +1471,7 @@ def multipart_ingest( - The run objects MUST contain the dotted_order and trace_id fields to be accepted by the API. """ - if not create and not update and not feedback: + if not (create or update or feedback): return # transform and convert to dicts all_attachments: Dict[str, ls_schemas.Attachments] = {} From 30d402a5cd15fee4dd112b385196457a98ddb737 Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Mon, 28 Oct 2024 18:25:31 -0700 Subject: [PATCH 8/8] Bump to rc1 --- python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index fc5e0f0af..620a3662a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langsmith" -version = "0.1.137" +version = "0.1.138rc1" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." authors = ["LangChain "] license = "MIT"