Skip to content
This repository was archived by the owner on Aug 5, 2025. It is now read-only.

Commit c5c241f

Browse files
Merge pull request #134 from Chainlit/clement/eng-1897-add-runtime-and-code-version-in-metadata-or-new-field
feat: add release param
2 parents ba22ec3 + 327a625 commit c5c241f

File tree

4 files changed

+66
-7
lines changed

4 files changed

+66
-7
lines changed

literalai/client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class BaseLiteralClient:
4646
"""
4747

4848
api: Union[LiteralAPI, AsyncLiteralAPI]
49+
global_metadata: Optional[Dict[str, str]] = None
4950

5051
def __init__(
5152
self,
@@ -55,6 +56,7 @@ def __init__(
5556
url: Optional[str] = None,
5657
environment: Optional[Environment] = None,
5758
disabled: bool = False,
59+
release: Optional[str] = None,
5860
):
5961
if not api_key:
6062
api_key = os.getenv("LITERAL_API_KEY", None)
@@ -77,6 +79,9 @@ def __init__(
7779
disabled=self.disabled,
7880
)
7981

82+
if release and release.strip():
83+
self.global_metadata = {"release": release.strip()}
84+
8085
def to_sync(self) -> "LiteralClient":
8186
if isinstance(self.api, AsyncLiteralAPI):
8287
return LiteralClient(
@@ -223,6 +228,9 @@ def message(
223228
metadata: Dict = {},
224229
root_run_id: Optional[str] = None,
225230
):
231+
if hasattr(self, "global_metadata") and self.global_metadata:
232+
metadata.update(self.global_metadata)
233+
226234
step = Message(
227235
name=name,
228236
id=id,
@@ -313,6 +321,11 @@ def start_step(
313321
root_run_id=root_run_id,
314322
**kwargs,
315323
)
324+
325+
if hasattr(self, "global_metadata") and self.global_metadata:
326+
step.metadata = step.metadata or {}
327+
step.metadata.update(self.global_metadata)
328+
316329
step.start()
317330
return step
318331

@@ -373,6 +386,7 @@ def __init__(
373386
url: Optional[str] = None,
374387
environment: Optional[Environment] = None,
375388
disabled: bool = False,
389+
release: Optional[str] = None,
376390
):
377391
super().__init__(
378392
batch_size=batch_size,
@@ -381,6 +395,7 @@ def __init__(
381395
url=url,
382396
disabled=disabled,
383397
environment=environment,
398+
release=release,
384399
)
385400

386401
def flush(self):
@@ -407,6 +422,7 @@ def __init__(
407422
url: Optional[str] = None,
408423
environment: Optional[Environment] = None,
409424
disabled: bool = False,
425+
release: Optional[str] = None,
410426
):
411427
super().__init__(
412428
batch_size=batch_size,
@@ -415,6 +431,7 @@ def __init__(
415431
url=url,
416432
disabled=disabled,
417433
environment=environment,
434+
release=release,
418435
)
419436

420437
async def flush(self):

literalai/observability/step.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ def __init__(
379379
processor: Optional["EventProcessor"] = None,
380380
tags: Optional[List[str]] = None,
381381
root_run_id: Optional[str] = None,
382+
metadata: Optional[Dict] = None,
382383
):
383384
from time import sleep
384385

@@ -400,6 +401,8 @@ def __init__(
400401
self.parent_id = parent_id
401402

402403
self.tags = tags
404+
if metadata:
405+
self.metadata = metadata
403406

404407
def start(self):
405408
active_steps = active_steps_var.get()
@@ -540,6 +543,7 @@ async def __aenter__(self):
540543
parent_id=self.parent_id,
541544
thread_id=self.thread_id,
542545
root_run_id=self.root_run_id,
546+
metadata=self.kwargs.get("metadata", None),
543547
**self.kwargs,
544548
)
545549

@@ -566,6 +570,7 @@ def __enter__(self) -> Step:
566570
parent_id=self.parent_id,
567571
thread_id=self.thread_id,
568572
root_run_id=self.root_run_id,
573+
metadata=self.kwargs.get("metadata", None),
569574
**self.kwargs,
570575
)
571576

literalai/observability/thread.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@ def to_dict(self) -> ThreadDict:
102102
"tags": self.tags,
103103
"name": self.name,
104104
"steps": [step.to_dict() for step in self.steps] if self.steps else [],
105-
"participant": UserDict(
106-
id=self.participant_id, identifier=self.participant_identifier
107-
)
108-
if self.participant_id
109-
else UserDict(),
105+
"participant": (
106+
UserDict(id=self.participant_id, identifier=self.participant_identifier)
107+
if self.participant_id
108+
else UserDict()
109+
),
110110
"createdAt": getattr(self, "created_at", None),
111111
}
112112

@@ -163,7 +163,12 @@ def upsert(self):
163163
"id": thread_data["id"],
164164
"name": thread_data["name"],
165165
}
166-
if metadata := thread_data.get("metadata"):
166+
167+
metadata = {
168+
**(self.client.global_metadata or {}),
169+
**(thread_data.get("metadata") or {}),
170+
}
171+
if metadata:
167172
thread_data_to_upsert["metadata"] = metadata
168173
if tags := thread_data.get("tags"):
169174
thread_data_to_upsert["tags"] = tags

tests/e2e/test_e2e.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,36 @@ async def a_thread_decorated():
315315
id = await a_thread_decorated()
316316
await assert_delete(id)
317317

318+
@pytest.mark.timeout(5)
319+
async def test_release(self):
320+
url = os.getenv("LITERAL_API_URL", None)
321+
api_key = os.getenv("LITERAL_API_KEY", None)
322+
async_client = AsyncLiteralClient(
323+
batch_size=5, url=url, api_key=api_key, release="test"
324+
)
325+
326+
async def assert_delete(thread_id: str, step_id: str):
327+
await async_client.flush()
328+
assert await async_client.api.delete_step(step_id) is True
329+
assert await async_client.api.delete_thread(thread_id) is True
330+
331+
@async_client.thread
332+
def thread_decorated():
333+
@async_client.step(name="foo", type="llm")
334+
def step_decorated():
335+
t = async_client.get_current_thread()
336+
s = async_client.get_current_step()
337+
assert s is not None
338+
assert s.name == "foo"
339+
assert s.type == "llm"
340+
assert s.metadata.get("release") == "test"
341+
return t.id, s.id
342+
343+
return step_decorated()
344+
345+
thread_id, step_id = thread_decorated()
346+
await assert_delete(thread_id, step_id)
347+
318348
@pytest.mark.timeout(5)
319349
async def test_step_decorator(
320350
self, client: LiteralClient, async_client: AsyncLiteralClient
@@ -600,7 +630,9 @@ async def test_prompt(self, async_client: AsyncLiteralClient):
600630
assert prompt.name == "Default"
601631
assert prompt.version == 0
602632
assert prompt.provider == "openai"
603-
assert prompt.url.endswith(f"projects/{project}/playground?name=Default&version=0")
633+
assert prompt.url.endswith(
634+
f"projects/{project}/playground?name=Default&version=0"
635+
)
604636

605637
prompt = await async_client.api.get_prompt(id=prompt.id, version=0)
606638
assert prompt is not None

0 commit comments

Comments
 (0)