chatterer 0.1.9__py3-none-any.whl → 0.1.10__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 +6 -1
- chatterer/language_model.py +141 -53
- chatterer/tools/__init__.py +3 -0
- chatterer/tools/youtube.py +132 -0
- chatterer/utils/code_agent.py +13 -9
- {chatterer-0.1.9.dist-info → chatterer-0.1.10.dist-info}/METADATA +2 -1
- {chatterer-0.1.9.dist-info → chatterer-0.1.10.dist-info}/RECORD +9 -8
- {chatterer-0.1.9.dist-info → chatterer-0.1.10.dist-info}/WHEEL +0 -0
- {chatterer-0.1.9.dist-info → chatterer-0.1.10.dist-info}/top_level.txt +0 -0
chatterer/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from .language_model import Chatterer
|
1
|
+
from .language_model import Chatterer, interactive_shell
|
2
2
|
from .messages import (
|
3
3
|
AIMessage,
|
4
4
|
BaseMessage,
|
@@ -16,10 +16,12 @@ from .tools import (
|
|
16
16
|
anything_to_markdown,
|
17
17
|
citation_chunker,
|
18
18
|
get_default_html_to_markdown_options,
|
19
|
+
get_youtube_video_subtitle,
|
19
20
|
html_to_markdown,
|
20
21
|
init_webpage_to_markdown,
|
21
22
|
pdf_to_text,
|
22
23
|
pyscripts_to_snippets,
|
24
|
+
get_youtube_video_details,
|
23
25
|
)
|
24
26
|
from .utils import (
|
25
27
|
Base64Image,
|
@@ -52,4 +54,7 @@ __all__ = [
|
|
52
54
|
"CodeExecutionResult",
|
53
55
|
"get_default_repl_tool",
|
54
56
|
"insert_callables_into_global",
|
57
|
+
"get_youtube_video_subtitle",
|
58
|
+
"get_youtube_video_details",
|
59
|
+
"interactive_shell",
|
55
60
|
]
|
chatterer/language_model.py
CHANGED
@@ -22,7 +22,7 @@ from langchain_core.runnables.config import RunnableConfig
|
|
22
22
|
from pydantic import BaseModel, Field
|
23
23
|
|
24
24
|
from .messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
|
25
|
-
from .utils.code_agent import CodeExecutionResult, FunctionSignature
|
25
|
+
from .utils.code_agent import CodeExecutionResult, FunctionSignature, get_default_repl_tool
|
26
26
|
|
27
27
|
if TYPE_CHECKING:
|
28
28
|
from instructor import Partial
|
@@ -189,7 +189,7 @@ class Chatterer(BaseModel):
|
|
189
189
|
stop: Optional[list[str]] = None,
|
190
190
|
**kwargs: Any,
|
191
191
|
) -> PydanticModelT:
|
192
|
-
result: StructuredOutputType =
|
192
|
+
result: StructuredOutputType = _with_structured_output(
|
193
193
|
client=self.client,
|
194
194
|
response_model=response_model,
|
195
195
|
structured_output_kwargs=self.structured_output_kwargs,
|
@@ -207,7 +207,7 @@ class Chatterer(BaseModel):
|
|
207
207
|
stop: Optional[list[str]] = None,
|
208
208
|
**kwargs: Any,
|
209
209
|
) -> PydanticModelT:
|
210
|
-
result: StructuredOutputType = await
|
210
|
+
result: StructuredOutputType = await _with_structured_output(
|
211
211
|
client=self.client,
|
212
212
|
response_model=response_model,
|
213
213
|
structured_output_kwargs=self.structured_output_kwargs,
|
@@ -231,7 +231,7 @@ class Chatterer(BaseModel):
|
|
231
231
|
raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
|
232
232
|
|
233
233
|
partial_response_model = instructor.Partial[response_model]
|
234
|
-
for chunk in
|
234
|
+
for chunk in _with_structured_output(
|
235
235
|
client=self.client,
|
236
236
|
response_model=partial_response_model,
|
237
237
|
structured_output_kwargs=self.structured_output_kwargs,
|
@@ -252,7 +252,7 @@ class Chatterer(BaseModel):
|
|
252
252
|
raise ImportError("Please install `instructor` with `pip install instructor` to use this feature.")
|
253
253
|
|
254
254
|
partial_response_model = instructor.Partial[response_model]
|
255
|
-
async for chunk in
|
255
|
+
async for chunk in _with_structured_output(
|
256
256
|
client=self.client,
|
257
257
|
response_model=partial_response_model,
|
258
258
|
structured_output_kwargs=self.structured_output_kwargs,
|
@@ -320,26 +320,24 @@ class Chatterer(BaseModel):
|
|
320
320
|
messages: LanguageModelInput,
|
321
321
|
repl_tool: Optional["PythonAstREPLTool"] = None,
|
322
322
|
prompt_for_code_invoke: Optional[str] = DEFAULT_CODE_GENERATION_PROMPT,
|
323
|
-
|
323
|
+
function_signatures: Optional[FunctionSignature | Iterable[FunctionSignature]] = None,
|
324
324
|
function_reference_prefix: Optional[str] = DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
325
325
|
function_reference_seperator: str = DEFAULT_FUNCTION_REFERENCE_SEPARATOR,
|
326
326
|
config: Optional[RunnableConfig] = None,
|
327
327
|
stop: Optional[list[str]] = None,
|
328
328
|
**kwargs: Any,
|
329
329
|
) -> CodeExecutionResult:
|
330
|
-
function_signatures:
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
if prompt_for_code_invoke:
|
342
|
-
messages = _add_message_last(messages=messages, prompt_to_add=prompt_for_code_invoke)
|
330
|
+
if not function_signatures:
|
331
|
+
function_signatures = []
|
332
|
+
elif isinstance(function_signatures, FunctionSignature):
|
333
|
+
function_signatures = [function_signatures]
|
334
|
+
messages = augment_prompt_for_toolcall(
|
335
|
+
function_signatures=function_signatures,
|
336
|
+
messages=messages,
|
337
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
338
|
+
function_reference_prefix=function_reference_prefix,
|
339
|
+
function_reference_seperator=function_reference_seperator,
|
340
|
+
)
|
343
341
|
code_obj: PythonCodeToExecute = self.generate_pydantic(
|
344
342
|
response_model=PythonCodeToExecute, messages=messages, config=config, stop=stop, **kwargs
|
345
343
|
)
|
@@ -363,19 +361,14 @@ class Chatterer(BaseModel):
|
|
363
361
|
stop: Optional[list[str]] = None,
|
364
362
|
**kwargs: Any,
|
365
363
|
) -> CodeExecutionResult:
|
366
|
-
function_signatures:
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
function_signatures, function_reference_prefix, function_reference_seperator
|
375
|
-
),
|
376
|
-
)
|
377
|
-
if prompt_for_code_invoke:
|
378
|
-
messages = _add_message_last(messages=messages, prompt_to_add=prompt_for_code_invoke)
|
364
|
+
function_signatures: list[FunctionSignature] = FunctionSignature.from_callable(additional_callables)
|
365
|
+
messages = augment_prompt_for_toolcall(
|
366
|
+
function_signatures=function_signatures,
|
367
|
+
messages=messages,
|
368
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
369
|
+
function_reference_prefix=function_reference_prefix,
|
370
|
+
function_reference_seperator=function_reference_seperator,
|
371
|
+
)
|
379
372
|
code_obj: PythonCodeToExecute = await self.agenerate_pydantic(
|
380
373
|
response_model=PythonCodeToExecute, messages=messages, config=config, stop=stop, **kwargs
|
381
374
|
)
|
@@ -392,7 +385,7 @@ class PythonCodeToExecute(BaseModel):
|
|
392
385
|
code: str = Field(description="Python code to execute")
|
393
386
|
|
394
387
|
|
395
|
-
def
|
388
|
+
def _with_structured_output(
|
396
389
|
client: BaseChatModel,
|
397
390
|
response_model: Type["PydanticModelT | Partial[PydanticModelT]"],
|
398
391
|
structured_output_kwargs: dict[str, Any],
|
@@ -424,25 +417,76 @@ def _add_message_last(messages: LanguageModelInput, prompt_to_add: str) -> Langu
|
|
424
417
|
# return messages
|
425
418
|
|
426
419
|
|
427
|
-
def
|
420
|
+
def augment_prompt_for_toolcall(
|
421
|
+
function_signatures: Iterable[FunctionSignature],
|
422
|
+
messages: LanguageModelInput,
|
423
|
+
prompt_for_code_invoke: Optional[str] = DEFAULT_CODE_GENERATION_PROMPT,
|
424
|
+
function_reference_prefix: Optional[str] = DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
425
|
+
function_reference_seperator: str = DEFAULT_FUNCTION_REFERENCE_SEPARATOR,
|
426
|
+
) -> LanguageModelInput:
|
427
|
+
if function_signatures:
|
428
|
+
messages = _add_message_last(
|
429
|
+
messages=messages,
|
430
|
+
prompt_to_add=FunctionSignature.as_prompt(
|
431
|
+
function_signatures, function_reference_prefix, function_reference_seperator
|
432
|
+
),
|
433
|
+
)
|
434
|
+
if prompt_for_code_invoke:
|
435
|
+
messages = _add_message_last(messages=messages, prompt_to_add=prompt_for_code_invoke)
|
436
|
+
return messages
|
437
|
+
|
438
|
+
|
439
|
+
def interactive_shell(
|
440
|
+
chatterer: Chatterer = Chatterer.openai(),
|
441
|
+
system_instruction: BaseMessage | Iterable[BaseMessage] = ([
|
442
|
+
SystemMessage("You are an AI that can answer questions and execute Python code."),
|
443
|
+
]),
|
444
|
+
repl_tool: Optional["PythonAstREPLTool"] = None,
|
445
|
+
prompt_for_code_invoke: Optional[str] = DEFAULT_CODE_GENERATION_PROMPT,
|
446
|
+
additional_callables: Optional[Callable[..., object] | Sequence[Callable[..., object]]] = None,
|
447
|
+
function_reference_prefix: Optional[str] = DEFAULT_FUNCTION_REFERENCE_PREFIX_PROMPT,
|
448
|
+
function_reference_seperator: str = DEFAULT_FUNCTION_REFERENCE_SEPARATOR,
|
449
|
+
config: Optional[RunnableConfig] = None,
|
450
|
+
stop: Optional[list[str]] = None,
|
451
|
+
**kwargs: Any,
|
452
|
+
) -> None:
|
428
453
|
# Define the CodeExecutionDecision class using Pydantic
|
429
454
|
|
430
455
|
from rich.console import Console
|
431
456
|
from rich.prompt import Prompt
|
432
457
|
|
433
|
-
class
|
458
|
+
class IsCodeExecutionNeeded(BaseModel):
|
434
459
|
is_code_execution_needed: bool = Field(
|
435
460
|
description="Whether Python tool calling is needed to answer user query."
|
436
461
|
)
|
437
462
|
|
463
|
+
class IsFurtherCodeExecutionNeeded(BaseModel):
|
464
|
+
review_on_code_execution: str = Field(description="Review on the code execution.")
|
465
|
+
next_action: str = Field(description="Next action to take.")
|
466
|
+
is_further_code_execution_needed: bool = Field(
|
467
|
+
description="Whether further Python tool calling is needed to answer user query."
|
468
|
+
)
|
469
|
+
|
470
|
+
# Get default REPL tool if not provided.
|
471
|
+
# This tool namespace is persistent across multiple code executions.
|
472
|
+
if repl_tool is None:
|
473
|
+
repl_tool = get_default_repl_tool()
|
474
|
+
|
475
|
+
function_signatures: list[FunctionSignature] = FunctionSignature.from_callable(additional_callables)
|
476
|
+
|
438
477
|
# Initialize Rich console
|
439
478
|
console = Console()
|
440
479
|
|
441
480
|
# Initialize conversation context
|
442
|
-
context: list[BaseMessage] = [
|
481
|
+
context: list[BaseMessage] = []
|
482
|
+
if system_instruction:
|
483
|
+
if isinstance(system_instruction, BaseMessage):
|
484
|
+
context.append(system_instruction)
|
485
|
+
else:
|
486
|
+
context.extend(system_instruction)
|
443
487
|
|
444
488
|
# Display welcome message
|
445
|
-
console.print("[bold blue]Welcome to the
|
489
|
+
console.print("[bold blue]Welcome to the Interactive Chatterer Shell![/bold blue]")
|
446
490
|
console.print("Type 'quit' or 'exit' to end the conversation.")
|
447
491
|
|
448
492
|
while True:
|
@@ -457,36 +501,80 @@ def chatbot_example(chatterer: Chatterer = Chatterer.openai()) -> None:
|
|
457
501
|
|
458
502
|
# Determine if code execution is needed
|
459
503
|
decision = chatterer.generate_pydantic(
|
460
|
-
response_model=
|
461
|
-
messages=
|
504
|
+
response_model=IsCodeExecutionNeeded, # Use response_model instead of pydantic_model
|
505
|
+
messages=augment_prompt_for_toolcall(
|
506
|
+
function_signatures=function_signatures,
|
507
|
+
messages=context,
|
508
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
509
|
+
function_reference_prefix=function_reference_prefix,
|
510
|
+
function_reference_seperator=function_reference_seperator,
|
511
|
+
),
|
462
512
|
)
|
463
513
|
|
464
514
|
if decision.is_code_execution_needed:
|
465
515
|
# Execute code if needed
|
466
|
-
code_result = chatterer.invoke_code_execution(
|
516
|
+
code_result = chatterer.invoke_code_execution(
|
517
|
+
messages=context,
|
518
|
+
repl_tool=repl_tool,
|
519
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
520
|
+
function_signatures=function_signatures,
|
521
|
+
function_reference_prefix=function_reference_prefix,
|
522
|
+
function_reference_seperator=function_reference_seperator,
|
523
|
+
config=config,
|
524
|
+
stop=stop,
|
525
|
+
**kwargs,
|
526
|
+
)
|
527
|
+
|
467
528
|
if code_result.code.strip() == "pass":
|
468
|
-
|
529
|
+
tool_use_message = None
|
469
530
|
else:
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
531
|
+
code_session_messages: list[BaseMessage] = []
|
532
|
+
while True:
|
533
|
+
code_execution_message = SystemMessage(
|
534
|
+
content=f"Executed code:\n```python\n{code_result.code}\n```\nOutput:\n{code_result.output}"
|
535
|
+
)
|
536
|
+
code_session_messages.append(code_execution_message)
|
537
|
+
console.print("[bold yellow]Executed code:[/bold yellow]")
|
538
|
+
console.print(f"[code]{code_result.code}[/code]")
|
539
|
+
console.print("[bold yellow]Output:[/bold yellow]")
|
540
|
+
console.print(code_result.output)
|
541
|
+
|
542
|
+
decision = chatterer.generate_pydantic(
|
543
|
+
response_model=IsFurtherCodeExecutionNeeded, # Use response_model instead of pydantic_model
|
544
|
+
messages=augment_prompt_for_toolcall(
|
545
|
+
function_signatures=function_signatures,
|
546
|
+
messages=context + code_session_messages,
|
547
|
+
prompt_for_code_invoke=prompt_for_code_invoke,
|
548
|
+
function_reference_prefix=function_reference_prefix,
|
549
|
+
function_reference_seperator=function_reference_seperator,
|
550
|
+
),
|
551
|
+
)
|
552
|
+
review_on_code_execution = decision.review_on_code_execution
|
553
|
+
next_action = decision.next_action
|
554
|
+
console.print("[bold blue]AI:[/bold blue]")
|
555
|
+
console.print(f"-[bold yellow]Review on code execution:[/bold yellow] {review_on_code_execution}")
|
556
|
+
console.print(f"-[bold yellow]Next Action:[/bold yellow] {next_action}")
|
557
|
+
code_session_messages.append(
|
558
|
+
AIMessage(
|
559
|
+
content=f"- Review upon code execution: {decision.review_on_code_execution}\n- Next Action: {decision.next_action}"
|
560
|
+
)
|
561
|
+
)
|
562
|
+
if not decision.is_further_code_execution_needed:
|
563
|
+
tool_use_message = code_execution_message
|
564
|
+
break
|
477
565
|
else:
|
478
566
|
# No code execution required
|
479
|
-
|
567
|
+
tool_use_message = None
|
480
568
|
|
481
569
|
# Add system message to context
|
482
|
-
if
|
483
|
-
context.append(
|
570
|
+
if tool_use_message:
|
571
|
+
context.append(tool_use_message)
|
484
572
|
|
485
573
|
# Generate and display chatbot response
|
486
574
|
response = chatterer.generate(messages=context) # Use generate instead of generate_response
|
487
575
|
context.append(AIMessage(content=response))
|
488
|
-
console.print(f"[bold blue]
|
576
|
+
console.print(f"[bold blue]AI:[/bold blue] {response}")
|
489
577
|
|
490
578
|
|
491
579
|
if __name__ == "__main__":
|
492
|
-
|
580
|
+
interactive_shell()
|
chatterer/tools/__init__.py
CHANGED
@@ -6,6 +6,7 @@ from .convert_to_text import (
|
|
6
6
|
pdf_to_text,
|
7
7
|
pyscripts_to_snippets,
|
8
8
|
)
|
9
|
+
from .youtube import get_youtube_video_subtitle, get_youtube_video_details
|
9
10
|
|
10
11
|
|
11
12
|
def init_webpage_to_markdown():
|
@@ -22,4 +23,6 @@ __all__ = [
|
|
22
23
|
"pyscripts_to_snippets",
|
23
24
|
"citation_chunker",
|
24
25
|
"init_webpage_to_markdown",
|
26
|
+
"get_youtube_video_subtitle",
|
27
|
+
"get_youtube_video_details",
|
25
28
|
]
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import json
|
2
|
+
import unicodedata
|
3
|
+
import urllib.parse
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Any, Optional, Self, cast
|
6
|
+
|
7
|
+
import requests
|
8
|
+
|
9
|
+
|
10
|
+
def get_youtube_video_details(
|
11
|
+
query: str,
|
12
|
+
) -> list[dict[str, Optional[str]]]:
|
13
|
+
"""Search for video metadata on YouTube using the given query. Returns a list of dictionaries containing `video_id`, `title`, `channel`, `duration`, `views`, `publish_time`, and `long_desc`."""
|
14
|
+
return [
|
15
|
+
{
|
16
|
+
"video_id": video_id,
|
17
|
+
"title": video.title,
|
18
|
+
"channel": video.channel,
|
19
|
+
"duration": video.duration,
|
20
|
+
"views": video.views,
|
21
|
+
"publish_time": video.publish_time,
|
22
|
+
"long_desc": video.long_desc,
|
23
|
+
}
|
24
|
+
for video in YoutubeSearchResult.from_query(base_url="https://youtube.com", query=query, max_results=10)
|
25
|
+
if (video_id := _get_video_id(video.url_suffix))
|
26
|
+
]
|
27
|
+
|
28
|
+
|
29
|
+
def get_youtube_video_subtitle(video_id: str) -> str:
|
30
|
+
"""Get the transcript of a YouTube video using the given video ID."""
|
31
|
+
|
32
|
+
from youtube_transcript_api._api import YouTubeTranscriptApi
|
33
|
+
|
34
|
+
get_transcript = YouTubeTranscriptApi.get_transcript # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
35
|
+
list_transcripts = YouTubeTranscriptApi.list_transcripts # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
36
|
+
|
37
|
+
result: str = ""
|
38
|
+
buffer_timestamp: str = "0s"
|
39
|
+
buffer_texts: list[str] = []
|
40
|
+
for entry in get_transcript(video_id, languages=(next(iter(list_transcripts(video_id))).language_code,)): # pyright: ignore[reportUnknownVariableType]
|
41
|
+
entry = cast(dict[object, object], entry)
|
42
|
+
text: str = str(entry.get("text", "")).strip().replace("\n", " ")
|
43
|
+
if not text:
|
44
|
+
continue
|
45
|
+
if len(buffer_texts) >= 10 or _is_special_char(text) or (buffer_texts and _is_special_char(buffer_texts[-1])):
|
46
|
+
result += f"[{buffer_timestamp}] {'. '.join(buffer_texts)}\n"
|
47
|
+
start = entry.get("start", 0)
|
48
|
+
if start:
|
49
|
+
buffer_timestamp = f"{start:.0f}s"
|
50
|
+
buffer_texts = [text]
|
51
|
+
else:
|
52
|
+
buffer_texts.append(text)
|
53
|
+
|
54
|
+
if buffer_texts:
|
55
|
+
result += f"[{buffer_timestamp}] {' '.join(buffer_texts)}"
|
56
|
+
return result
|
57
|
+
|
58
|
+
|
59
|
+
def _get_video_id(suffix: str) -> str:
|
60
|
+
return next(iter(urllib.parse.parse_qs(urllib.parse.urlparse(suffix).query)["v"]), "")
|
61
|
+
|
62
|
+
|
63
|
+
def _is_special_char(text: str) -> bool:
|
64
|
+
if not text:
|
65
|
+
return False
|
66
|
+
return not unicodedata.category(text[0]).startswith("L")
|
67
|
+
|
68
|
+
|
69
|
+
@dataclass
|
70
|
+
class YoutubeSearchResult:
|
71
|
+
url_suffix: str
|
72
|
+
id: Optional[str]
|
73
|
+
thumbnails: list[str]
|
74
|
+
title: Optional[str]
|
75
|
+
long_desc: Optional[str]
|
76
|
+
channel: Optional[str]
|
77
|
+
duration: Optional[str]
|
78
|
+
views: Optional[str]
|
79
|
+
publish_time: Optional[str]
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def from_query(cls, base_url: str, query: str, max_results: int) -> list[Self]:
|
83
|
+
url: str = f"{base_url}/results?search_query={urllib.parse.quote_plus(query)}"
|
84
|
+
response: str = requests.get(url).text
|
85
|
+
while "ytInitialData" not in response:
|
86
|
+
response = requests.get(url).text
|
87
|
+
results: list[Self] = cls.parse_html(response)
|
88
|
+
return results[:max_results]
|
89
|
+
|
90
|
+
@classmethod
|
91
|
+
def parse_html(cls, html: str) -> list[Self]:
|
92
|
+
results: list[Self] = []
|
93
|
+
start: int = html.index("ytInitialData") + len("ytInitialData") + 3
|
94
|
+
end: int = html.index("};", start) + 1
|
95
|
+
data: Any = json.loads(html[start:end])
|
96
|
+
for contents in data["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"][
|
97
|
+
"contents"
|
98
|
+
]:
|
99
|
+
for video in contents["itemSectionRenderer"]["contents"]:
|
100
|
+
if "videoRenderer" in video.keys():
|
101
|
+
video_data = video.get("videoRenderer", {})
|
102
|
+
suffix = (
|
103
|
+
video_data.get("navigationEndpoint", {})
|
104
|
+
.get("commandMetadata", {})
|
105
|
+
.get("webCommandMetadata", {})
|
106
|
+
.get("url", None)
|
107
|
+
)
|
108
|
+
if not suffix:
|
109
|
+
continue
|
110
|
+
res = cls(
|
111
|
+
id=video_data.get("videoId", None),
|
112
|
+
thumbnails=[
|
113
|
+
thumb.get("url", None) for thumb in video_data.get("thumbnail", {}).get("thumbnails", [{}])
|
114
|
+
],
|
115
|
+
title=video_data.get("title", {}).get("runs", [[{}]])[0].get("text", None),
|
116
|
+
long_desc=video_data.get("descriptionSnippet", {}).get("runs", [{}])[0].get("text", None),
|
117
|
+
channel=video_data.get("longBylineText", {}).get("runs", [[{}]])[0].get("text", None),
|
118
|
+
duration=video_data.get("lengthText", {}).get("simpleText", 0),
|
119
|
+
views=video_data.get("viewCountText", {}).get("simpleText", 0),
|
120
|
+
publish_time=video_data.get("publishedTimeText", {}).get("simpleText", 0),
|
121
|
+
url_suffix=suffix,
|
122
|
+
)
|
123
|
+
results.append(res)
|
124
|
+
|
125
|
+
if results:
|
126
|
+
break
|
127
|
+
return results
|
128
|
+
|
129
|
+
|
130
|
+
if __name__ == "__main__":
|
131
|
+
print(get_youtube_video_details("BTS"))
|
132
|
+
# print(get_youtube_transcript("y7jrpS8GHxs"))
|
chatterer/utils/code_agent.py
CHANGED
@@ -21,7 +21,15 @@ class FunctionSignature(NamedTuple):
|
|
21
21
|
signature: str
|
22
22
|
|
23
23
|
@classmethod
|
24
|
-
def from_callable(cls,
|
24
|
+
def from_callable(cls, callables: Optional[Callable[..., object] | Iterable[Callable[..., object]]]) -> list[Self]:
|
25
|
+
if callables is None:
|
26
|
+
return []
|
27
|
+
if callable(callables):
|
28
|
+
return [cls._from_callable(callables)]
|
29
|
+
return [cls._from_callable(callable) for callable in callables]
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
def _from_callable(cls, callable: Callable[..., object]) -> Self:
|
25
33
|
"""
|
26
34
|
Get the name and signature of a function as a string.
|
27
35
|
"""
|
@@ -54,21 +62,17 @@ class FunctionSignature(NamedTuple):
|
|
54
62
|
else:
|
55
63
|
return cls(name=function_name, callable=callable, signature=signature)
|
56
64
|
|
57
|
-
@classmethod
|
58
|
-
def from_callables(cls, callables: Iterable[Callable[..., object]]) -> list[Self]:
|
59
|
-
return [cls.from_callable(callable) for callable in callables]
|
60
|
-
|
61
65
|
@classmethod
|
62
66
|
def as_prompt(
|
63
67
|
cls,
|
64
|
-
|
68
|
+
function_signatures: Iterable[Self],
|
65
69
|
prefix: Optional[str] = "You can use the pre-made functions below without defining them:\n",
|
66
70
|
sep: str = "\n---\n",
|
67
71
|
) -> str:
|
68
72
|
"""
|
69
73
|
Generate a prompt string from a list of callables.
|
70
74
|
"""
|
71
|
-
body: str = sep.join(fsig.signature for fsig in
|
75
|
+
body: str = sep.join(fsig.signature for fsig in function_signatures)
|
72
76
|
if prefix:
|
73
77
|
return f"{prefix}{body}"
|
74
78
|
return body
|
@@ -92,7 +96,7 @@ class CodeExecutionResult(NamedTuple):
|
|
92
96
|
"""
|
93
97
|
if repl_tool is None:
|
94
98
|
repl_tool = get_default_repl_tool()
|
95
|
-
if function_signatures
|
99
|
+
if function_signatures:
|
96
100
|
insert_callables_into_global(function_signatures=function_signatures, repl_tool=repl_tool)
|
97
101
|
output = str(repl_tool.invoke(code, config=config, **kwargs)) # pyright: ignore[reportUnknownMemberType]
|
98
102
|
return cls(code=code, output=output)
|
@@ -111,7 +115,7 @@ class CodeExecutionResult(NamedTuple):
|
|
111
115
|
"""
|
112
116
|
if repl_tool is None:
|
113
117
|
repl_tool = get_default_repl_tool()
|
114
|
-
if function_signatures
|
118
|
+
if function_signatures:
|
115
119
|
insert_callables_into_global(function_signatures=function_signatures, repl_tool=repl_tool)
|
116
120
|
output = str(await repl_tool.ainvoke(code, config=config, **kwargs)) # pyright: ignore[reportUnknownMemberType]
|
117
121
|
return cls(code=code, output=output)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: chatterer
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.10
|
4
4
|
Summary: The highest-level interface for various LLM APIs.
|
5
5
|
Requires-Python: >=3.12
|
6
6
|
Description-Content-Type: text/markdown
|
@@ -18,6 +18,7 @@ Requires-Dist: pillow>=11.1.0; extra == "conversion"
|
|
18
18
|
Requires-Dist: mistune>=3.1.2; extra == "conversion"
|
19
19
|
Requires-Dist: markitdown>=0.0.2; extra == "conversion"
|
20
20
|
Requires-Dist: pymupdf>=1.25.4; extra == "conversion"
|
21
|
+
Requires-Dist: youtube-transcript-api>=1.0.2; extra == "conversion"
|
21
22
|
Provides-Extra: langchain
|
22
23
|
Requires-Dist: chatterer[langchain-providers]; extra == "langchain"
|
23
24
|
Requires-Dist: langchain-experimental>=0.3.4; extra == "langchain"
|
@@ -1,12 +1,13 @@
|
|
1
|
-
chatterer/__init__.py,sha256=
|
2
|
-
chatterer/language_model.py,sha256=
|
1
|
+
chatterer/__init__.py,sha256=BPgCQ6VWGBXSh8xJr_0bpM0hcOOUz0KoxcKxOd9GYyI,1388
|
2
|
+
chatterer/language_model.py,sha256=qnVC5_W4IYM0y0o1PTYMGXUlblRv5fsRk0zIiL_vT3Q,24491
|
3
3
|
chatterer/messages.py,sha256=OtbZ3two0LUQ4PXES97FDIBUSO3IcMHdFV1VFkDL2mI,229
|
4
4
|
chatterer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
chatterer/strategies/__init__.py,sha256=SdOggbmHpw4f7Njwy-T8q64e91OLOUp1k0a0ozZd4qI,221
|
6
6
|
chatterer/strategies/atom_of_thoughts.py,sha256=CygOCLu5vLk-fzY9O-iE3qLShfjD7iY40ks9jH4ULBM,40872
|
7
7
|
chatterer/strategies/base.py,sha256=b2gMPqodp97OP1dkHfj0UqixjdjVhmTw_V5qJ7i2S6g,427
|
8
|
-
chatterer/tools/__init__.py,sha256=
|
8
|
+
chatterer/tools/__init__.py,sha256=hmWIuLJWotGQodL__i4LLbHdXe7Nl5uKHqNke9tHMro,705
|
9
9
|
chatterer/tools/convert_to_text.py,sha256=kBqxCJ0IoiAw2eiPYqep_SPZm-TtYKF7mdACLsWQUuI,15915
|
10
|
+
chatterer/tools/youtube.py,sha256=5hwASZWA92d_7Y5RqlCK2tULRBQx8bGnE_0NCnvaKi0,5499
|
10
11
|
chatterer/tools/citation_chunking/__init__.py,sha256=gG7Fnkkp28UpcWMbfMY_4gqzZSZ8QzlhalHBoeoq7K0,82
|
11
12
|
chatterer/tools/citation_chunking/chunks.py,sha256=50Dpa43RaYftlNox8tM1qI8htZ3_AJ9Uyyn02WsmxYk,2173
|
12
13
|
chatterer/tools/citation_chunking/citation_chunker.py,sha256=yx5O9pUkowlNcFyyNf7f3sbq7-CV8AXOzFnviDldPR8,4894
|
@@ -18,9 +19,9 @@ chatterer/tools/webpage_to_markdown/__init__.py,sha256=bHH4qfnXyw8Zz-yBPLaTezF1s
|
|
18
19
|
chatterer/tools/webpage_to_markdown/playwright_bot.py,sha256=yP0KixYZNQ4Kn_ZCFDI3mVyBD_DpUGfqgklpaGJUTCU,27496
|
19
20
|
chatterer/tools/webpage_to_markdown/utils.py,sha256=ZLUU94imYciEdynD2K7Dmcsbt8BVQTaOP56Ba6DAFvk,12593
|
20
21
|
chatterer/utils/__init__.py,sha256=8nzpFJKU_wSRPH6LBP6HRBotPMrSl_VO9UlmFprTrK0,334
|
21
|
-
chatterer/utils/code_agent.py,sha256=
|
22
|
+
chatterer/utils/code_agent.py,sha256=UaWdeGzJMPzRSFy9yrxuveBJsvOPSa0te6OuE18bees,5143
|
22
23
|
chatterer/utils/image.py,sha256=F3_D1677UDFlgp-UQBS_ChkNODzf_VOfjYNSUi02MaI,10852
|
23
|
-
chatterer-0.1.
|
24
|
-
chatterer-0.1.
|
25
|
-
chatterer-0.1.
|
26
|
-
chatterer-0.1.
|
24
|
+
chatterer-0.1.10.dist-info/METADATA,sha256=qPx7b41yUvBG0XFH4ra89LIyOSUZUo_8gZ-adVkTKME,4458
|
25
|
+
chatterer-0.1.10.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
26
|
+
chatterer-0.1.10.dist-info/top_level.txt,sha256=7nSQKP0bHxPRc7HyzdbKsJdkvPgYD0214o6slRizv9s,10
|
27
|
+
chatterer-0.1.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|