Skip to content

Commit 1c4d49d

Browse files
authored
Merge pull request #17 from weavel-ai/aschung01/wea-509-fix-set-generation-name-as-optional-param
fix: set generation name as optional param
2 parents 3e9bb22 + d73bd5c commit 1c4d49d

6 files changed

Lines changed: 90 additions & 29 deletions

File tree

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ You can find our full documentation [here](https://weavel.ai/docs/python-sdk).
2323

2424
## How to use
2525

26-
### Basic Usage
26+
### Option 1: Using OpenAI wrapper
2727

2828
```python
2929
from weavel import WeavelOpenAI as OpenAI
@@ -42,7 +42,40 @@ response = openai.chat.completions.create(
4242

4343
```
4444

45-
### Advanced Usage
45+
### Option 2: Logging inputs/outputs of LLM calls
46+
47+
```python
48+
from weavel import Weavel
49+
from openai import OpenAI
50+
from pydantic import BaseModel
51+
52+
openai = OpenAI()
53+
# initialize Weavel
54+
weavel = Weavel()
55+
56+
class Answer(BaseModel):
57+
reasoning: str
58+
answer: str
59+
60+
question = "What is x if x + 2 = 4?"
61+
response = openai.beta.chat.completions.parse(
62+
model="gpt-4o-2024-08-06",
63+
messages=[
64+
{"role": "system", "content": "You are a math teacher."},
65+
{"role": "user", "content": question}
66+
],
67+
response_format=Answer
68+
).choices[0].message.parsed
69+
70+
# log the generation
71+
weavel.generation(
72+
name="solve-math", # optional
73+
inputs={"question": question},
74+
outputs=response.model_dump()
75+
)
76+
```
77+
78+
### Option 3 (Advanced Usage): OTEL-compatible trace logging
4679

4780
```python
4881
from weavel import Weavel

setup.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name="weavel",
13-
version="1.7.1",
13+
version="1.8.0",
1414
packages=find_namespace_packages(),
1515
entry_points={},
1616
description="Weavel, Prompt Optimization and Evaluation for LLM Applications",
@@ -32,8 +32,6 @@
3232
"termcolor",
3333
"watchdog",
3434
"readerwriterlock",
35-
"pendulum",
36-
"httpx[http2]",
3735
"nest_asyncio",
3836
],
3937
python_requires=">=3.8.10",
@@ -47,6 +45,6 @@
4745
"dataset curation",
4846
"prompt engineering",
4947
"prompt optimization",
50-
"AI Prompt Engineer"
48+
"AI Prompt Engineer",
5149
],
5250
)

weavel/_body.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ class CaptureRecordBody(BaseRecordBody):
132132

133133

134134
class CaptureObservationBody(BaseObservationBody):
135-
name: str
135+
name: Optional[str] = Field(
136+
default=None,
137+
description="The name of the observation. Optional.",
138+
)
136139

137140

138141
class CaptureMessageBody(CaptureRecordBody):
@@ -262,6 +265,22 @@ class CaptureGenerationBody(CaptureObservationBody):
262265
default=None,
263266
description="The outputs of the generation. Optional.",
264267
)
268+
messages: Optional[List[Dict[str, str]]] = Field(
269+
default=None,
270+
description="The messages of the generation. Optional.",
271+
)
272+
model: Optional[str] = Field(
273+
default=None,
274+
description="The model of the generation. Optional.",
275+
)
276+
latency: Optional[float] = Field(
277+
default=None,
278+
description="The latency of the generation. Optional.",
279+
)
280+
cost: Optional[float] = Field(
281+
default=None,
282+
description="The cost of the generation. Optional.",
283+
)
265284
prompt_name: Optional[str] = Field(
266285
default=None,
267286
description="The name of the prompt. Optional.",

weavel/_worker.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,15 @@ def capture_generation(
341341
self,
342342
observation_id: str,
343343
created_at: datetime,
344-
name: str,
344+
name: Optional[str] = None,
345345
record_id: Optional[str] = None,
346346
inputs: Optional[Union[Dict[str, Any], List[Any], str]] = None,
347347
outputs: Optional[Union[Dict[str, Any], List[Any], str]] = None,
348+
messages: Optional[List[Dict[str, str]]] = None,
348349
metadata: Optional[Dict[str, str]] = None,
350+
model: Optional[str] = None,
351+
latency: Optional[float] = None,
352+
cost: Optional[float] = None,
349353
parent_observation_id: Optional[str] = None,
350354
prompt_name: Optional[str] = None,
351355
):
@@ -358,6 +362,10 @@ def capture_generation(
358362
name=name,
359363
inputs=inputs,
360364
outputs=outputs,
365+
messages=messages,
366+
model=model,
367+
latency=latency,
368+
cost=cost,
361369
metadata=metadata,
362370
parent_observation_id=parent_observation_id,
363371
prompt_name=prompt_name,
@@ -374,6 +382,7 @@ async def acapture_generation(
374382
record_id: Optional[str] = None,
375383
inputs: Optional[Union[Dict[str, Any], List[Any], str]] = None,
376384
outputs: Optional[Union[Dict[str, Any], List[Any], str]] = None,
385+
messages: Optional[List[Dict[str, str]]] = None,
377386
metadata: Optional[Dict[str, str]] = None,
378387
parent_observation_id: Optional[str] = None,
379388
prompt_name: Optional[str] = None,
@@ -387,6 +396,7 @@ async def acapture_generation(
387396
name=name,
388397
inputs=inputs,
389398
outputs=outputs,
399+
messages=messages,
390400
metadata=metadata,
391401
parent_observation_id=parent_observation_id,
392402
prompt_name=prompt_name,

weavel/client.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def generation(
190190
name: Optional[str] = None,
191191
inputs: Optional[Union[Dict[str, Any], List[Any], str]] = None,
192192
outputs: Optional[Union[Dict[str, Any], List[Any], str]] = None,
193+
messages: Optional[List[Dict[str, str]]] = None,
193194
metadata: Optional[Dict[str, Any]] = None,
194195
parent_observation_id: Optional[str] = None,
195196
prompt_name: Optional[str] = None,
@@ -212,16 +213,11 @@ def generation(
212213
name=name,
213214
inputs=inputs,
214215
outputs=outputs,
216+
messages=messages,
215217
metadata=metadata,
216218
parent_observation_id=parent_observation_id,
217219
prompt_name=prompt_name,
218220
)
219-
if (
220-
record_id or (record_id is None and parent_observation_id)
221-
) and name is None:
222-
raise ValueError(
223-
"If you want to create a new generation, you must provide a name."
224-
)
225221

226222
if record_id is not None or (
227223
record_id is None and parent_observation_id is not None
@@ -240,6 +236,7 @@ def generation(
240236
name=name,
241237
inputs=inputs,
242238
outputs=outputs,
239+
messages=messages,
243240
metadata=metadata,
244241
parent_observation_id=parent_observation_id,
245242
prompt_name=prompt_name,
@@ -251,6 +248,7 @@ def generation(
251248
name=name,
252249
inputs=inputs,
253250
outputs=outputs,
251+
messages=messages,
254252
metadata=metadata,
255253
parent_observation_id=parent_observation_id,
256254
prompt_name=prompt_name,
@@ -442,8 +440,8 @@ def create_dataset_items(
442440
return
443441

444442
for item in items:
445-
if not isinstance(item, DatasetItem):
446-
item = DatasetItem(**item)
443+
if isinstance(item, DatasetItem):
444+
item = item.model_dump()
447445

448446
self._worker.create_dataset_items(dataset_name, items)
449447

@@ -462,8 +460,8 @@ async def acreate_dataset_items(
462460
return
463461

464462
for item in items:
465-
if not isinstance(item, DatasetItem):
466-
item = DatasetItem(**item)
463+
if isinstance(item, DatasetItem):
464+
item = item.model_dump()
467465

468466
await self._worker.acreate_dataset_items(dataset_name, items)
469467

weavel/wrapper.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
load_dotenv()
4343

44+
4445
def calculate_cost(model: str, usage: Dict[str, int]) -> float:
4546
if model not in pricing:
4647
# raise ValueError(f"Unknown model: {model}")
@@ -240,6 +241,7 @@ def _handle_non_streaming(self, *args, **kwargs):
240241
cost = calculate_cost(response.model, formatted_response["usage"])
241242
formatted_response["cost"] = cost
242243

244+
# TODO: models, latency, cost shouldn't be logged as inputs. Should have separate fields for each.
243245
self._capture_generation(inputs=kwargs, outputs=formatted_response)
244246
return response
245247

@@ -275,11 +277,11 @@ def parse(self, *args, **kwargs):
275277
# context manager to suppress duplicate logging
276278
with LoggingSuppressor(self._worker):
277279
response = self.original.parse(*args, **kwargs)
278-
280+
279281
end_time = time.time()
280282
latency = end_time - start_time
281283
latency = round(latency, 6)
282-
284+
283285
formatted_response = {
284286
"id": response.id,
285287
"choices": [choice.model_dump() for choice in response.choices],
@@ -291,11 +293,12 @@ def parse(self, *args, **kwargs):
291293
"system_fingerprint": response.system_fingerprint,
292294
"latency": latency,
293295
}
294-
296+
295297
# Calculate and add cost
296298
cost = calculate_cost(response.model, formatted_response["usage"])
297299
formatted_response["cost"] = cost
298-
300+
301+
# TODO: models, latency, cost shouldn't be logged as inputs. Should have separate fields for each.
299302
self._worker.capture_generation(
300303
observation_id=str(uuid4()),
301304
created_at=datetime.now(timezone.utc),
@@ -304,7 +307,7 @@ def parse(self, *args, **kwargs):
304307
inputs=kwargs,
305308
outputs=formatted_response,
306309
)
307-
310+
308311
return response
309312

310313
self.chat.completions = CustomChatCompletions(
@@ -442,7 +445,7 @@ async def _handle_streaming(self, *args, **kwargs):
442445
usage = accumulated_response["usage"]
443446
cost = calculate_cost(model, usage)
444447
accumulated_response["cost"] = cost
445-
448+
446449
if self._worker.capture_generation is not None:
447450
await self._capture_generation(
448451
inputs=kwargs, outputs=accumulated_response
@@ -478,7 +481,7 @@ async def _handle_non_streaming(self, *args, **kwargs):
478481
inputs=kwargs, outputs=formatted_response
479482
)
480483
return response
481-
484+
482485
async def _capture_generation(
483486
self,
484487
inputs,
@@ -507,14 +510,14 @@ def __init__(self, original, weavel_api_key: str, base_url: Optional[str]):
507510
async def parse(self, *args, **kwargs):
508511
header = kwargs.pop("headers", {})
509512
start_time = time.time()
510-
513+
511514
async with AsyncLoggingSuppressor(self._worker):
512515
response = await self.original.parse(*args, **kwargs)
513-
516+
514517
end_time = time.time()
515518
latency = end_time - start_time
516519
latency = round(latency, 6)
517-
520+
518521
formatted_response = {
519522
"id": response.id,
520523
"choices": [choice.model_dump() for choice in response.choices],
@@ -526,7 +529,7 @@ async def parse(self, *args, **kwargs):
526529
"system_fingerprint": response.system_fingerprint,
527530
"latency": latency,
528531
}
529-
532+
530533
# Calculate and add cost
531534
cost = calculate_cost(response.model, formatted_response["usage"])
532535
formatted_response["cost"] = cost

0 commit comments

Comments
 (0)