chatterer 0.1.10__py3-none-any.whl → 0.1.12__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.
- chatterer/__init__.py +60 -60
- chatterer/language_model.py +577 -580
- chatterer/messages.py +9 -9
- chatterer/strategies/__init__.py +13 -13
- chatterer/strategies/atom_of_thoughts.py +975 -975
- chatterer/strategies/base.py +14 -14
- chatterer/tools/__init__.py +28 -28
- chatterer/tools/citation_chunking/__init__.py +3 -3
- chatterer/tools/citation_chunking/chunks.py +53 -53
- chatterer/tools/citation_chunking/citation_chunker.py +118 -118
- chatterer/tools/citation_chunking/citations.py +285 -285
- chatterer/tools/citation_chunking/prompt.py +157 -157
- chatterer/tools/citation_chunking/reference.py +26 -26
- chatterer/tools/citation_chunking/utils.py +138 -138
- chatterer/tools/convert_to_text.py +463 -463
- chatterer/tools/webpage_to_markdown/__init__.py +4 -4
- chatterer/tools/webpage_to_markdown/playwright_bot.py +649 -649
- chatterer/tools/webpage_to_markdown/utils.py +334 -329
- chatterer/tools/youtube.py +146 -132
- chatterer/utils/__init__.py +15 -15
- chatterer/utils/code_agent.py +138 -138
- chatterer/utils/image.py +291 -288
- {chatterer-0.1.10.dist-info → chatterer-0.1.12.dist-info}/METADATA +170 -170
- chatterer-0.1.12.dist-info/RECORD +27 -0
- {chatterer-0.1.10.dist-info → chatterer-0.1.12.dist-info}/WHEEL +1 -1
- chatterer-0.1.10.dist-info/RECORD +0 -27
- {chatterer-0.1.10.dist-info → chatterer-0.1.12.dist-info}/top_level.txt +0 -0
chatterer/language_model.py
CHANGED
@@ -1,580 +1,577 @@
|
|
1
|
-
from typing import (
|
2
|
-
TYPE_CHECKING,
|
3
|
-
Any,
|
4
|
-
AsyncIterator,
|
5
|
-
Callable,
|
6
|
-
Iterable,
|
7
|
-
Iterator,
|
8
|
-
Optional,
|
9
|
-
Self,
|
10
|
-
Sequence,
|
11
|
-
Type,
|
12
|
-
TypeAlias,
|
13
|
-
TypeVar,
|
14
|
-
cast,
|
15
|
-
overload,
|
16
|
-
)
|
17
|
-
|
18
|
-
from langchain_core.language_models.base import LanguageModelInput
|
19
|
-
from langchain_core.language_models.chat_models import BaseChatModel
|
20
|
-
from langchain_core.runnables.base import Runnable
|
21
|
-
from langchain_core.runnables.config import RunnableConfig
|
22
|
-
from pydantic import BaseModel, Field
|
23
|
-
|
24
|
-
from .messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
|
25
|
-
from .utils.code_agent import CodeExecutionResult, FunctionSignature, get_default_repl_tool
|
26
|
-
|
27
|
-
if TYPE_CHECKING:
|
28
|
-
from instructor import Partial
|
29
|
-
from langchain_experimental.tools.python.tool import PythonAstREPLTool
|
30
|
-
|
31
|
-
PydanticModelT = TypeVar("PydanticModelT", bound=BaseModel)
|
32
|
-
StructuredOutputType: TypeAlias = dict[object, object] | BaseModel
|
33
|
-
|
34
|
-
DEFAULT_IMAGE_DESCRIPTION_INSTRUCTION = "Provide a detailed description of all visible elements in the image, summarizing key details in a few clear sentences."
|
35
|
-
DEFAULT_CODE_GENERATION_PROMPT = (
|
36
|
-
"You are utilizing a Python code execution tool now.\n"
|
37
|
-
"Your goal is to generate Python code that solves the task efficiently and appends both the code and its output to your context memory.\n"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
41
|
-
"-
|
42
|
-
"-
|
43
|
-
"
|
44
|
-
"
|
45
|
-
"\n
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
"
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
**kwargs
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
**kwargs
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
**kwargs:
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
**kwargs:
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
return result
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
return result
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
return
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
function_signatures = []
|
332
|
-
|
333
|
-
function_signatures
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
structured_output_kwargs:
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
)
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
]
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
if __name__ == "__main__":
|
580
|
-
interactive_shell()
|
1
|
+
from typing import (
|
2
|
+
TYPE_CHECKING,
|
3
|
+
Any,
|
4
|
+
AsyncIterator,
|
5
|
+
Callable,
|
6
|
+
Iterable,
|
7
|
+
Iterator,
|
8
|
+
Optional,
|
9
|
+
Self,
|
10
|
+
Sequence,
|
11
|
+
Type,
|
12
|
+
TypeAlias,
|
13
|
+
TypeVar,
|
14
|
+
cast,
|
15
|
+
overload,
|
16
|
+
)
|
17
|
+
|
18
|
+
from langchain_core.language_models.base import LanguageModelInput
|
19
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
20
|
+
from langchain_core.runnables.base import Runnable
|
21
|
+
from langchain_core.runnables.config import RunnableConfig
|
22
|
+
from pydantic import BaseModel, Field
|
23
|
+
|
24
|
+
from .messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
|
25
|
+
from .utils.code_agent import CodeExecutionResult, FunctionSignature, get_default_repl_tool
|
26
|
+
|
27
|
+
if TYPE_CHECKING:
|
28
|
+
from instructor import Partial
|
29
|
+
from langchain_experimental.tools.python.tool import PythonAstREPLTool
|
30
|
+
|
31
|
+
PydanticModelT = TypeVar("PydanticModelT", bound=BaseModel)
|
32
|
+
StructuredOutputType: TypeAlias = dict[object, object] | BaseModel
|
33
|
+
|
34
|
+
DEFAULT_IMAGE_DESCRIPTION_INSTRUCTION = "Provide a detailed description of all visible elements in the image, summarizing key details in a few clear sentences."
|
35
|
+
DEFAULT_CODE_GENERATION_PROMPT = (
|
36
|
+
"You are utilizing a Python code execution tool now.\n"
|
37
|
+
"Your goal is to generate Python code that solves the task efficiently and appends both the code and its output to your context memory.\n"
|
38
|
+
"\n"
|
39
|
+
"To optimize tool efficiency, follow these guidelines:\n"
|
40
|
+
"- Write concise, efficient code that directly serves the intended purpose.\n"
|
41
|
+
"- Avoid unnecessary operations (e.g., excessive loops, recursion, or heavy computations).\n"
|
42
|
+
"- Handle potential errors gracefully (e.g., using try-except blocks).\n"
|
43
|
+
"\n"
|
44
|
+
"Return your response strictly in the following JSON format:\n"
|
45
|
+
'{\n "code": "<your_python_code_here>"\n}\n\n'
|
46
|
+
)
|
47
|
+
|
48
|
+
|
49
|
+
DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT = (
|
50
|
+
"Below functions are included in global scope and can be used in your code.\n"
|
51
|
+
"Do not try to redefine the function(s).\n"
|
52
|
+
"You don't have to force yourself to use these tools - use them only when you need to.\n"
|
53
|
+
)
|
54
|
+
DEFAULT_FUNCTION_REFERENCE_SEPARATOR = "\n---\n" # Separator to distinguish different function references
|
55
|
+
|
56
|
+
|
57
|
+
class Chatterer(BaseModel):
|
58
|
+
"""Language model for generating text from a given input."""
|
59
|
+
|
60
|
+
client: BaseChatModel
|
61
|
+
structured_output_kwargs: dict[str, Any] = Field(default_factory=dict)
|
62
|
+
|
63
|
+
@overload
|
64
|
+
def __call__(
|
65
|
+
self,
|
66
|
+
messages: LanguageModelInput,
|
67
|
+
response_model: Type[PydanticModelT],
|
68
|
+
config: Optional[RunnableConfig] = None,
|
69
|
+
stop: Optional[list[str]] = None,
|
70
|
+
**kwargs: Any,
|
71
|
+
) -> PydanticModelT: ...
|
72
|
+
|
73
|
+
@overload
|
74
|
+
def __call__(
|
75
|
+
self,
|
76
|
+
messages: LanguageModelInput,
|
77
|
+
response_model: None = None,
|
78
|
+
config: Optional[RunnableConfig] = None,
|
79
|
+
stop: Optional[list[str]] = None,
|
80
|
+
**kwargs: Any,
|
81
|
+
) -> str: ...
|
82
|
+
|
83
|
+
def __call__(
|
84
|
+
self,
|
85
|
+
messages: LanguageModelInput,
|
86
|
+
response_model: Optional[Type[PydanticModelT]] = None,
|
87
|
+
config: Optional[RunnableConfig] = None,
|
88
|
+
stop: Optional[list[str]] = None,
|
89
|
+
**kwargs: Any,
|
90
|
+
) -> str | PydanticModelT:
|
91
|
+
if response_model:
|
92
|
+
return self.generate_pydantic(response_model, messages, config, stop, **kwargs)
|
93
|
+
return self.client.invoke(input=messages, config=config, stop=stop, **kwargs).text()
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def openai(
|
97
|
+
cls,
|
98
|
+
model: str = "gpt-4o-mini",
|
99
|
+
structured_output_kwargs: Optional[dict[str, Any]] = {"strict": True},
|
100
|
+
) -> Self:
|
101
|
+
from langchain_openai import ChatOpenAI
|
102
|
+
|
103
|
+
return cls(client=ChatOpenAI(model=model), structured_output_kwargs=structured_output_kwargs or {})
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
def anthropic(
|
107
|
+
cls,
|
108
|
+
model_name: str = "claude-3-7-sonnet-20250219",
|
109
|
+
structured_output_kwargs: Optional[dict[str, Any]] = None,
|
110
|
+
) -> Self:
|
111
|
+
from langchain_anthropic import ChatAnthropic
|
112
|
+
|
113
|
+
return cls(
|
114
|
+
client=ChatAnthropic(model_name=model_name, timeout=None, stop=None),
|
115
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
116
|
+
)
|
117
|
+
|
118
|
+
@classmethod
|
119
|
+
def google(
|
120
|
+
cls,
|
121
|
+
model: str = "gemini-2.0-flash",
|
122
|
+
structured_output_kwargs: Optional[dict[str, Any]] = None,
|
123
|
+
) -> Self:
|
124
|
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
125
|
+
|
126
|
+
return cls(
|
127
|
+
client=ChatGoogleGenerativeAI(model=model),
|
128
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
129
|
+
)
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
def ollama(
|
133
|
+
cls,
|
134
|
+
model: str = "deepseek-r1:1.5b",
|
135
|
+
structured_output_kwargs: Optional[dict[str, Any]] = None,
|
136
|
+
) -> Self:
|
137
|
+
from langchain_ollama import ChatOllama
|
138
|
+
|
139
|
+
return cls(
|
140
|
+
client=ChatOllama(model=model),
|
141
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
142
|
+
)
|
143
|
+
|
144
|
+
def generate(
|
145
|
+
self,
|
146
|
+
messages: LanguageModelInput,
|
147
|
+
config: Optional[RunnableConfig] = None,
|
148
|
+
stop: Optional[list[str]] = None,
|
149
|
+
**kwargs: Any,
|
150
|
+
) -> str:
|
151
|
+
return self.client.invoke(input=messages, config=config, stop=stop, **kwargs).text()
|
152
|
+
|
153
|
+
async def agenerate(
|
154
|
+
self,
|
155
|
+
messages: LanguageModelInput,
|
156
|
+
config: Optional[RunnableConfig] = None,
|
157
|
+
stop: Optional[list[str]] = None,
|
158
|
+
**kwargs: Any,
|
159
|
+
) -> str:
|
160
|
+
return (await self.client.ainvoke(input=messages, config=config, stop=stop, **kwargs)).text()
|
161
|
+
|
162
|
+
def generate_stream(
|
163
|
+
self,
|
164
|
+
messages: LanguageModelInput,
|
165
|
+
config: Optional[RunnableConfig] = None,
|
166
|
+
stop: Optional[list[str]] = None,
|
167
|
+
**kwargs: Any,
|
168
|
+
) -> Iterator[str]:
|
169
|
+
for chunk in self.client.stream(input=messages, config=config, stop=stop, **kwargs):
|
170
|
+
yield chunk.text()
|
171
|
+
|
172
|
+
async def agenerate_stream(
|
173
|
+
self,
|
174
|
+
messages: LanguageModelInput,
|
175
|
+
config: Optional[RunnableConfig] = None,
|
176
|
+
stop: Optional[list[str]] = None,
|
177
|
+
**kwargs: Any,
|
178
|
+
) -> AsyncIterator[str]:
|
179
|
+
async for chunk in self.client.astream(input=messages, config=config, stop=stop, **kwargs):
|
180
|
+
yield chunk.text()
|
181
|
+
|
182
|
+
def generate_pydantic(
|
183
|
+
self,
|
184
|
+
response_model: Type[PydanticModelT],
|
185
|
+
messages: LanguageModelInput,
|
186
|
+
config: Optional[RunnableConfig] = None,
|
187
|
+
stop: Optional[list[str]] = None,
|
188
|
+
**kwargs: Any,
|
189
|
+
) -> PydanticModelT:
|
190
|
+
result: StructuredOutputType = _with_structured_output(
|
191
|
+
client=self.client,
|
192
|
+
response_model=response_model,
|
193
|
+
structured_output_kwargs=self.structured_output_kwargs,
|
194
|
+
).invoke(input=messages, config=config, stop=stop, **kwargs)
|
195
|
+
if isinstance(result, response_model):
|
196
|
+
return result
|
197
|
+
else:
|
198
|
+
return response_model.model_validate(result)
|
199
|
+
|
200
|
+
async def agenerate_pydantic(
|
201
|
+
self,
|
202
|
+
response_model: Type[PydanticModelT],
|
203
|
+
messages: LanguageModelInput,
|
204
|
+
config: Optional[RunnableConfig] = None,
|
205
|
+
stop: Optional[list[str]] = None,
|
206
|
+
**kwargs: Any,
|
207
|
+
) -> PydanticModelT:
|
208
|
+
result: StructuredOutputType = await _with_structured_output(
|
209
|
+
client=self.client,
|
210
|
+
response_model=response_model,
|
211
|
+
structured_output_kwargs=self.structured_output_kwargs,
|
212
|
+
).ainvoke(input=messages, config=config, stop=stop, **kwargs)
|
213
|
+
if isinstance(result, response_model):
|
214
|
+
return result
|
215
|
+
else:
|
216
|
+
return response_model.model_validate(result)
|
217
|
+
|
218
|
+
def generate_pydantic_stream(
|
219
|
+
self,
|
220
|
+
response_model: Type[PydanticModelT],
|
221
|
+
messages: LanguageModelInput,
|
222
|
+
config: Optional[RunnableConfig] = None,
|
223
|
+
stop: Optional[list[str]] = None,
|
224
|
+
**kwargs: Any,
|
225
|
+
) -> Iterator[PydanticModelT]:
|
226
|
+
try:
|
227
|
+
import instructor
|
228
|
+
except ImportError:
|
229
|
+
raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
|
230
|
+
|
231
|
+
partial_response_model = instructor.Partial[response_model]
|
232
|
+
for chunk in _with_structured_output(
|
233
|
+
client=self.client,
|
234
|
+
response_model=partial_response_model,
|
235
|
+
structured_output_kwargs=self.structured_output_kwargs,
|
236
|
+
).stream(input=messages, config=config, stop=stop, **kwargs):
|
237
|
+
yield response_model.model_validate(chunk)
|
238
|
+
|
239
|
+
async def agenerate_pydantic_stream(
|
240
|
+
self,
|
241
|
+
response_model: Type[PydanticModelT],
|
242
|
+
messages: LanguageModelInput,
|
243
|
+
config: Optional[RunnableConfig] = None,
|
244
|
+
stop: Optional[list[str]] = None,
|
245
|
+
**kwargs: Any,
|
246
|
+
) -> AsyncIterator[PydanticModelT]:
|
247
|
+
try:
|
248
|
+
import instructor
|
249
|
+
except ImportError:
|
250
|
+
raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
|
251
|
+
|
252
|
+
partial_response_model = instructor.Partial[response_model]
|
253
|
+
async for chunk in _with_structured_output(
|
254
|
+
client=self.client,
|
255
|
+
response_model=partial_response_model,
|
256
|
+
structured_output_kwargs=self.structured_output_kwargs,
|
257
|
+
).astream(input=messages, config=config, stop=stop, **kwargs):
|
258
|
+
yield response_model.model_validate(chunk)
|
259
|
+
|
260
|
+
def describe_image(self, image_url: str, instruction: str = DEFAULT_IMAGE_DESCRIPTION_INSTRUCTION) -> str:
|
261
|
+
"""
|
262
|
+
Create a detailed description of an image using the Vision Language Model.
|
263
|
+
- image_url: Image URL to describe
|
264
|
+
"""
|
265
|
+
return self.generate([
|
266
|
+
HumanMessage(
|
267
|
+
content=[{"type": "text", "text": instruction}, {"type": "image_url", "image_url": {"url": image_url}}],
|
268
|
+
)
|
269
|
+
])
|
270
|
+
|
271
|
+
async def adescribe_image(self, image_url: str, instruction: str = DEFAULT_IMAGE_DESCRIPTION_INSTRUCTION) -> str:
|
272
|
+
"""
|
273
|
+
Create a detailed description of an image using the Vision Language Model asynchronously.
|
274
|
+
- image_url: Image URL to describe
|
275
|
+
"""
|
276
|
+
return await self.agenerate([
|
277
|
+
HumanMessage(
|
278
|
+
content=[{"type": "text", "text": instruction}, {"type": "image_url", "image_url": {"url": image_url}}],
|
279
|
+
)
|
280
|
+
])
|
281
|
+
|
282
|
+
@staticmethod
|
283
|
+
def get_num_tokens_from_message(message: BaseMessage) -> Optional[tuple[int, int]]:
|
284
|
+
try:
|
285
|
+
if isinstance(message, AIMessage) and (usage_metadata := message.usage_metadata):
|
286
|
+
input_tokens = int(usage_metadata["input_tokens"])
|
287
|
+
output_tokens = int(usage_metadata["output_tokens"])
|
288
|
+
else:
|
289
|
+
# Dynamic extraction for unknown structures
|
290
|
+
input_tokens: Optional[int] = None
|
291
|
+
output_tokens: Optional[int] = None
|
292
|
+
|
293
|
+
def _find_tokens(obj: object) -> None:
|
294
|
+
nonlocal input_tokens, output_tokens
|
295
|
+
if isinstance(obj, dict):
|
296
|
+
for key, value in cast(dict[object, object], obj).items():
|
297
|
+
if isinstance(value, int):
|
298
|
+
if "input" in str(key) or "prompt" in str(key):
|
299
|
+
input_tokens = value
|
300
|
+
elif "output" in str(key) or "completion" in str(key):
|
301
|
+
output_tokens = value
|
302
|
+
else:
|
303
|
+
_find_tokens(value)
|
304
|
+
elif isinstance(obj, list):
|
305
|
+
for item in cast(list[object], obj):
|
306
|
+
_find_tokens(item)
|
307
|
+
|
308
|
+
_find_tokens(message.model_dump())
|
309
|
+
|
310
|
+
if input_tokens is None or output_tokens is None:
|
311
|
+
return None
|
312
|
+
return input_tokens, output_tokens
|
313
|
+
except Exception:
|
314
|
+
return None
|
315
|
+
|
316
|
+
def invoke_code_execution(
|
317
|
+
self,
|
318
|
+
messages: LanguageModelInput,
|
319
|
+
repl_tool: Optional["PythonAstREPLTool"] = None,
|
320
|
+
prompt_for_code_invoke: Optional[str] = DEFAULT_CODE_GENERATION_PROMPT,
|
321
|
+
function_signatures: Optional[FunctionSignature | Iterable[FunctionSignature]] = None,
|
322
|
+
function_reference_prefix: Optional[str] = DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
323
|
+
function_reference_seperator: str = DEFAULT_FUNCTION_REFERENCE_SEPARATOR,
|
324
|
+
config: Optional[RunnableConfig] = None,
|
325
|
+
stop: Optional[list[str]] = None,
|
326
|
+
**kwargs: Any,
|
327
|
+
) -> CodeExecutionResult:
|
328
|
+
if not function_signatures:
|
329
|
+
function_signatures = []
|
330
|
+
elif isinstance(function_signatures, FunctionSignature):
|
331
|
+
function_signatures = [function_signatures]
|
332
|
+
messages = augment_prompt_for_toolcall(
|
333
|
+
function_signatures=function_signatures,
|
334
|
+
messages=messages,
|
335
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
336
|
+
function_reference_prefix=function_reference_prefix,
|
337
|
+
function_reference_seperator=function_reference_seperator,
|
338
|
+
)
|
339
|
+
code_obj: PythonCodeToExecute = self.generate_pydantic(
|
340
|
+
response_model=PythonCodeToExecute, messages=messages, config=config, stop=stop, **kwargs
|
341
|
+
)
|
342
|
+
return CodeExecutionResult.from_code(
|
343
|
+
code=code_obj.code,
|
344
|
+
config=config,
|
345
|
+
repl_tool=repl_tool,
|
346
|
+
function_signatures=function_signatures,
|
347
|
+
**kwargs,
|
348
|
+
)
|
349
|
+
|
350
|
+
async def ainvoke_code_execution(
|
351
|
+
self,
|
352
|
+
messages: LanguageModelInput,
|
353
|
+
repl_tool: Optional["PythonAstREPLTool"] = None,
|
354
|
+
prompt_for_code_invoke: Optional[str] = DEFAULT_CODE_GENERATION_PROMPT,
|
355
|
+
additional_callables: Optional[Callable[..., object] | Sequence[Callable[..., object]]] = None,
|
356
|
+
function_reference_prefix: Optional[str] = DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
357
|
+
function_reference_seperator: str = DEFAULT_FUNCTION_REFERENCE_SEPARATOR,
|
358
|
+
config: Optional[RunnableConfig] = None,
|
359
|
+
stop: Optional[list[str]] = None,
|
360
|
+
**kwargs: Any,
|
361
|
+
) -> CodeExecutionResult:
|
362
|
+
function_signatures: list[FunctionSignature] = FunctionSignature.from_callable(additional_callables)
|
363
|
+
messages = augment_prompt_for_toolcall(
|
364
|
+
function_signatures=function_signatures,
|
365
|
+
messages=messages,
|
366
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
367
|
+
function_reference_prefix=function_reference_prefix,
|
368
|
+
function_reference_seperator=function_reference_seperator,
|
369
|
+
)
|
370
|
+
code_obj: PythonCodeToExecute = await self.agenerate_pydantic(
|
371
|
+
response_model=PythonCodeToExecute, messages=messages, config=config, stop=stop, **kwargs
|
372
|
+
)
|
373
|
+
return await CodeExecutionResult.afrom_code(
|
374
|
+
code=code_obj.code,
|
375
|
+
config=config,
|
376
|
+
repl_tool=repl_tool,
|
377
|
+
function_signatures=function_signatures,
|
378
|
+
**kwargs,
|
379
|
+
)
|
380
|
+
|
381
|
+
|
382
|
+
class PythonCodeToExecute(BaseModel):
|
383
|
+
code: str = Field(description="Python code to execute")
|
384
|
+
|
385
|
+
|
386
|
+
def _with_structured_output(
|
387
|
+
client: BaseChatModel,
|
388
|
+
response_model: Type["PydanticModelT | Partial[PydanticModelT]"],
|
389
|
+
structured_output_kwargs: dict[str, Any],
|
390
|
+
) -> Runnable[LanguageModelInput, dict[object, object] | BaseModel]:
|
391
|
+
return client.with_structured_output(schema=response_model, **structured_output_kwargs) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
|
392
|
+
|
393
|
+
|
394
|
+
# def _add_message_last(messages: LanguageModelInput, prompt_to_add: str) -> LanguageModelInput:
|
395
|
+
# if isinstance(messages, str):
|
396
|
+
# messages += f"\n{prompt_to_add}"
|
397
|
+
# elif isinstance(messages, Sequence):
|
398
|
+
# messages = list(messages)
|
399
|
+
# messages.append(SystemMessage(content=prompt_to_add))
|
400
|
+
# else:
|
401
|
+
# messages = messages.to_messages()
|
402
|
+
# messages.append(SystemMessage(content=prompt_to_add))
|
403
|
+
# return messages
|
404
|
+
|
405
|
+
|
406
|
+
def _add_message_first(messages: LanguageModelInput, prompt_to_add: str) -> LanguageModelInput:
|
407
|
+
if isinstance(messages, str):
|
408
|
+
messages = f"{prompt_to_add}\n{messages}"
|
409
|
+
elif isinstance(messages, Sequence):
|
410
|
+
messages = list(messages)
|
411
|
+
messages.insert(0, SystemMessage(content=prompt_to_add))
|
412
|
+
else:
|
413
|
+
messages = messages.to_messages()
|
414
|
+
messages.insert(0, SystemMessage(content=prompt_to_add))
|
415
|
+
return messages
|
416
|
+
|
417
|
+
|
418
|
+
def augment_prompt_for_toolcall(
|
419
|
+
function_signatures: Iterable[FunctionSignature],
|
420
|
+
messages: LanguageModelInput,
|
421
|
+
prompt_for_code_invoke: Optional[str] = DEFAULT_CODE_GENERATION_PROMPT,
|
422
|
+
function_reference_prefix: Optional[str] = DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
423
|
+
function_reference_seperator: str = DEFAULT_FUNCTION_REFERENCE_SEPARATOR,
|
424
|
+
) -> LanguageModelInput:
|
425
|
+
if function_signatures:
|
426
|
+
messages = _add_message_first(
|
427
|
+
messages=messages,
|
428
|
+
prompt_to_add=FunctionSignature.as_prompt(
|
429
|
+
function_signatures, function_reference_prefix, function_reference_seperator
|
430
|
+
),
|
431
|
+
)
|
432
|
+
if prompt_for_code_invoke:
|
433
|
+
messages = _add_message_first(messages=messages, prompt_to_add=prompt_for_code_invoke)
|
434
|
+
return messages
|
435
|
+
|
436
|
+
|
437
|
+
def interactive_shell(
|
438
|
+
chatterer: Chatterer = Chatterer.openai(),
|
439
|
+
system_instruction: BaseMessage | Iterable[BaseMessage] = ([
|
440
|
+
SystemMessage("You are an AI that can answer questions and execute Python code."),
|
441
|
+
]),
|
442
|
+
repl_tool: Optional["PythonAstREPLTool"] = None,
|
443
|
+
prompt_for_code_invoke: Optional[str] = DEFAULT_CODE_GENERATION_PROMPT,
|
444
|
+
additional_callables: Optional[Callable[..., object] | Sequence[Callable[..., object]]] = None,
|
445
|
+
function_reference_prefix: Optional[str] = DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
446
|
+
function_reference_seperator: str = DEFAULT_FUNCTION_REFERENCE_SEPARATOR,
|
447
|
+
config: Optional[RunnableConfig] = None,
|
448
|
+
stop: Optional[list[str]] = None,
|
449
|
+
**kwargs: Any,
|
450
|
+
) -> None:
|
451
|
+
from rich.console import Console
|
452
|
+
from rich.prompt import Prompt
|
453
|
+
|
454
|
+
# 코드 실행 필요 여부를 판단하는 모델
|
455
|
+
class IsCodeExecutionNeeded(BaseModel):
|
456
|
+
is_code_execution_needed: bool = Field(
|
457
|
+
description="Whether Python tool calling is needed to answer user query."
|
458
|
+
)
|
459
|
+
|
460
|
+
# 추가 코드 실행 필요 여부를 판단하는 모델
|
461
|
+
class IsFurtherCodeExecutionNeeded(BaseModel):
|
462
|
+
review_on_code_execution: str = Field(description="Review on the code execution.")
|
463
|
+
next_action: str = Field(description="Next action to take.")
|
464
|
+
is_further_code_execution_needed: bool = Field(
|
465
|
+
description="Whether further Python tool calling is needed to answer user query."
|
466
|
+
)
|
467
|
+
|
468
|
+
def respond(messages: list[BaseMessage]) -> str:
|
469
|
+
# AI 응답 스트리밍 출력
|
470
|
+
console.print("[bold blue]AI:[/bold blue] ", end="")
|
471
|
+
response = ""
|
472
|
+
for chunk in chatterer.generate_stream(messages=messages):
|
473
|
+
response += chunk
|
474
|
+
console.print(chunk, end="")
|
475
|
+
console.print() # 응답 후 줄바꿈 추가
|
476
|
+
return response.strip()
|
477
|
+
|
478
|
+
def code_session_returning_end_of_turn() -> bool:
|
479
|
+
code_session_messages: list[BaseMessage] = []
|
480
|
+
while True:
|
481
|
+
code_execution: CodeExecutionResult = chatterer.invoke_code_execution(
|
482
|
+
messages=context,
|
483
|
+
repl_tool=repl_tool,
|
484
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
485
|
+
function_signatures=function_signatures,
|
486
|
+
function_reference_prefix=function_reference_prefix,
|
487
|
+
function_reference_seperator=function_reference_seperator,
|
488
|
+
config=config,
|
489
|
+
stop=stop,
|
490
|
+
**kwargs,
|
491
|
+
)
|
492
|
+
if code_execution.code.strip() in ("", "quit", "exit", "pass"):
|
493
|
+
return False
|
494
|
+
|
495
|
+
last_tool_use_message = AIMessage(
|
496
|
+
content=f"Executed code:\n```python\n{code_execution.code}\n```\nOutput:\n{code_execution.output}".strip()
|
497
|
+
)
|
498
|
+
code_session_messages.append(last_tool_use_message)
|
499
|
+
console.print("[bold yellow]Executed code:[/bold yellow]")
|
500
|
+
console.print(f"[code]{code_execution.code}[/code]")
|
501
|
+
console.print("[bold yellow]Output:[/bold yellow]")
|
502
|
+
console.print(code_execution.output)
|
503
|
+
|
504
|
+
decision = chatterer.generate_pydantic(
|
505
|
+
response_model=IsFurtherCodeExecutionNeeded,
|
506
|
+
messages=augment_prompt_for_toolcall(
|
507
|
+
function_signatures=function_signatures,
|
508
|
+
messages=context + code_session_messages,
|
509
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
510
|
+
function_reference_prefix=function_reference_prefix,
|
511
|
+
function_reference_seperator=function_reference_seperator,
|
512
|
+
),
|
513
|
+
)
|
514
|
+
review_on_code_execution = decision.review_on_code_execution.strip()
|
515
|
+
next_action = decision.next_action.strip()
|
516
|
+
console.print("[bold blue]AI:[/bold blue]")
|
517
|
+
console.print(f"-[bold yellow]Review on code execution:[/bold yellow] {review_on_code_execution}")
|
518
|
+
console.print(f"-[bold yellow]Next Action:[/bold yellow] {next_action}")
|
519
|
+
code_session_messages.append(
|
520
|
+
AIMessage(
|
521
|
+
content=f"- Review upon code execution: {review_on_code_execution}\n- Next Action: {next_action}".strip()
|
522
|
+
)
|
523
|
+
)
|
524
|
+
if not decision.is_further_code_execution_needed:
|
525
|
+
response: str = respond(context + code_session_messages)
|
526
|
+
context.append(last_tool_use_message)
|
527
|
+
context.append(AIMessage(content=response))
|
528
|
+
return True
|
529
|
+
|
530
|
+
# REPL 도구 초기화
|
531
|
+
if repl_tool is None:
|
532
|
+
repl_tool = get_default_repl_tool()
|
533
|
+
|
534
|
+
function_signatures: list[FunctionSignature] = FunctionSignature.from_callable(additional_callables)
|
535
|
+
console = Console()
|
536
|
+
context: list[BaseMessage] = []
|
537
|
+
if system_instruction:
|
538
|
+
if isinstance(system_instruction, BaseMessage):
|
539
|
+
context.append(system_instruction)
|
540
|
+
else:
|
541
|
+
context.extend(system_instruction)
|
542
|
+
|
543
|
+
# 환영 메시지
|
544
|
+
console.print("[bold blue]Welcome to the Interactive Chatterer Shell![/bold blue]")
|
545
|
+
console.print("Type 'quit' or 'exit' to end the conversation.")
|
546
|
+
|
547
|
+
while True:
|
548
|
+
# 사용자 입력 받기
|
549
|
+
user_input = Prompt.ask("[bold green]You[/bold green]")
|
550
|
+
if user_input.lower() in ["quit", "exit"]:
|
551
|
+
console.print("[bold blue]Goodbye![/bold blue]")
|
552
|
+
break
|
553
|
+
|
554
|
+
context.append(HumanMessage(content=user_input))
|
555
|
+
|
556
|
+
# 코드 실행 필요 여부 판단
|
557
|
+
decision = chatterer.generate_pydantic(
|
558
|
+
response_model=IsCodeExecutionNeeded,
|
559
|
+
messages=augment_prompt_for_toolcall(
|
560
|
+
function_signatures=function_signatures,
|
561
|
+
messages=context,
|
562
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
563
|
+
function_reference_prefix=function_reference_prefix,
|
564
|
+
function_reference_seperator=function_reference_seperator,
|
565
|
+
),
|
566
|
+
)
|
567
|
+
|
568
|
+
# 코드 실행 처리
|
569
|
+
if decision.is_code_execution_needed and code_session_returning_end_of_turn():
|
570
|
+
continue
|
571
|
+
|
572
|
+
# AI 응답 스트리밍 출력
|
573
|
+
context.append(AIMessage(content=respond(context)))
|
574
|
+
|
575
|
+
|
576
|
+
if __name__ == "__main__":
|
577
|
+
interactive_shell()
|