chatterer 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,608 +1,371 @@
1
- from typing import (
2
- Any,
3
- AsyncIterator,
4
- Iterator,
5
- Optional,
6
- Self,
7
- Type,
8
- TypeAlias,
9
- TypeVar,
10
- )
11
-
12
- from langchain_core.language_models.base import LanguageModelInput
13
- from langchain_core.language_models.chat_models import BaseChatModel
14
- from langchain_core.runnables.config import RunnableConfig
15
- from pydantic import BaseModel, Field
16
-
17
- PydanticModelT = TypeVar("PydanticModelT", bound=BaseModel)
18
- ContentType: TypeAlias = str | list[str | dict[str, Any]]
19
- StructuredOutputType: TypeAlias = dict[str, Any] | BaseModel
20
-
21
-
22
- class Chatterer(BaseModel):
23
- """Language model for generating text from a given input."""
24
-
25
- client: BaseChatModel
26
- structured_output_kwargs: dict[str, Any] = Field(default_factory=dict)
27
-
28
- def __call__(self, messages: LanguageModelInput) -> str:
29
- return self.generate(messages)
30
-
31
- @classmethod
32
- def openai(
33
- cls,
34
- model: str = "gpt-4o-mini",
35
- structured_output_kwargs: Optional[dict[str, Any]] = {"strict": True},
36
- ) -> Self:
37
- from langchain_openai import ChatOpenAI
38
-
39
- return cls(client=ChatOpenAI(model=model), structured_output_kwargs=structured_output_kwargs or {})
40
-
41
- @classmethod
42
- def anthropic(
43
- cls,
44
- model_name: str = "claude-3-7-sonnet-20250219",
45
- structured_output_kwargs: Optional[dict[str, Any]] = None,
46
- ) -> Self:
47
- from langchain_anthropic import ChatAnthropic
48
-
49
- return cls(
50
- client=ChatAnthropic(model_name=model_name, timeout=None, stop=None),
51
- structured_output_kwargs=structured_output_kwargs or {},
52
- )
53
-
54
- @classmethod
55
- def google(
56
- cls,
57
- model: str = "gemini-2.0-flash",
58
- structured_output_kwargs: Optional[dict[str, Any]] = None,
59
- ) -> Self:
60
- from langchain_google_genai import ChatGoogleGenerativeAI
61
-
62
- return cls(
63
- client=ChatGoogleGenerativeAI(model=model),
64
- structured_output_kwargs=structured_output_kwargs or {},
65
- )
66
-
67
- @classmethod
68
- def ollama(
69
- cls,
70
- model: str = "deepseek-r1:1.5b",
71
- structured_output_kwargs: Optional[dict[str, Any]] = None,
72
- ) -> Self:
73
- from langchain_ollama import ChatOllama
74
-
75
- return cls(
76
- client=ChatOllama(model=model),
77
- structured_output_kwargs=structured_output_kwargs or {},
78
- )
79
-
80
- def generate(
81
- self,
82
- messages: LanguageModelInput,
83
- config: Optional[RunnableConfig] = None,
84
- stop: Optional[list[str]] = None,
85
- **kwargs: Any,
86
- ) -> str:
87
- content: ContentType = self.client.invoke(input=messages, config=config, stop=stop, **kwargs).content
88
- if isinstance(content, str):
89
- return content
90
- else:
91
- return "".join(part for part in content if isinstance(part, str))
92
-
93
- async def agenerate(
94
- self,
95
- messages: LanguageModelInput,
96
- config: Optional[RunnableConfig] = None,
97
- stop: Optional[list[str]] = None,
98
- **kwargs: Any,
99
- ) -> str:
100
- content: ContentType = (await self.client.ainvoke(input=messages, config=config, stop=stop, **kwargs)).content
101
- if isinstance(content, str):
102
- return content
103
- else:
104
- return "".join(part for part in content if isinstance(part, str))
105
-
106
- def generate_stream(
107
- self,
108
- messages: LanguageModelInput,
109
- config: Optional[RunnableConfig] = None,
110
- stop: Optional[list[str]] = None,
111
- **kwargs: Any,
112
- ) -> Iterator[str]:
113
- for chunk in self.client.stream(input=messages, config=config, stop=stop, **kwargs):
114
- content: ContentType = chunk.content
115
- if isinstance(content, str):
116
- yield content
117
- elif isinstance(content, list):
118
- for part in content:
119
- if isinstance(part, str):
120
- yield part
121
- else:
122
- continue
123
- else:
124
- continue
125
-
126
- async def agenerate_stream(
127
- self,
128
- messages: LanguageModelInput,
129
- config: Optional[RunnableConfig] = None,
130
- stop: Optional[list[str]] = None,
131
- **kwargs: Any,
132
- ) -> AsyncIterator[str]:
133
- async for chunk in self.client.astream(input=messages, config=config, stop=stop, **kwargs):
134
- content: ContentType = chunk.content
135
- if isinstance(content, str):
136
- yield content
137
- elif isinstance(content, list):
138
- for part in content:
139
- if isinstance(part, str):
140
- yield part
141
- else:
142
- continue
143
- else:
144
- continue
145
-
146
- def generate_pydantic(
147
- self,
148
- response_model: Type[PydanticModelT],
149
- messages: LanguageModelInput,
150
- config: Optional[RunnableConfig] = None,
151
- stop: Optional[list[str]] = None,
152
- **kwargs: Any,
153
- ) -> PydanticModelT:
154
- result: StructuredOutputType = self.client.with_structured_output(
155
- response_model, **self.structured_output_kwargs
156
- ).invoke(input=messages, config=config, stop=stop, **kwargs)
157
- if isinstance(result, response_model):
158
- return result
159
- else:
160
- return response_model.model_validate(result)
161
-
162
- async def agenerate_pydantic(
163
- self,
164
- response_model: Type[PydanticModelT],
165
- messages: LanguageModelInput,
166
- config: Optional[RunnableConfig] = None,
167
- stop: Optional[list[str]] = None,
168
- **kwargs: Any,
169
- ) -> PydanticModelT:
170
- result: StructuredOutputType = await self.client.with_structured_output(
171
- response_model, **self.structured_output_kwargs
172
- ).ainvoke(input=messages, config=config, stop=stop, **kwargs)
173
- if isinstance(result, response_model):
174
- return result
175
- else:
176
- return response_model.model_validate(result)
177
-
178
- def generate_pydantic_stream(
179
- self,
180
- response_model: Type[PydanticModelT],
181
- messages: LanguageModelInput,
182
- config: Optional[RunnableConfig] = None,
183
- stop: Optional[list[str]] = None,
184
- **kwargs: Any,
185
- ) -> Iterator[PydanticModelT]:
186
- try:
187
- import instructor
188
- except ImportError:
189
- raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
190
-
191
- partial_response_model = instructor.Partial[response_model]
192
- for chunk in self.client.with_structured_output(partial_response_model, **self.structured_output_kwargs).stream(
193
- input=messages, config=config, stop=stop, **kwargs
194
- ):
195
- yield response_model.model_validate(chunk)
196
-
197
- async def agenerate_pydantic_stream(
198
- self,
199
- response_model: Type[PydanticModelT],
200
- messages: LanguageModelInput,
201
- config: Optional[RunnableConfig] = None,
202
- stop: Optional[list[str]] = None,
203
- **kwargs: Any,
204
- ) -> AsyncIterator[PydanticModelT]:
205
- try:
206
- import instructor
207
- except ImportError:
208
- raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
209
-
210
- partial_response_model = instructor.Partial[response_model]
211
- async for chunk in self.client.with_structured_output(
212
- partial_response_model, **self.structured_output_kwargs
213
- ).astream(input=messages, config=config, stop=stop, **kwargs):
214
- yield response_model.model_validate(chunk)
215
-
216
-
217
- if __name__ == "__main__":
218
- import asyncio
219
-
220
- # 테스트용 Pydantic 모델 정의
221
- class Propositions(BaseModel):
222
- proposition_topic: str
223
- proposition_content: str
224
-
225
- chatterer = Chatterer.openai()
226
- prompt = "What is the meaning of life?"
227
-
228
- # === Synchronous Tests ===
229
-
230
- # 1. generate
231
- print("=== Synchronous generate ===")
232
- result_sync = chatterer.generate(prompt)
233
- print("Result (generate):", result_sync)
234
-
235
- # 2. __call__
236
- print("\n=== Synchronous __call__ ===")
237
- result_call = chatterer(prompt)
238
- print("Result (__call__):", result_call)
239
-
240
- # 3. generate_stream
241
- print("\n=== Synchronous generate_stream ===")
242
- for i, chunk in enumerate(chatterer.generate_stream(prompt)):
243
- print(f"Chunk {i}:", chunk)
244
-
245
- # 4. generate_pydantic
246
- print("\n=== Synchronous generate_pydantic ===")
247
- try:
248
- result_pydantic = chatterer.generate_pydantic(Propositions, prompt)
249
- print("Result (generate_pydantic):", result_pydantic)
250
- except Exception as e:
251
- print("Error in generate_pydantic:", e)
252
-
253
- # 5. generate_pydantic_stream
254
- print("\n=== Synchronous generate_pydantic_stream ===")
255
- try:
256
- for i, chunk in enumerate(chatterer.generate_pydantic_stream(Propositions, prompt)):
257
- print(f"Pydantic Chunk {i}:", chunk)
258
- except Exception as e:
259
- print("Error in generate_pydantic_stream:", e)
260
-
261
- # === Asynchronous Tests ===
262
-
263
- # Async helper function to enumerate async iterator
264
- async def async_enumerate(aiter: AsyncIterator[Any], start: int = 0) -> AsyncIterator[tuple[int, Any]]:
265
- i = start
266
- async for item in aiter:
267
- yield i, item
268
- i += 1
269
-
270
- async def run_async_tests():
271
- # 6. agenerate
272
- print("\n=== Asynchronous agenerate ===")
273
- result_async = await chatterer.agenerate(prompt)
274
- print("Result (agenerate):", result_async)
275
-
276
- # 7. agenerate_stream
277
- print("\n=== Asynchronous agenerate_stream ===")
278
- async for i, chunk in async_enumerate(chatterer.agenerate_stream(prompt)):
279
- print(f"Async Chunk {i}:", chunk)
280
-
281
- # 8. agenerate_pydantic
282
- print("\n=== Asynchronous agenerate_pydantic ===")
283
- try:
284
- result_async_pydantic = await chatterer.agenerate_pydantic(Propositions, prompt)
285
- print("Result (agenerate_pydantic):", result_async_pydantic)
286
- except Exception as e:
287
- print("Error in agenerate_pydantic:", e)
288
-
289
- # 9. agenerate_pydantic_stream
290
- print("\n=== Asynchronous agenerate_pydantic_stream ===")
291
- try:
292
- i = 0
293
- async for chunk in chatterer.agenerate_pydantic_stream(Propositions, prompt):
294
- print(f"Async Pydantic Chunk {i}:", chunk)
295
- i += 1
296
- except Exception as e:
297
- print("Error in agenerate_pydantic_stream:", e)
298
-
299
- asyncio.run(run_async_tests())
300
-
301
- # === Synchronous generate ===
302
- # Result (generate): The meaning of life is a deeply philosophical question that has been pondered by humans for centuries. Different cultures, religions, and belief systems have their own interpretations of the meaning of life. Some believe that the meaning of life is to seek happiness and pleasure, others believe it is to serve a higher power or fulfill a spiritual purpose. Ultimately, the meaning of life is a personal and subjective concept that each individual must find and define for themselves.
303
-
304
- # === Synchronous __call__ ===
305
- # Result (__call__): The meaning of life is a philosophical and existential question that has been debated for centuries. Different individuals, cultures, and philosophies offer varying perspectives on the purpose and significance of life. Some believe that the meaning of life is to seek happiness, fulfillment, and personal growth, while others believe it is to fulfill a higher spiritual or moral purpose. Ultimately, the meaning of life is a deeply personal and subjective concept that each individual must explore and discover for themselves.
306
-
307
- # === Synchronous generate_stream ===
308
- # Chunk 0:
309
- # Chunk 1: The
310
- # Chunk 2: meaning
311
- # Chunk 3: of
312
- # Chunk 4: life
313
- # Chunk 5: is
314
- # Chunk 6: a
315
- # Chunk 7: complex
316
- # Chunk 8: and
317
- # Chunk 9: deeply
318
- # Chunk 10: personal
319
- # Chunk 11: question
320
- # Chunk 12: that
321
- # Chunk 13: has
322
- # Chunk 14: been
323
- # Chunk 15: debated
324
- # Chunk 16: by
325
- # Chunk 17: philosophers
326
- # Chunk 18: ,
327
- # Chunk 19: theolog
328
- # Chunk 20: ians
329
- # Chunk 21: ,
330
- # Chunk 22: and
331
- # Chunk 23: individuals
332
- # Chunk 24: throughout
333
- # Chunk 25: history
334
- # Chunk 26: .
335
- # Chunk 27: The
336
- # Chunk 28: answer
337
- # Chunk 29: to
338
- # Chunk 30: this
339
- # Chunk 31: question
340
- # Chunk 32: can
341
- # Chunk 33: vary
342
- # Chunk 34: greatly
343
- # Chunk 35: depending
344
- # Chunk 36: on
345
- # Chunk 37: one
346
- # Chunk 38: 's
347
- # Chunk 39: beliefs
348
- # Chunk 40: ,
349
- # Chunk 41: values
350
- # Chunk 42: ,
351
- # Chunk 43: and
352
- # Chunk 44: experiences
353
- # Chunk 45: .
354
- # Chunk 46: Some
355
- # Chunk 47: may
356
- # Chunk 48: find
357
- # Chunk 49: meaning
358
- # Chunk 50: in
359
- # Chunk 51: pursuing
360
- # Chunk 52: personal
361
- # Chunk 53: happiness
362
- # Chunk 54: and
363
- # Chunk 55: fulfillment
364
- # Chunk 56: ,
365
- # Chunk 57: others
366
- # Chunk 58: may
367
- # Chunk 59: find
368
- # Chunk 60: meaning
369
- # Chunk 61: in
370
- # Chunk 62: contributing
371
- # Chunk 63: to
372
- # Chunk 64: the
373
- # Chunk 65: greater
374
- # Chunk 66: good
375
- # Chunk 67: of
376
- # Chunk 68: society
377
- # Chunk 69: ,
378
- # Chunk 70: while
379
- # Chunk 71: others
380
- # Chunk 72: may
381
- # Chunk 73: find
382
- # Chunk 74: meaning
383
- # Chunk 75: in
384
- # Chunk 76: spiritual
385
- # Chunk 77: or
386
- # Chunk 78: religious
387
- # Chunk 79: beliefs
388
- # Chunk 80: .
389
- # Chunk 81: Ultimately
390
- # Chunk 82: ,
391
- # Chunk 83: the
392
- # Chunk 84: meaning
393
- # Chunk 85: of
394
- # Chunk 86: life
395
- # Chunk 87: is
396
- # Chunk 88: subjective
397
- # Chunk 89: and
398
- # Chunk 90: can
399
- # Chunk 91: differ
400
- # Chunk 92: from
401
- # Chunk 93: person
402
- # Chunk 94: to
403
- # Chunk 95: person
404
- # Chunk 96: .
405
- # Chunk 97:
406
-
407
- # === Synchronous generate_pydantic ===
408
- # C:\Users\cosogi\chatterer\.venv\Lib\site-packages\langchain_openai\chat_models\base.py:1390: UserWarning: Cannot use method='json_schema' with model gpt-3.5-turbo since it doesn't support OpenAI's Structured Output API. You can see supported models here: https://platform.openai.com/docs/guides/structured-outputs#supported-models. To fix this warning, set `method='function_calling'. Overriding to method='function_calling'.
409
- # warnings.warn(
410
- # Result (generate_pydantic): proposition_topic='meaning of life' proposition_content='The meaning of life is a philosophical question that has been debated by thinkers and scholars for centuries. There are different perspectives on the meaning of life, including religious, existential, and philosophical views. Some argue that the meaning of life is to seek happiness and fulfillment, while others believe it is to fulfill a higher purpose or serve others. Ultimately, the meaning of life is subjective and may vary depending on individual beliefs and values.'
411
-
412
- # === Synchronous generate_pydantic_stream ===
413
- # C:\Users\cosogi\chatterer\.venv\Lib\site-packages\langchain_openai\chat_models\base.py:1390: UserWarning: Cannot use method='json_schema' with model gpt-3.5-turbo since it doesn't support OpenAI's Structured Output API. You can see supported models here: https://platform.openai.com/docs/guides/structured-outputs#supported-models. To fix this warning, set `method='function_calling'. Overriding to method='function_calling'.
414
- # warnings.warn(
415
- # Pydantic Chunk 0: proposition_topic='Meaning of Life' proposition_content=''
416
- # Pydantic Chunk 1: proposition_topic='Meaning of Life' proposition_content='The'
417
- # Pydantic Chunk 2: proposition_topic='Meaning of Life' proposition_content='The meaning'
418
- # Pydantic Chunk 3: proposition_topic='Meaning of Life' proposition_content='The meaning of'
419
- # Pydantic Chunk 4: proposition_topic='Meaning of Life' proposition_content='The meaning of life'
420
- # Pydantic Chunk 5: proposition_topic='Meaning of Life' proposition_content='The meaning of life is'
421
- # Pydantic Chunk 6: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a'
422
- # Pydantic Chunk 7: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical'
423
- # Pydantic Chunk 8: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and'
424
- # Pydantic Chunk 9: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential'
425
- # Pydantic Chunk 10: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question'
426
- # Pydantic Chunk 11: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that'
427
- # Pydantic Chunk 12: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has'
428
- # Pydantic Chunk 13: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been'
429
- # Pydantic Chunk 14: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated'
430
- # Pydantic Chunk 15: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by'
431
- # Pydantic Chunk 16: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars'
432
- # Pydantic Chunk 17: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars,'
433
- # Pydantic Chunk 18: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers'
434
- # Pydantic Chunk 19: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers,'
435
- # Pydantic Chunk 20: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and'
436
- # Pydantic Chunk 21: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals'
437
- # Pydantic Chunk 22: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout'
438
- # Pydantic Chunk 23: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history'
439
- # Pydantic Chunk 24: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history.'
440
- # Pydantic Chunk 25: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It'
441
- # Pydantic Chunk 26: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers'
442
- # Pydantic Chunk 27: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to'
443
- # Pydantic Chunk 28: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the'
444
- # Pydantic Chunk 29: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose'
445
- # Pydantic Chunk 30: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose,'
446
- # Pydantic Chunk 31: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance'
447
- # Pydantic Chunk 32: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance,'
448
- # Pydantic Chunk 33: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or'
449
- # Pydantic Chunk 34: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or value'
450
- # Pydantic Chunk 35: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or value of'
451
- # Pydantic Chunk 36: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or value of human'
452
- # Pydantic Chunk 37: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or value of human existence'
453
- # Pydantic Chunk 38: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or value of human existence and'
454
- # Pydantic Chunk 39: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or value of human existence and consciousness'
455
- # Pydantic Chunk 40: proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that has been debated by scholars, philosophers, and individuals throughout history. It refers to the purpose, significance, or value of human existence and consciousness.'
456
-
457
- # === Asynchronous agenerate ===
458
- # Result (agenerate): The meaning of life is a complex and subjective philosophical question that has been debated throughout history. Different individuals and cultures may have different beliefs and perspectives on the meaning of life. Some may believe that the meaning of life is to seek happiness and fulfillment, others may believe it is to serve a higher power or contribute to the greater good of society. Ultimately, the meaning of life is a highly personal and individualistic concept that each person must determine for themselves.
459
-
460
- # === Asynchronous agenerate_stream ===
461
- # Async Chunk 0:
462
- # Async Chunk 1: This
463
- # Async Chunk 2: is
464
- # Async Chunk 3: a
465
- # Async Chunk 4: deeply
466
- # Async Chunk 5: philosophical
467
- # Async Chunk 6: question
468
- # Async Chunk 7: that
469
- # Async Chunk 8: has
470
- # Async Chunk 9: puzzled
471
- # Async Chunk 10: humans
472
- # Async Chunk 11: for
473
- # Async Chunk 12: centuries
474
- # Async Chunk 13: .
475
- # Async Chunk 14: The
476
- # Async Chunk 15: meaning
477
- # Async Chunk 16: of
478
- # Async Chunk 17: life
479
- # Async Chunk 18: is
480
- # Async Chunk 19: a
481
- # Async Chunk 20: highly
482
- # Async Chunk 21: individual
483
- # Async Chunk 22: and
484
- # Async Chunk 23: subjective
485
- # Async Chunk 24: concept
486
- # Async Chunk 25: ,
487
- # Async Chunk 26: with
488
- # Async Chunk 27: different
489
- # Async Chunk 28: people
490
- # Async Chunk 29: finding
491
- # Async Chunk 30: meaning
492
- # Async Chunk 31: in
493
- # Async Chunk 32: different
494
- # Async Chunk 33: things
495
- # Async Chunk 34: .
496
- # Async Chunk 35: Some
497
- # Async Chunk 36: may
498
- # Async Chunk 37: find
499
- # Async Chunk 38: meaning
500
- # Async Chunk 39: in
501
- # Async Chunk 40: their
502
- # Async Chunk 41: relationships
503
- # Async Chunk 42: ,
504
- # Async Chunk 43: their
505
- # Async Chunk 44: work
506
- # Async Chunk 45: ,
507
- # Async Chunk 46: their
508
- # Async Chunk 47: beliefs
509
- # Async Chunk 48: ,
510
- # Async Chunk 49: or
511
- # Async Chunk 50: their
512
- # Async Chunk 51: experiences
513
- # Async Chunk 52: .
514
- # Async Chunk 53: Others
515
- # Async Chunk 54: may
516
- # Async Chunk 55: find
517
- # Async Chunk 56: meaning
518
- # Async Chunk 57: in
519
- # Async Chunk 58: the
520
- # Async Chunk 59: pursuit
521
- # Async Chunk 60: of
522
- # Async Chunk 61: personal
523
- # Async Chunk 62: growth
524
- # Async Chunk 63: ,
525
- # Async Chunk 64: knowledge
526
- # Async Chunk 65: ,
527
- # Async Chunk 66: or
528
- # Async Chunk 67: happiness
529
- # Async Chunk 68: .
530
- # Async Chunk 69: Ultimately
531
- # Async Chunk 70: ,
532
- # Async Chunk 71: the
533
- # Async Chunk 72: meaning
534
- # Async Chunk 73: of
535
- # Async Chunk 74: life
536
- # Async Chunk 75: is
537
- # Async Chunk 76: something
538
- # Async Chunk 77: that
539
- # Async Chunk 78: each
540
- # Async Chunk 79: person
541
- # Async Chunk 80: must
542
- # Async Chunk 81: discover
543
- # Async Chunk 82: for
544
- # Async Chunk 83: themselves
545
- # Async Chunk 84: through
546
- # Async Chunk 85: intros
547
- # Async Chunk 86: pection
548
- # Async Chunk 87: ,
549
- # Async Chunk 88: reflection
550
- # Async Chunk 89: ,
551
- # Async Chunk 90: and
552
- # Async Chunk 91: experience
553
- # Async Chunk 92: .
554
- # Async Chunk 93:
555
-
556
- # === Asynchronous agenerate_pydantic ===
557
- # C:\Users\cosogi\chatterer\.venv\Lib\site-packages\langchain_openai\chat_models\base.py:1390: UserWarning: Cannot use method='json_schema' with model gpt-3.5-turbo since it doesn't support OpenAI's Structured Output API. You can see supported models here: https://platform.openai.com/docs/guides/structured-outputs#supported-models. To fix this warning, set `method='function_calling'. Overriding to method='function_calling'.
558
- # warnings.warn(
559
- # Result (agenerate_pydantic): proposition_topic='Meaning of Life' proposition_content='The meaning of life is a philosophical and existential question that explores the purpose and significance of human existence. It is a complex and subjective concept that has been debated by philosophers, theologians, and thinkers throughout history. Some believe that the meaning of life is to seek happiness, fulfillment, or spiritual enlightenment, while others argue that it is to make a positive impact on the world or to find meaning in personal relationships and experiences. Ultimately, the meaning of life is a deeply personal and reflective question that each individual must contemplate and define for themselves.'
560
-
561
- # === Asynchronous agenerate_pydantic_stream ===
562
- # C:\Users\cosogi\chatterer\.venv\Lib\site-packages\langchain_openai\chat_models\base.py:1390: UserWarning: Cannot use method='json_schema' with model gpt-3.5-turbo since it doesn't support OpenAI's Structured Output API. You can see supported models here: https://platform.openai.com/docs/guides/structured-outputs#supported-models. To fix this warning, set `method='function_calling'. Overriding to method='function_calling'.
563
- # warnings.warn(
564
- # Async Pydantic Chunk 0: proposition_topic='The Meaning of Life' proposition_content=''
565
- # Async Pydantic Chunk 1: proposition_topic='The Meaning of Life' proposition_content='The'
566
- # Async Pydantic Chunk 2: proposition_topic='The Meaning of Life' proposition_content='The meaning'
567
- # Async Pydantic Chunk 3: proposition_topic='The Meaning of Life' proposition_content='The meaning of'
568
- # Async Pydantic Chunk 4: proposition_topic='The Meaning of Life' proposition_content='The meaning of life'
569
- # Async Pydantic Chunk 5: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is'
570
- # Async Pydantic Chunk 6: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a'
571
- # Async Pydantic Chunk 7: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical'
572
- # Async Pydantic Chunk 8: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question'
573
- # Async Pydantic Chunk 9: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that'
574
- # Async Pydantic Chunk 10: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks'
575
- # Async Pydantic Chunk 11: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to'
576
- # Async Pydantic Chunk 12: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand'
577
- # Async Pydantic Chunk 13: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the'
578
- # Async Pydantic Chunk 14: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose'
579
- # Async Pydantic Chunk 15: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and'
580
- # Async Pydantic Chunk 16: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance'
581
- # Async Pydantic Chunk 17: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of'
582
- # Async Pydantic Chunk 18: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human'
583
- # Async Pydantic Chunk 19: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence'
584
- # Async Pydantic Chunk 20: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence.'
585
- # Async Pydantic Chunk 21: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It'
586
- # Async Pydantic Chunk 22: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is'
587
- # Async Pydantic Chunk 23: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a'
588
- # Async Pydantic Chunk 24: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound'
589
- # Async Pydantic Chunk 25: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and'
590
- # Async Pydantic Chunk 26: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex'
591
- # Async Pydantic Chunk 27: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic'
592
- # Async Pydantic Chunk 28: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that'
593
- # Async Pydantic Chunk 29: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has'
594
- # Async Pydantic Chunk 30: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been'
595
- # Async Pydantic Chunk 31: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated'
596
- # Async Pydantic Chunk 32: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and'
597
- # Async Pydantic Chunk 33: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored'
598
- # Async Pydantic Chunk 34: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by'
599
- # Async Pydantic Chunk 35: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers'
600
- # Async Pydantic Chunk 36: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers,'
601
- # Async Pydantic Chunk 37: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theolog'
602
- # Async Pydantic Chunk 38: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theologians'
603
- # Async Pydantic Chunk 39: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theologians,'
604
- # Async Pydantic Chunk 40: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theologians, and'
605
- # Async Pydantic Chunk 41: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theologians, and thinkers'
606
- # Async Pydantic Chunk 42: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theologians, and thinkers throughout'
607
- # Async Pydantic Chunk 43: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theologians, and thinkers throughout history'
608
- # Async Pydantic Chunk 44: proposition_topic='The Meaning of Life' proposition_content='The meaning of life is a philosophical question that seeks to understand the purpose and significance of human existence. It is a profound and complex topic that has been debated and explored by philosophers, theologians, and thinkers throughout history.'
1
+ from typing import (
2
+ TYPE_CHECKING,
3
+ Any,
4
+ AsyncIterator,
5
+ Iterator,
6
+ Optional,
7
+ Self,
8
+ Type,
9
+ TypeAlias,
10
+ TypeVar,
11
+ cast,
12
+ overload,
13
+ )
14
+
15
+ from langchain_core.language_models.base import LanguageModelInput
16
+ from langchain_core.language_models.chat_models import BaseChatModel
17
+ from langchain_core.runnables.base import Runnable
18
+ from langchain_core.runnables.config import RunnableConfig
19
+ from pydantic import BaseModel, Field
20
+
21
+ from .messages import AIMessage, BaseMessage, HumanMessage
22
+
23
+ if TYPE_CHECKING:
24
+ from instructor import Partial
25
+
26
+ PydanticModelT = TypeVar("PydanticModelT", bound=BaseModel)
27
+ StructuredOutputType: TypeAlias = dict[object, object] | BaseModel
28
+
29
+ DEFAULT_IMAGE_DESCRIPTION_INSTRUCTION = "Just describe all the details you see in the image in few sentences."
30
+
31
+
32
+ class Chatterer(BaseModel):
33
+ """Language model for generating text from a given input."""
34
+
35
+ client: BaseChatModel
36
+ structured_output_kwargs: dict[str, Any] = Field(default_factory=dict)
37
+
38
+ @overload
39
+ def __call__(
40
+ self,
41
+ messages: LanguageModelInput,
42
+ response_model: Type[PydanticModelT],
43
+ config: Optional[RunnableConfig] = None,
44
+ stop: Optional[list[str]] = None,
45
+ **kwargs: Any,
46
+ ) -> PydanticModelT: ...
47
+
48
+ @overload
49
+ def __call__(
50
+ self,
51
+ messages: LanguageModelInput,
52
+ response_model: None = None,
53
+ config: Optional[RunnableConfig] = None,
54
+ stop: Optional[list[str]] = None,
55
+ **kwargs: Any,
56
+ ) -> str: ...
57
+
58
+ def __call__(
59
+ self,
60
+ messages: LanguageModelInput,
61
+ response_model: Optional[Type[PydanticModelT]] = None,
62
+ config: Optional[RunnableConfig] = None,
63
+ stop: Optional[list[str]] = None,
64
+ **kwargs: Any,
65
+ ) -> str | PydanticModelT:
66
+ if response_model:
67
+ return self.generate_pydantic(response_model, messages, config, stop, **kwargs)
68
+ return self.client.invoke(input=messages, config=config, stop=stop, **kwargs).text()
69
+
70
+ @classmethod
71
+ def openai(
72
+ cls,
73
+ model: str = "gpt-4o-mini",
74
+ structured_output_kwargs: Optional[dict[str, Any]] = {"strict": True},
75
+ ) -> Self:
76
+ from langchain_openai import ChatOpenAI
77
+
78
+ return cls(client=ChatOpenAI(model=model), structured_output_kwargs=structured_output_kwargs or {})
79
+
80
+ @classmethod
81
+ def anthropic(
82
+ cls,
83
+ model_name: str = "claude-3-7-sonnet-20250219",
84
+ structured_output_kwargs: Optional[dict[str, Any]] = None,
85
+ ) -> Self:
86
+ from langchain_anthropic import ChatAnthropic
87
+
88
+ return cls(
89
+ client=ChatAnthropic(model_name=model_name, timeout=None, stop=None),
90
+ structured_output_kwargs=structured_output_kwargs or {},
91
+ )
92
+
93
+ @classmethod
94
+ def google(
95
+ cls,
96
+ model: str = "gemini-2.0-flash",
97
+ structured_output_kwargs: Optional[dict[str, Any]] = None,
98
+ ) -> Self:
99
+ from langchain_google_genai import ChatGoogleGenerativeAI
100
+
101
+ return cls(
102
+ client=ChatGoogleGenerativeAI(model=model),
103
+ structured_output_kwargs=structured_output_kwargs or {},
104
+ )
105
+
106
+ @classmethod
107
+ def ollama(
108
+ cls,
109
+ model: str = "deepseek-r1:1.5b",
110
+ structured_output_kwargs: Optional[dict[str, Any]] = None,
111
+ ) -> Self:
112
+ from langchain_ollama import ChatOllama
113
+
114
+ return cls(
115
+ client=ChatOllama(model=model),
116
+ structured_output_kwargs=structured_output_kwargs or {},
117
+ )
118
+
119
+ def generate(
120
+ self,
121
+ messages: LanguageModelInput,
122
+ config: Optional[RunnableConfig] = None,
123
+ stop: Optional[list[str]] = None,
124
+ **kwargs: Any,
125
+ ) -> str:
126
+ return self.client.invoke(input=messages, config=config, stop=stop, **kwargs).text()
127
+
128
+ async def agenerate(
129
+ self,
130
+ messages: LanguageModelInput,
131
+ config: Optional[RunnableConfig] = None,
132
+ stop: Optional[list[str]] = None,
133
+ **kwargs: Any,
134
+ ) -> str:
135
+ return (await self.client.ainvoke(input=messages, config=config, stop=stop, **kwargs)).text()
136
+
137
+ def generate_stream(
138
+ self,
139
+ messages: LanguageModelInput,
140
+ config: Optional[RunnableConfig] = None,
141
+ stop: Optional[list[str]] = None,
142
+ **kwargs: Any,
143
+ ) -> Iterator[str]:
144
+ for chunk in self.client.stream(input=messages, config=config, stop=stop, **kwargs):
145
+ yield chunk.text()
146
+
147
+ async def agenerate_stream(
148
+ self,
149
+ messages: LanguageModelInput,
150
+ config: Optional[RunnableConfig] = None,
151
+ stop: Optional[list[str]] = None,
152
+ **kwargs: Any,
153
+ ) -> AsyncIterator[str]:
154
+ async for chunk in self.client.astream(input=messages, config=config, stop=stop, **kwargs):
155
+ yield chunk.text()
156
+
157
+ def generate_pydantic(
158
+ self,
159
+ response_model: Type[PydanticModelT],
160
+ messages: LanguageModelInput,
161
+ config: Optional[RunnableConfig] = None,
162
+ stop: Optional[list[str]] = None,
163
+ **kwargs: Any,
164
+ ) -> PydanticModelT:
165
+ result: StructuredOutputType = with_structured_output(
166
+ client=self.client,
167
+ response_model=response_model,
168
+ structured_output_kwargs=self.structured_output_kwargs,
169
+ ).invoke(input=messages, config=config, stop=stop, **kwargs)
170
+ if isinstance(result, response_model):
171
+ return result
172
+ else:
173
+ return response_model.model_validate(result)
174
+
175
+ async def agenerate_pydantic(
176
+ self,
177
+ response_model: Type[PydanticModelT],
178
+ messages: LanguageModelInput,
179
+ config: Optional[RunnableConfig] = None,
180
+ stop: Optional[list[str]] = None,
181
+ **kwargs: Any,
182
+ ) -> PydanticModelT:
183
+ result: StructuredOutputType = await with_structured_output(
184
+ client=self.client,
185
+ response_model=response_model,
186
+ structured_output_kwargs=self.structured_output_kwargs,
187
+ ).ainvoke(input=messages, config=config, stop=stop, **kwargs)
188
+ if isinstance(result, response_model):
189
+ return result
190
+ else:
191
+ return response_model.model_validate(result)
192
+
193
+ def generate_pydantic_stream(
194
+ self,
195
+ response_model: Type[PydanticModelT],
196
+ messages: LanguageModelInput,
197
+ config: Optional[RunnableConfig] = None,
198
+ stop: Optional[list[str]] = None,
199
+ **kwargs: Any,
200
+ ) -> Iterator[PydanticModelT]:
201
+ try:
202
+ import instructor
203
+ except ImportError:
204
+ raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
205
+
206
+ partial_response_model = instructor.Partial[response_model]
207
+ for chunk in with_structured_output(
208
+ client=self.client,
209
+ response_model=partial_response_model,
210
+ structured_output_kwargs=self.structured_output_kwargs,
211
+ ).stream(input=messages, config=config, stop=stop, **kwargs):
212
+ yield response_model.model_validate(chunk)
213
+
214
+ async def agenerate_pydantic_stream(
215
+ self,
216
+ response_model: Type[PydanticModelT],
217
+ messages: LanguageModelInput,
218
+ config: Optional[RunnableConfig] = None,
219
+ stop: Optional[list[str]] = None,
220
+ **kwargs: Any,
221
+ ) -> AsyncIterator[PydanticModelT]:
222
+ try:
223
+ import instructor
224
+ except ImportError:
225
+ raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
226
+
227
+ partial_response_model = instructor.Partial[response_model]
228
+ async for chunk in with_structured_output(
229
+ client=self.client,
230
+ response_model=partial_response_model,
231
+ structured_output_kwargs=self.structured_output_kwargs,
232
+ ).astream(input=messages, config=config, stop=stop, **kwargs):
233
+ yield response_model.model_validate(chunk)
234
+
235
+ def describe_image(self, image_url: str, instruction: str = DEFAULT_IMAGE_DESCRIPTION_INSTRUCTION) -> str:
236
+ """
237
+ Create a detailed description of an image using the Vision Language Model.
238
+ - image_url: Image URL to describe
239
+ """
240
+ return self.generate([
241
+ HumanMessage(
242
+ content=[{"type": "text", "text": instruction}, {"type": "image_url", "image_url": {"url": image_url}}],
243
+ )
244
+ ])
245
+
246
+ async def adescribe_image(self, image_url: str, instruction: str = DEFAULT_IMAGE_DESCRIPTION_INSTRUCTION) -> str:
247
+ """
248
+ Create a detailed description of an image using the Vision Language Model asynchronously.
249
+ - image_url: Image URL to describe
250
+ """
251
+ return await self.agenerate([
252
+ HumanMessage(
253
+ content=[{"type": "text", "text": instruction}, {"type": "image_url", "image_url": {"url": image_url}}],
254
+ )
255
+ ])
256
+
257
+ @staticmethod
258
+ def get_num_tokens_from_message(message: BaseMessage) -> Optional[tuple[int, int]]:
259
+ try:
260
+ if isinstance(message, AIMessage) and (usage_metadata := message.usage_metadata):
261
+ input_tokens = int(usage_metadata["input_tokens"])
262
+ output_tokens = int(usage_metadata["output_tokens"])
263
+ else:
264
+ # Dynamic extraction for unknown structures
265
+ input_tokens: Optional[int] = None
266
+ output_tokens: Optional[int] = None
267
+
268
+ def _find_tokens(obj: object) -> None:
269
+ nonlocal input_tokens, output_tokens
270
+ if isinstance(obj, dict):
271
+ for key, value in cast(dict[object, object], obj).items():
272
+ if isinstance(value, int):
273
+ if "input" in str(key) or "prompt" in str(key):
274
+ input_tokens = value
275
+ elif "output" in str(key) or "completion" in str(key):
276
+ output_tokens = value
277
+ else:
278
+ _find_tokens(value)
279
+ elif isinstance(obj, list):
280
+ for item in cast(list[object], obj):
281
+ _find_tokens(item)
282
+
283
+ _find_tokens(message.model_dump())
284
+
285
+ if input_tokens is None or output_tokens is None:
286
+ return None
287
+ return input_tokens, output_tokens
288
+ except Exception:
289
+ return None
290
+
291
+
292
+ def with_structured_output(
293
+ client: BaseChatModel,
294
+ response_model: Type["PydanticModelT | Partial[PydanticModelT]"],
295
+ structured_output_kwargs: dict[str, Any],
296
+ ) -> Runnable[LanguageModelInput, dict[object, object] | BaseModel]:
297
+ return client.with_structured_output(schema=response_model, **structured_output_kwargs) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
298
+
299
+
300
+ if __name__ == "__main__":
301
+ import asyncio
302
+
303
+ # 테스트용 Pydantic 모델 정의
304
+ class Propositions(BaseModel):
305
+ proposition_topic: str
306
+ proposition_content: str
307
+
308
+ chatterer = Chatterer.openai()
309
+ prompt = "What is the meaning of life?"
310
+
311
+ # === Synchronous Tests ===
312
+
313
+ # generate
314
+ print("=== Synchronous generate ===")
315
+ result_sync = chatterer(prompt)
316
+ print("Result (generate):", result_sync)
317
+
318
+ # generate_stream
319
+ print("\n=== Synchronous generate_stream ===")
320
+ for i, chunk in enumerate(chatterer.generate_stream(prompt)):
321
+ print(f"Chunk {i}:", chunk)
322
+
323
+ # generate_pydantic
324
+ print("\n=== Synchronous generate_pydantic ===")
325
+ result_pydantic = chatterer(prompt, Propositions)
326
+ print("Result (generate_pydantic):", result_pydantic)
327
+
328
+ # generate_pydantic_stream
329
+ print("\n=== Synchronous generate_pydantic_stream ===")
330
+ for i, chunk in enumerate(chatterer.generate_pydantic_stream(Propositions, prompt)):
331
+ print(f"Pydantic Chunk {i}:", chunk)
332
+
333
+ # === Asynchronous Tests ===
334
+
335
+ # Async helper function to enumerate async iterator
336
+ async def async_enumerate(aiter: AsyncIterator[Any], start: int = 0) -> AsyncIterator[tuple[int, Any]]:
337
+ i = start
338
+ async for item in aiter:
339
+ yield i, item
340
+ i += 1
341
+
342
+ async def run_async_tests():
343
+ # 6. agenerate
344
+ print("\n=== Asynchronous agenerate ===")
345
+ result_async = await chatterer.agenerate(prompt)
346
+ print("Result (agenerate):", result_async)
347
+
348
+ # 7. agenerate_stream
349
+ print("\n=== Asynchronous agenerate_stream ===")
350
+ async for i, chunk in async_enumerate(chatterer.agenerate_stream(prompt)):
351
+ print(f"Async Chunk {i}:", chunk)
352
+
353
+ # 8. agenerate_pydantic
354
+ print("\n=== Asynchronous agenerate_pydantic ===")
355
+ try:
356
+ result_async_pydantic = await chatterer.agenerate_pydantic(Propositions, prompt)
357
+ print("Result (agenerate_pydantic):", result_async_pydantic)
358
+ except Exception as e:
359
+ print("Error in agenerate_pydantic:", e)
360
+
361
+ # 9. agenerate_pydantic_stream
362
+ print("\n=== Asynchronous agenerate_pydantic_stream ===")
363
+ try:
364
+ i = 0
365
+ async for chunk in chatterer.agenerate_pydantic_stream(Propositions, prompt):
366
+ print(f"Async Pydantic Chunk {i}:", chunk)
367
+ i += 1
368
+ except Exception as e:
369
+ print("Error in agenerate_pydantic_stream:", e)
370
+
371
+ asyncio.run(run_async_tests())