From 6cbe2c29879bc9f60f0e6f14e5be99ae8c177cfe Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:20:48 -0800 Subject: [PATCH] Wfh/update project (#307) --- js/src/client.ts | 69 ++++++++++++++++++++++++++++++++------ python/langsmith/client.py | 63 ++++++++++++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 12 deletions(-) diff --git a/js/src/client.ts b/js/src/client.ts index fd3140def..139b97e18 100644 --- a/js/src/client.ts +++ b/js/src/client.ts @@ -689,24 +689,31 @@ export class Client { public async createProject({ projectName, - projectExtra, - upsert, - referenceDatasetId, + description = null, + metadata = null, + upsert = false, + projectExtra = null, + referenceDatasetId = null, }: { projectName: string; - projectExtra?: object; + description?: string | null; + metadata?: Record | null; upsert?: boolean; - referenceDatasetId?: string; + projectExtra?: Record | null; + referenceDatasetId?: string | null; }): Promise { const upsert_ = upsert ? `?upsert=true` : ""; const endpoint = `${this.apiUrl}/sessions${upsert_}`; - const body: Record = { + const extra: Record = projectExtra || {}; + if (metadata) { + extra["metadata"] = metadata; + } + const body: Record = { name: projectName, + extra, + description, }; - if (projectExtra !== undefined) { - body["extra"] = projectExtra; - } - if (referenceDatasetId !== undefined) { + if (referenceDatasetId !== null) { body["reference_dataset_id"] = referenceDatasetId; } const response = await this.caller.call(fetch, endpoint, { @@ -724,6 +731,48 @@ export class Client { return result as TracerSession; } + public async updateProject( + projectId: string, + { + name = null, + description = null, + metadata = null, + projectExtra = null, + endTime = null, + }: { + name?: string | null; + description?: string | null; + metadata?: Record | null; + projectExtra?: Record | null; + endTime?: string | null; + } + ): Promise { + const endpoint = `${this.apiUrl}/sessions/${projectId}`; + let extra = projectExtra; + if (metadata) { + extra = { ...(extra || {}), metadata }; + } + const body: Record = { + name, + extra, + description, + end_time: endTime ? new Date(endTime).toISOString() : null, + }; + const response = await this.caller.call(fetch, endpoint, { + method: "PATCH", + headers: { ...this.headers, "Content-Type": "application/json" }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(this.timeout_ms), + }); + const result = await response.json(); + if (!response.ok) { + throw new Error( + `Failed to update project ${projectId}: ${response.status} ${response.statusText}` + ); + } + return result as TracerSession; + } + public async readProject({ projectId, projectName, diff --git a/python/langsmith/client.py b/python/langsmith/client.py index 9f49439dc..672c5963a 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -1073,8 +1073,10 @@ def create_project( self, project_name: str, *, - project_extra: Optional[dict] = None, + description: Optional[str] = None, + metadata: Optional[dict] = None, upsert: bool = False, + project_extra: Optional[dict] = None, reference_dataset_id: Optional[ID_TYPE] = None, ) -> ls_schemas.TracerSession: """Create a project on the LangSmith API. @@ -1085,6 +1087,10 @@ def create_project( The name of the project. project_extra : dict or None, default=None Additional project information. + metadata: dict or None, default=None + Additional metadata to associate with the project. + description : str or None, default=None + The description of the project. upsert : bool, default=False Whether to update the project if it already exists. reference_dataset_id: UUID or None, default=None @@ -1096,9 +1102,13 @@ def create_project( The created project. """ endpoint = f"{self.api_url}/sessions" + extra = project_extra + if metadata: + extra = {**(extra or {}), "metadata": metadata} body: Dict[str, Any] = { "name": project_name, - "extra": project_extra, + "extra": extra, + "description": description, } params = {} if upsert: @@ -1113,6 +1123,55 @@ def create_project( ls_utils.raise_for_status_with_text(response) return ls_schemas.TracerSession(**response.json(), _host_url=self._host_url) + def update_project( + self, + project_id: ID_TYPE, + *, + name: Optional[str] = None, + description: Optional[str] = None, + metadata: Optional[dict] = None, + project_extra: Optional[dict] = None, + end_time: Optional[datetime.datetime] = None, + ) -> ls_schemas.TracerSession: + """Update a LangSmith project. + + Parameters + ---------- + project_id : UUID + The ID of the project to update. + name : str or None, default=None + The new name to give the project. This is only valid if the project + has been assigned an end_time, meaning it has been completed/closed. + description : str or None, default=None + The new description to give the project. + metadata: dict or None, default=None + + project_extra : dict or None, default=None + Additional project information. + + Returns + ------- + TracerSession + The updated project. + """ + endpoint = f"{self.api_url}/sessions/{_as_uuid(project_id)}" + extra = project_extra + if metadata: + extra = {**(extra or {}), "metadata": metadata} + body: Dict[str, Any] = { + "name": name, + "extra": extra, + "description": description, + "end_time": end_time.isoformat() if end_time else None, + } + response = self.session.patch( + endpoint, + headers={**self._headers, "Content-Type": "application/json"}, + data=json.dumps(body, default=_serialize_json), + ) + ls_utils.raise_for_status_with_text(response) + return ls_schemas.TracerSession(**response.json(), _host_url=self._host_url) + def _get_tenant_id(self) -> uuid.UUID: if self._tenant_id is not None: return self._tenant_id