chatterer 0.1.13__py3-none-any.whl → 0.1.16__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 +36 -5
- chatterer/interactive.py +692 -0
- chatterer/language_model.py +217 -261
- chatterer/messages.py +13 -1
- chatterer/tools/__init__.py +26 -15
- chatterer/tools/{webpage_to_markdown/utils.py → caption_markdown_images.py} +158 -108
- chatterer/tools/convert_pdf_to_markdown.py +302 -0
- chatterer/tools/convert_to_text.py +45 -16
- chatterer/tools/upstage_document_parser.py +481 -214
- chatterer/tools/{webpage_to_markdown/playwright_bot.py → webpage_to_markdown.py} +197 -107
- chatterer/tools/youtube.py +2 -1
- chatterer/utils/__init__.py +1 -1
- chatterer/utils/{image.py → base64_image.py} +56 -62
- chatterer/utils/code_agent.py +137 -38
- chatterer/utils/imghdr.py +148 -0
- chatterer-0.1.16.dist-info/METADATA +392 -0
- chatterer-0.1.16.dist-info/RECORD +33 -0
- {chatterer-0.1.13.dist-info → chatterer-0.1.16.dist-info}/WHEEL +1 -1
- chatterer/tools/webpage_to_markdown/__init__.py +0 -4
- chatterer-0.1.13.dist-info/METADATA +0 -171
- chatterer-0.1.13.dist-info/RECORD +0 -31
- {chatterer-0.1.13.dist-info → chatterer-0.1.16.dist-info}/top_level.txt +0 -0
chatterer/language_model.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import re
|
1
2
|
from typing import (
|
2
3
|
TYPE_CHECKING,
|
3
4
|
Any,
|
@@ -5,13 +6,13 @@ from typing import (
|
|
5
6
|
Callable,
|
6
7
|
Iterable,
|
7
8
|
Iterator,
|
9
|
+
Literal,
|
8
10
|
Optional,
|
9
11
|
Self,
|
10
12
|
Sequence,
|
11
13
|
Type,
|
12
14
|
TypeAlias,
|
13
15
|
TypeVar,
|
14
|
-
cast,
|
15
16
|
overload,
|
16
17
|
)
|
17
18
|
|
@@ -19,10 +20,11 @@ from langchain_core.language_models.base import LanguageModelInput
|
|
19
20
|
from langchain_core.language_models.chat_models import BaseChatModel
|
20
21
|
from langchain_core.runnables.base import Runnable
|
21
22
|
from langchain_core.runnables.config import RunnableConfig
|
22
|
-
from
|
23
|
+
from langchain_core.utils.utils import secret_from_env
|
24
|
+
from pydantic import BaseModel, Field, SecretStr
|
23
25
|
|
24
|
-
from .messages import AIMessage, BaseMessage, HumanMessage,
|
25
|
-
from .utils.code_agent import CodeExecutionResult, FunctionSignature,
|
26
|
+
from .messages import AIMessage, BaseMessage, HumanMessage, UsageMetadata
|
27
|
+
from .utils.code_agent import CodeExecutionResult, FunctionSignature, augment_prompt_for_toolcall
|
26
28
|
|
27
29
|
if TYPE_CHECKING:
|
28
30
|
from instructor import Partial
|
@@ -53,6 +55,8 @@ DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT = (
|
|
53
55
|
)
|
54
56
|
DEFAULT_FUNCTION_REFERENCE_SEPARATOR = "\n---\n" # Separator to distinguish different function references
|
55
57
|
|
58
|
+
PYTHON_CODE_PATTERN: re.Pattern[str] = re.compile(r"```(?:python\s*\n)?(.*?)```", re.DOTALL)
|
59
|
+
|
56
60
|
|
57
61
|
class Chatterer(BaseModel):
|
58
62
|
"""Language model for generating text from a given input."""
|
@@ -60,71 +64,82 @@ class Chatterer(BaseModel):
|
|
60
64
|
client: BaseChatModel
|
61
65
|
structured_output_kwargs: dict[str, Any] = Field(default_factory=dict)
|
62
66
|
|
63
|
-
@
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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: ...
|
67
|
+
@classmethod
|
68
|
+
def from_provider(
|
69
|
+
cls, provider_and_model: str, structured_output_kwargs: Optional[dict[str, object]] = {"strict": True}
|
70
|
+
) -> Self:
|
71
|
+
backend, model = provider_and_model.split(":", 1)
|
72
|
+
backends = cls.get_backends()
|
73
|
+
if func := backends.get(backend):
|
74
|
+
return func(model, structured_output_kwargs)
|
75
|
+
else:
|
76
|
+
raise ValueError(f"Unsupported provider: {backend}. Supported providers are: {', '.join(backends.keys())}.")
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
return self.client.invoke(input=messages, config=config, stop=stop, **kwargs).text()
|
78
|
+
@classmethod
|
79
|
+
def get_backends(cls) -> dict[str, Callable[[str, Optional[dict[str, object]]], Self]]:
|
80
|
+
return {
|
81
|
+
"openai": cls.openai,
|
82
|
+
"anthropic": cls.anthropic,
|
83
|
+
"google": cls.google,
|
84
|
+
"ollama": cls.ollama,
|
85
|
+
"openrouter": cls.open_router,
|
86
|
+
"xai": cls.xai,
|
87
|
+
}
|
94
88
|
|
95
89
|
@classmethod
|
96
90
|
def openai(
|
97
91
|
cls,
|
98
|
-
model: str = "gpt-
|
99
|
-
structured_output_kwargs: Optional[dict[str,
|
92
|
+
model: str = "gpt-4.1",
|
93
|
+
structured_output_kwargs: Optional[dict[str, object]] = {"strict": True},
|
94
|
+
api_key: Optional[str] = None,
|
95
|
+
**kwargs: Any,
|
100
96
|
) -> Self:
|
101
97
|
from langchain_openai import ChatOpenAI
|
102
98
|
|
103
|
-
return cls(
|
99
|
+
return cls(
|
100
|
+
client=ChatOpenAI(
|
101
|
+
model=model,
|
102
|
+
api_key=_get_api_key(api_key=api_key, env_key="OPENAI_API_KEY", raise_if_none=False),
|
103
|
+
**kwargs,
|
104
|
+
),
|
105
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
106
|
+
)
|
104
107
|
|
105
108
|
@classmethod
|
106
109
|
def anthropic(
|
107
110
|
cls,
|
108
111
|
model_name: str = "claude-3-7-sonnet-20250219",
|
109
|
-
structured_output_kwargs: Optional[dict[str,
|
112
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
113
|
+
api_key: Optional[str] = None,
|
114
|
+
**kwargs: Any,
|
110
115
|
) -> Self:
|
111
116
|
from langchain_anthropic import ChatAnthropic
|
112
117
|
|
113
118
|
return cls(
|
114
|
-
client=ChatAnthropic(
|
119
|
+
client=ChatAnthropic(
|
120
|
+
model_name=model_name,
|
121
|
+
api_key=_get_api_key(api_key=api_key, env_key="ANTHROPIC_API_KEY", raise_if_none=True),
|
122
|
+
**kwargs,
|
123
|
+
),
|
115
124
|
structured_output_kwargs=structured_output_kwargs or {},
|
116
125
|
)
|
117
126
|
|
118
127
|
@classmethod
|
119
128
|
def google(
|
120
129
|
cls,
|
121
|
-
model: str = "gemini-2.
|
122
|
-
structured_output_kwargs: Optional[dict[str,
|
130
|
+
model: str = "gemini-2.5-flash-preview-04-17",
|
131
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
132
|
+
api_key: Optional[str] = None,
|
133
|
+
**kwargs: Any,
|
123
134
|
) -> Self:
|
124
135
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
125
136
|
|
126
137
|
return cls(
|
127
|
-
client=ChatGoogleGenerativeAI(
|
138
|
+
client=ChatGoogleGenerativeAI(
|
139
|
+
model=model,
|
140
|
+
api_key=_get_api_key(api_key=api_key, env_key="GOOGLE_API_KEY", raise_if_none=False),
|
141
|
+
**kwargs,
|
142
|
+
),
|
128
143
|
structured_output_kwargs=structured_output_kwargs or {},
|
129
144
|
)
|
130
145
|
|
@@ -132,15 +147,112 @@ class Chatterer(BaseModel):
|
|
132
147
|
def ollama(
|
133
148
|
cls,
|
134
149
|
model: str = "deepseek-r1:1.5b",
|
135
|
-
structured_output_kwargs: Optional[dict[str,
|
150
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
151
|
+
**kwargs: Any,
|
136
152
|
) -> Self:
|
137
153
|
from langchain_ollama import ChatOllama
|
138
154
|
|
139
155
|
return cls(
|
140
|
-
client=ChatOllama(model=model),
|
156
|
+
client=ChatOllama(model=model, **kwargs),
|
157
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
158
|
+
)
|
159
|
+
|
160
|
+
@classmethod
|
161
|
+
def open_router(
|
162
|
+
cls,
|
163
|
+
model: str = "openrouter/quasar-alpha",
|
164
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
165
|
+
api_key: Optional[str] = None,
|
166
|
+
**kwargs: Any,
|
167
|
+
) -> Self:
|
168
|
+
from langchain_openai import ChatOpenAI
|
169
|
+
|
170
|
+
return cls(
|
171
|
+
client=ChatOpenAI(
|
172
|
+
model=model,
|
173
|
+
base_url="https://openrouter.ai/api/v1",
|
174
|
+
api_key=_get_api_key(api_key=api_key, env_key="OPENROUTER_API_KEY", raise_if_none=False),
|
175
|
+
**kwargs,
|
176
|
+
),
|
141
177
|
structured_output_kwargs=structured_output_kwargs or {},
|
142
178
|
)
|
143
179
|
|
180
|
+
@classmethod
|
181
|
+
def xai(
|
182
|
+
cls,
|
183
|
+
model: str = "grok-3-mini",
|
184
|
+
structured_output_kwargs: Optional[dict[str, object]] = None,
|
185
|
+
base_url: str = "https://api.x.ai/v1",
|
186
|
+
api_key: Optional[str] = None,
|
187
|
+
**kwargs: Any,
|
188
|
+
) -> Self:
|
189
|
+
from langchain_openai import ChatOpenAI
|
190
|
+
|
191
|
+
return cls(
|
192
|
+
client=ChatOpenAI(
|
193
|
+
model=model,
|
194
|
+
base_url=base_url,
|
195
|
+
api_key=_get_api_key(api_key=api_key, env_key="XAI_API_KEY", raise_if_none=False),
|
196
|
+
**kwargs,
|
197
|
+
),
|
198
|
+
structured_output_kwargs=structured_output_kwargs or {},
|
199
|
+
)
|
200
|
+
|
201
|
+
@property
|
202
|
+
def invoke(self):
|
203
|
+
return self.client.invoke
|
204
|
+
|
205
|
+
@property
|
206
|
+
def ainvoke(self):
|
207
|
+
return self.client.ainvoke
|
208
|
+
|
209
|
+
@property
|
210
|
+
def stream(self):
|
211
|
+
return self.client.stream
|
212
|
+
|
213
|
+
@property
|
214
|
+
def astream(self):
|
215
|
+
return self.client.astream
|
216
|
+
|
217
|
+
@property
|
218
|
+
def bind_tools(self): # pyright: ignore[reportUnknownParameterType]
|
219
|
+
return self.client.bind_tools # pyright: ignore[reportUnknownParameterType, reportUnknownVariableType, reportUnknownMemberType]
|
220
|
+
|
221
|
+
def __getattr__(self, name: str) -> Any:
|
222
|
+
return getattr(self.client, name)
|
223
|
+
|
224
|
+
@overload
|
225
|
+
def __call__(
|
226
|
+
self,
|
227
|
+
messages: LanguageModelInput,
|
228
|
+
response_model: Type[PydanticModelT],
|
229
|
+
config: Optional[RunnableConfig] = None,
|
230
|
+
stop: Optional[list[str]] = None,
|
231
|
+
**kwargs: Any,
|
232
|
+
) -> PydanticModelT: ...
|
233
|
+
|
234
|
+
@overload
|
235
|
+
def __call__(
|
236
|
+
self,
|
237
|
+
messages: LanguageModelInput,
|
238
|
+
response_model: None = None,
|
239
|
+
config: Optional[RunnableConfig] = None,
|
240
|
+
stop: Optional[list[str]] = None,
|
241
|
+
**kwargs: Any,
|
242
|
+
) -> str: ...
|
243
|
+
|
244
|
+
def __call__(
|
245
|
+
self,
|
246
|
+
messages: LanguageModelInput,
|
247
|
+
response_model: Optional[Type[PydanticModelT]] = None,
|
248
|
+
config: Optional[RunnableConfig] = None,
|
249
|
+
stop: Optional[list[str]] = None,
|
250
|
+
**kwargs: Any,
|
251
|
+
) -> str | PydanticModelT:
|
252
|
+
if response_model:
|
253
|
+
return self.generate_pydantic(response_model, messages, config, stop, **kwargs)
|
254
|
+
return self.client.invoke(input=messages, config=config, stop=stop, **kwargs).text()
|
255
|
+
|
144
256
|
def generate(
|
145
257
|
self,
|
146
258
|
messages: LanguageModelInput,
|
@@ -279,41 +391,32 @@ class Chatterer(BaseModel):
|
|
279
391
|
)
|
280
392
|
])
|
281
393
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
394
|
+
def get_approximate_token_count(self, message: BaseMessage) -> int:
|
395
|
+
return self.client.get_num_tokens_from_messages([message]) # pyright: ignore[reportUnknownMemberType]
|
396
|
+
|
397
|
+
def get_usage_metadata(self, message: BaseMessage) -> UsageMetadata:
|
398
|
+
if isinstance(message, AIMessage):
|
399
|
+
usage_metadata = message.usage_metadata
|
400
|
+
if usage_metadata is not None:
|
401
|
+
input_tokens = usage_metadata["input_tokens"]
|
402
|
+
output_tokens = usage_metadata["output_tokens"]
|
403
|
+
return {
|
404
|
+
"input_tokens": input_tokens,
|
405
|
+
"output_tokens": output_tokens,
|
406
|
+
"total_tokens": input_tokens + output_tokens,
|
407
|
+
}
|
288
408
|
else:
|
289
|
-
|
290
|
-
input_tokens:
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
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(
|
409
|
+
approx_tokens = self.get_approximate_token_count(message)
|
410
|
+
return {"input_tokens": 0, "output_tokens": approx_tokens, "total_tokens": approx_tokens}
|
411
|
+
else:
|
412
|
+
approx_tokens = self.get_approximate_token_count(message)
|
413
|
+
return {
|
414
|
+
"input_tokens": approx_tokens,
|
415
|
+
"output_tokens": 0,
|
416
|
+
"total_tokens": approx_tokens,
|
417
|
+
}
|
418
|
+
|
419
|
+
def exec(
|
317
420
|
self,
|
318
421
|
messages: LanguageModelInput,
|
319
422
|
repl_tool: Optional["PythonAstREPLTool"] = None,
|
@@ -347,7 +450,12 @@ class Chatterer(BaseModel):
|
|
347
450
|
**kwargs,
|
348
451
|
)
|
349
452
|
|
350
|
-
|
453
|
+
@property
|
454
|
+
def invoke_code_execution(self) -> Callable[..., CodeExecutionResult]:
|
455
|
+
"""Alias for exec method for backward compatibility."""
|
456
|
+
return self.exec
|
457
|
+
|
458
|
+
async def aexec(
|
351
459
|
self,
|
352
460
|
messages: LanguageModelInput,
|
353
461
|
repl_tool: Optional["PythonAstREPLTool"] = None,
|
@@ -378,10 +486,24 @@ class Chatterer(BaseModel):
|
|
378
486
|
**kwargs,
|
379
487
|
)
|
380
488
|
|
489
|
+
@property
|
490
|
+
def ainvoke_code_execution(self):
|
491
|
+
"""Alias for aexec method for backward compatibility."""
|
492
|
+
return self.aexec
|
493
|
+
|
381
494
|
|
382
495
|
class PythonCodeToExecute(BaseModel):
|
383
496
|
code: str = Field(description="Python code to execute")
|
384
497
|
|
498
|
+
def model_post_init(self, context: object) -> None:
|
499
|
+
super().model_post_init(context)
|
500
|
+
|
501
|
+
codes: list[str] = []
|
502
|
+
for match in PYTHON_CODE_PATTERN.finditer(self.code):
|
503
|
+
codes.append(match.group(1))
|
504
|
+
if codes:
|
505
|
+
self.code = "\n".join(codes)
|
506
|
+
|
385
507
|
|
386
508
|
def _with_structured_output(
|
387
509
|
client: BaseChatModel,
|
@@ -391,187 +513,21 @@ def _with_structured_output(
|
|
391
513
|
return client.with_structured_output(schema=response_model, **structured_output_kwargs) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
|
392
514
|
|
393
515
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
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()
|
516
|
+
@overload
|
517
|
+
def _get_api_key(api_key: Optional[str], env_key: str, raise_if_none: Literal[True]) -> SecretStr: ...
|
518
|
+
@overload
|
519
|
+
def _get_api_key(api_key: Optional[str], env_key: str, raise_if_none: Literal[False]) -> Optional[SecretStr]: ...
|
520
|
+
def _get_api_key(api_key: Optional[str], env_key: str, raise_if_none: bool) -> Optional[SecretStr]:
|
521
|
+
if api_key is None:
|
522
|
+
api_key_found: SecretStr | None = secret_from_env(env_key, default=None)()
|
523
|
+
if raise_if_none and api_key_found is None:
|
524
|
+
raise ValueError(
|
525
|
+
(
|
526
|
+
f"Did not find API key, please add an environment variable"
|
527
|
+
f" `{env_key}` which contains it, or pass"
|
528
|
+
f" api_key as a named parameter."
|
522
529
|
)
|
523
530
|
)
|
524
|
-
|
525
|
-
|
526
|
-
|
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()
|
531
|
+
return api_key_found
|
532
|
+
else:
|
533
|
+
return SecretStr(api_key)
|
chatterer/messages.py
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
from langchain_core.
|
1
|
+
from langchain_core.language_models.base import LanguageModelInput
|
2
|
+
from langchain_core.messages import (
|
3
|
+
AIMessage,
|
4
|
+
BaseMessage,
|
5
|
+
BaseMessageChunk,
|
6
|
+
FunctionMessage,
|
7
|
+
HumanMessage,
|
8
|
+
SystemMessage,
|
9
|
+
)
|
10
|
+
from langchain_core.messages.ai import UsageMetadata
|
2
11
|
|
3
12
|
__all__ = [
|
4
13
|
"AIMessage",
|
@@ -6,4 +15,7 @@ __all__ = [
|
|
6
15
|
"HumanMessage",
|
7
16
|
"SystemMessage",
|
8
17
|
"FunctionMessage",
|
18
|
+
"BaseMessageChunk",
|
19
|
+
"UsageMetadata",
|
20
|
+
"LanguageModelInput",
|
9
21
|
]
|
chatterer/tools/__init__.py
CHANGED
@@ -1,26 +1,24 @@
|
|
1
|
+
from .caption_markdown_images import MarkdownLink, acaption_markdown_images, caption_markdown_images
|
1
2
|
from .citation_chunking import citation_chunker
|
3
|
+
from .convert_pdf_to_markdown import PdfToMarkdown, extract_text_from_pdf, open_pdf, render_pdf_as_image
|
2
4
|
from .convert_to_text import (
|
5
|
+
CodeSnippets,
|
3
6
|
anything_to_markdown,
|
4
7
|
get_default_html_to_markdown_options,
|
5
8
|
html_to_markdown,
|
6
9
|
pdf_to_text,
|
7
10
|
pyscripts_to_snippets,
|
8
11
|
)
|
12
|
+
from .upstage_document_parser import UpstageDocumentParseParser
|
13
|
+
from .webpage_to_markdown import (
|
14
|
+
PlayWrightBot,
|
15
|
+
PlaywrightLaunchOptions,
|
16
|
+
PlaywrightOptions,
|
17
|
+
PlaywrightPersistencyOptions,
|
18
|
+
get_default_playwright_launch_options,
|
19
|
+
)
|
9
20
|
from .youtube import get_youtube_video_details, get_youtube_video_subtitle
|
10
21
|
|
11
|
-
|
12
|
-
def init_webpage_to_markdown():
|
13
|
-
from . import webpage_to_markdown
|
14
|
-
|
15
|
-
return webpage_to_markdown
|
16
|
-
|
17
|
-
|
18
|
-
def init_upstage_document_parser():
|
19
|
-
from . import upstage_document_parser
|
20
|
-
|
21
|
-
return upstage_document_parser
|
22
|
-
|
23
|
-
|
24
22
|
__all__ = [
|
25
23
|
"html_to_markdown",
|
26
24
|
"anything_to_markdown",
|
@@ -28,8 +26,21 @@ __all__ = [
|
|
28
26
|
"get_default_html_to_markdown_options",
|
29
27
|
"pyscripts_to_snippets",
|
30
28
|
"citation_chunker",
|
31
|
-
"
|
29
|
+
"webpage_to_markdown",
|
32
30
|
"get_youtube_video_subtitle",
|
33
31
|
"get_youtube_video_details",
|
34
|
-
"
|
32
|
+
"CodeSnippets",
|
33
|
+
"PlayWrightBot",
|
34
|
+
"PlaywrightLaunchOptions",
|
35
|
+
"PlaywrightOptions",
|
36
|
+
"PlaywrightPersistencyOptions",
|
37
|
+
"get_default_playwright_launch_options",
|
38
|
+
"UpstageDocumentParseParser",
|
39
|
+
"acaption_markdown_images",
|
40
|
+
"caption_markdown_images",
|
41
|
+
"MarkdownLink",
|
42
|
+
"PdfToMarkdown",
|
43
|
+
"extract_text_from_pdf",
|
44
|
+
"open_pdf",
|
45
|
+
"render_pdf_as_image",
|
35
46
|
]
|