fabricatio 0.2.6.dev7__cp312-cp312-manylinux_2_34_x86_64.whl → 0.2.7__cp312-cp312-manylinux_2_34_x86_64.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.
fabricatio/_rust.pyi CHANGED
@@ -11,7 +11,7 @@ class TemplateManager:
11
11
  """
12
12
 
13
13
  def __init__(
14
- self, template_dirs: List[Path], suffix: Optional[str] = None, active_loading: Optional[bool] = None
14
+ self, template_dirs: List[Path], suffix: Optional[str] = None, active_loading: Optional[bool] = None
15
15
  ) -> None:
16
16
  """Initialize the template manager.
17
17
 
@@ -55,6 +55,16 @@ class TemplateManager:
55
55
  RuntimeError: If template rendering fails
56
56
  """
57
57
 
58
+ def render_template_raw(self, template: str, data: Dict[str, Any]) -> str:
59
+ """Render a template with context data.
60
+
61
+ Args:
62
+ template: The template string
63
+ data: Context dictionary to provide variables to the template
64
+
65
+ Returns:
66
+ Rendered template content as string
67
+ """
58
68
 
59
69
  def blake3_hash(content: bytes) -> str:
60
70
  """Calculate the BLAKE3 cryptographic hash of data.
@@ -66,7 +76,6 @@ def blake3_hash(content: bytes) -> str:
66
76
  Hex-encoded BLAKE3 hash string
67
77
  """
68
78
 
69
-
70
79
  class BibManager:
71
80
  """BibTeX bibliography manager for parsing and querying citation data."""
72
81
 
@@ -1,16 +1,17 @@
1
1
  """Actions for transmitting tasks to targets."""
2
2
 
3
- from os import PathLike
4
3
  from pathlib import Path
5
4
  from typing import Any, Callable, List, Optional
6
5
 
7
6
  from fabricatio.fs import safe_text_read
8
7
  from fabricatio.journal import logger
9
8
  from fabricatio.models.action import Action
10
- from fabricatio.models.extra import ArticleEssence, ArticleOutline, ArticleProposal
9
+ from fabricatio.models.extra.article_essence import ArticleEssence
10
+ from fabricatio.models.extra.article_main import Article
11
+ from fabricatio.models.extra.article_outline import ArticleOutline
12
+ from fabricatio.models.extra.article_proposal import ArticleProposal
11
13
  from fabricatio.models.task import Task
12
- from questionary import confirm, text
13
- from rich import print as rprint
14
+ from fabricatio.models.utils import ok
14
15
 
15
16
 
16
17
  class ExtractArticleEssence(Action):
@@ -24,10 +25,10 @@ class ExtractArticleEssence(Action):
24
25
  output_key: str = "article_essence"
25
26
  """The key of the output data."""
26
27
 
27
- async def _execute[P: PathLike | str](
28
+ async def _execute(
28
29
  self,
29
30
  task_input: Task,
30
- reader: Callable[[P], str] = lambda p: Path(p).read_text(encoding="utf-8"),
31
+ reader: Callable[[str], str] = lambda p: Path(p).read_text(encoding="utf-8"),
31
32
  **_,
32
33
  ) -> Optional[List[ArticleEssence]]:
33
34
  if not task_input.dependencies:
@@ -51,18 +52,34 @@ class GenerateArticleProposal(Action):
51
52
 
52
53
  async def _execute(
53
54
  self,
54
- task_input: Task,
55
+ task_input: Optional[Task] = None,
56
+ article_briefing: Optional[str] = None,
57
+ article_briefing_path: Optional[str] = None,
55
58
  **_,
56
59
  ) -> Optional[ArticleProposal]:
57
- input_path = await self.awhich_pathstr(
58
- f"{task_input.briefing}\nExtract the path of file, which contains the article briefing that I need to read."
59
- )
60
-
61
- return await self.propose(
62
- ArticleProposal,
63
- safe_text_read(input_path),
64
- system_message=f"# your personal briefing: \n{self.briefing}",
65
- )
60
+ if article_briefing is None and article_briefing_path is None and task_input is None:
61
+ logger.error("Task not approved, since all inputs are None.")
62
+ return None
63
+
64
+ return ok(
65
+ await self.propose(
66
+ ArticleProposal,
67
+ briefing := (
68
+ article_briefing
69
+ or safe_text_read(
70
+ ok(
71
+ article_briefing_path
72
+ or await self.awhich_pathstr(
73
+ f"{ok(task_input).briefing}\nExtract the path of file which contains the article briefing."
74
+ ),
75
+ "Could not find the path of file to read.",
76
+ )
77
+ )
78
+ ),
79
+ **self.prepend_sys_msg(),
80
+ ),
81
+ "Could not generate the proposal.",
82
+ ).update_ref(briefing)
66
83
 
67
84
 
68
85
  class GenerateOutline(Action):
@@ -76,35 +93,111 @@ class GenerateOutline(Action):
76
93
  article_proposal: ArticleProposal,
77
94
  **_,
78
95
  ) -> Optional[ArticleOutline]:
79
- return await self.propose(
80
- ArticleOutline,
81
- article_proposal.display(),
82
- system_message=f"# your personal briefing: \n{self.briefing}",
96
+ out = ok(
97
+ await self.propose(
98
+ ArticleOutline,
99
+ article_proposal.as_prompt(),
100
+ **self.prepend_sys_msg(),
101
+ ),
102
+ "Could not generate the outline.",
103
+ )
104
+
105
+ introspect_manual = ok(
106
+ await self.draft_rating_manual(
107
+ topic=(
108
+ intro_topic
109
+ := "Fix the error in the article outline, make sure there is no more error in the article outline."
110
+ ),
111
+ ),
112
+ "Could not generate the rating manual.",
113
+ )
114
+
115
+ while pack := out.find_introspected():
116
+ component, err = ok(pack)
117
+ logger.warning(f"Found introspected error: {err}")
118
+ corrected = ok(
119
+ await self.correct_obj(
120
+ component,
121
+ reference=f"# Original Article Outline\n{out.display()}\n# Error Need to be fixed\n{err}",
122
+ topic=intro_topic,
123
+ rating_manual=introspect_manual,
124
+ supervisor_check=False,
125
+ ),
126
+ "Could not correct the component.",
127
+ )
128
+ component.update_from(corrected)
129
+
130
+ ref_manual = ok(
131
+ await self.draft_rating_manual(
132
+ topic=(
133
+ ref_topic
134
+ := "Fix the internal referring error, make sure there is no more `ArticleRef` pointing to a non-existing article component."
135
+ ),
136
+ ),
137
+ "Could not generate the rating manual.",
138
+ )
139
+
140
+ while pack := out.find_illegal_ref():
141
+ component, err = ok(pack)
142
+ logger.warning(f"Found illegal referring error: {err}")
143
+ corrected_metadata = ok(
144
+ await self.correct_obj(
145
+ component.metadata,
146
+ reference=f"# Original Article Outline\n{out.display()}\n# Error Need to be fixed\n{err}\n\n# Create a new metadata to handle the error.",
147
+ topic=ref_topic,
148
+ rating_manual=ref_manual,
149
+ supervisor_check=False,
150
+ )
151
+ )
152
+ component.update_metadata(corrected_metadata)
153
+ return out.update_ref(article_proposal)
154
+
155
+
156
+ class GenerateArticle(Action):
157
+ """Generate the article based on the outline."""
158
+
159
+ output_key: str = "article"
160
+ """The key of the output data."""
161
+
162
+ async def _execute(
163
+ self,
164
+ article_outline: ArticleOutline,
165
+ **_,
166
+ ) -> Optional[Article]:
167
+ article: Article = Article.from_outline(ok(article_outline, "Article outline not specified.")).update_ref(
168
+ article_outline
83
169
  )
84
170
 
171
+ write_para_manual = ok(
172
+ await self.draft_rating_manual(w_topic := "write the following paragraph in the subsection.")
173
+ )
174
+
175
+ for *_, subsec in article.iter_subsections():
176
+ corrected_subsec = await self.correct_obj(
177
+ subsec,
178
+ reference=f"# Original Article Outline\n{article_outline.display()}\n# Error Need to be fixed\n{subsec.introspect()}",
179
+ topic=w_topic,
180
+ rating_manual=write_para_manual,
181
+ supervisor_check=False,
182
+ )
183
+ subsec.paragraphs.clear()
184
+ subsec.paragraphs.extend(corrected_subsec.paragraphs)
185
+
186
+ logger.success(f"Finished: {article.display()}")
187
+ logger.success(f"Dump: {article.finalized_dump()}")
188
+ return article
189
+
85
190
 
86
191
  class CorrectProposal(Action):
87
192
  """Correct the proposal of the article."""
88
193
 
89
194
  output_key: str = "corrected_proposal"
90
195
 
91
- async def _execute(self, task_input: Task, article_proposal: ArticleProposal, **_) -> Any:
92
- input_path = await self.awhich_pathstr(
93
- f"{task_input.briefing}\nExtract the path of file, which contains the article briefing that I need to read."
196
+ async def _execute(self, article_proposal: ArticleProposal, **_) -> Any:
197
+ return (await self.censor_obj(article_proposal, reference=article_proposal.referenced)).update_ref(
198
+ article_proposal
94
199
  )
95
200
 
96
- ret = None
97
- while await confirm("Do you want to correct the Proposal?").ask_async():
98
- rprint(article_proposal.display())
99
- while not (topic := await text("What is the topic of the proposal reviewing?").ask_async()):
100
- ...
101
- ret = await self.correct_obj(
102
- article_proposal,
103
- safe_text_read(input_path),
104
- topic=topic,
105
- )
106
- return ret or article_proposal
107
-
108
201
 
109
202
  class CorrectOutline(Action):
110
203
  """Correct the outline of the article."""
@@ -115,14 +208,23 @@ class CorrectOutline(Action):
115
208
  async def _execute(
116
209
  self,
117
210
  article_outline: ArticleOutline,
118
- article_proposal: ArticleProposal,
211
+ **_,
212
+ ) -> ArticleOutline:
213
+ return (await self.censor_obj(article_outline, reference=article_outline.referenced.as_prompt())).update_ref(
214
+ article_outline
215
+ )
216
+
217
+
218
+ class CorrectArticle(Action):
219
+ """Correct the article based on the outline."""
119
220
 
221
+ output_key: str = "corrected_article"
222
+ """The key of the output data."""
223
+
224
+ async def _execute(
225
+ self,
226
+ article: Article,
227
+ article_outline: ArticleOutline,
120
228
  **_,
121
- ) -> Optional[str]:
122
- ret = None
123
- while await confirm("Do you want to correct the outline?").ask_async():
124
- rprint(article_outline.finalized_dump())
125
- while not (topic := await text("What is the topic of the outline reviewing?").ask_async()):
126
- ...
127
- ret = await self.correct_obj(article_outline, article_proposal.display(), topic=topic)
128
- return ret or article_outline
229
+ ) -> Article:
230
+ return await self.censor_obj(article, reference=article_outline.referenced.as_prompt())
@@ -0,0 +1,35 @@
1
+ """A module for writing articles using RAG (Retrieval-Augmented Generation) capabilities."""
2
+
3
+ from typing import Optional
4
+
5
+ from fabricatio.capabilities.rag import RAG
6
+ from fabricatio.journal import logger
7
+ from fabricatio.models.action import Action
8
+ from fabricatio.models.extra.article_main import Article
9
+ from fabricatio.models.extra.article_outline import ArticleOutline
10
+
11
+
12
+ class GenerateArticleRAG(Action, RAG):
13
+ """Write an article based on the provided outline."""
14
+
15
+ output_key: str = "article"
16
+
17
+ async def _execute(self, article_outline: ArticleOutline, **cxt) -> Optional[Article]:
18
+ """Write an article based on the provided outline."""
19
+ logger.info(f"Writing an article based on the outline:\n{article_outline.title}")
20
+ refined_q = await self.arefined_query(article_outline.display())
21
+ return await self.propose(
22
+ Article,
23
+ article_outline.display(),
24
+ **self.prepend_sys_msg(f"{await self.aretrieve_compact(refined_q)}\n{self.briefing}"),
25
+ )
26
+
27
+
28
+ class WriteArticleFineGrind(Action, RAG):
29
+ """Fine-grind an article based on the provided outline."""
30
+
31
+ output_key: str = "article"
32
+
33
+ async def _execute(self, article_outline: ArticleOutline, **cxt) -> Optional[Article]:
34
+ """Fine-grind an article based on the provided outline."""
35
+ logger.info(f"Fine-grinding an article based on the outline:\n{article_outline.title}")
@@ -1,8 +1,12 @@
1
1
  """Dump the finalized output to a file."""
2
2
 
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
3
6
  from fabricatio.models.action import Action
4
7
  from fabricatio.models.generic import FinalizedDumpAble
5
8
  from fabricatio.models.task import Task
9
+ from fabricatio.models.utils import ok
6
10
 
7
11
 
8
12
  class DumpFinalizedOutput(Action):
@@ -10,10 +14,21 @@ class DumpFinalizedOutput(Action):
10
14
 
11
15
  output_key: str = "dump_path"
12
16
 
13
- async def _execute(self, task_input: Task, to_dump: FinalizedDumpAble, **_) -> str:
14
- dump_path = await self.awhich_pathstr(
15
- f"{task_input.briefing}\n\nExtract a single path of the file, to which I will dump the data."
17
+ async def _execute(
18
+ self,
19
+ to_dump: FinalizedDumpAble,
20
+ task_input: Optional[Task] = None,
21
+ dump_path: Optional[str | Path] = None,
22
+ **_,
23
+ ) -> str:
24
+ dump_path = Path(
25
+ dump_path
26
+ or ok(
27
+ await self.awhich_pathstr(
28
+ f"{ok(task_input, 'Neither `task_input` and `dump_path` is provided.').briefing}\n\nExtract a single path of the file, to which I will dump the data."
29
+ ),
30
+ "Could not find the path of file to dump the data.",
31
+ )
16
32
  )
17
-
18
- to_dump.finalized_dump_to(dump_path)
19
- return dump_path
33
+ ok(to_dump, "Could not dump the data since the path is not specified.").finalized_dump_to(dump_path)
34
+ return dump_path.as_posix()
fabricatio/actions/rag.py CHANGED
@@ -3,8 +3,11 @@
3
3
  from typing import List, Optional
4
4
 
5
5
  from fabricatio.capabilities.rag import RAG
6
+ from fabricatio.journal import logger
6
7
  from fabricatio.models.action import Action
7
8
  from fabricatio.models.generic import PrepareVectorization
9
+ from fabricatio.models.task import Task
10
+ from questionary import text
8
11
 
9
12
 
10
13
  class InjectToDB(Action, RAG):
@@ -13,13 +16,58 @@ class InjectToDB(Action, RAG):
13
16
  output_key: str = "collection_name"
14
17
 
15
18
  async def _execute[T: PrepareVectorization](
16
- self, to_inject: T | List[T], collection_name: Optional[str] = "my_collection", **_
19
+ self, to_inject: Optional[T] | List[Optional[T]], collection_name: str = "my_collection",override_inject:bool=False, **_
17
20
  ) -> Optional[str]:
18
21
  if not isinstance(to_inject, list):
19
22
  to_inject = [to_inject]
20
-
23
+ logger.info(f"Injecting {len(to_inject)} items into the collection '{collection_name}'")
24
+ if override_inject:
25
+ self.check_client().client.drop_collection(collection_name)
21
26
  await self.view(collection_name, create=True).consume_string(
22
- [t.prepare_vectorization(self.embedding_max_sequence_length) for t in to_inject],
27
+ [
28
+ t.prepare_vectorization(self.embedding_max_sequence_length)
29
+ for t in to_inject
30
+ if isinstance(t, PrepareVectorization)
31
+ ],
23
32
  )
24
33
 
25
34
  return collection_name
35
+
36
+
37
+ class RAGTalk(Action, RAG):
38
+ """RAG-enabled conversational action that processes user questions based on a given task.
39
+
40
+ This action establishes an interactive conversation loop where it retrieves context-relevant
41
+ information to answer user queries according to the assigned task briefing.
42
+
43
+ Notes:
44
+ task_input: Task briefing that guides how to respond to user questions
45
+ collection_name: Name of the vector collection to use for retrieval (default: "my_collection")
46
+
47
+ Returns:
48
+ Number of conversation turns completed before termination
49
+ """
50
+
51
+ output_key: str = "task_output"
52
+
53
+ async def _execute(self, task_input: Task[str], **kwargs) -> int:
54
+ collection_name = kwargs.get("collection_name", "my_collection")
55
+ counter = 0
56
+
57
+ self.view(collection_name, create=True)
58
+
59
+ try:
60
+ while True:
61
+ user_say = await text("User: ").ask_async()
62
+ if user_say is None:
63
+ break
64
+ gpt_say = await self.aask_retrieved(
65
+ user_say,
66
+ user_say,
67
+ extra_system_message=f"You have to answer to user obeying task assigned to you:\n{task_input.briefing}",
68
+ )
69
+ print(f"GPT: {gpt_say}") # noqa: T201
70
+ counter += 1
71
+ except KeyboardInterrupt:
72
+ logger.info(f"executed talk action {counter} times")
73
+ return counter
@@ -10,9 +10,11 @@ from typing import Optional, Unpack, cast
10
10
  from fabricatio._rust_instances import TEMPLATE_MANAGER
11
11
  from fabricatio.capabilities.review import Review, ReviewResult
12
12
  from fabricatio.config import configs
13
- from fabricatio.models.generic import Display, ProposedAble, WithBriefing
14
- from fabricatio.models.kwargs_types import CorrectKwargs, ReviewKwargs
13
+ from fabricatio.models.generic import CensoredAble, Display, ProposedAble, WithBriefing
14
+ from fabricatio.models.kwargs_types import CensoredCorrectKwargs, CorrectKwargs, ReviewKwargs
15
15
  from fabricatio.models.task import Task
16
+ from questionary import confirm, text
17
+ from rich import print as rprint
16
18
 
17
19
 
18
20
  class Correct(Review):
@@ -55,7 +57,7 @@ class Correct(Review):
55
57
  if supervisor_check:
56
58
  await review_res.supervisor_check()
57
59
  if "default" in kwargs:
58
- cast(ReviewKwargs[None], kwargs)["default"] = None
60
+ cast("ReviewKwargs[None]", kwargs)["default"] = None
59
61
  return await self.propose(
60
62
  obj.__class__,
61
63
  TEMPLATE_MANAGER.render_template(
@@ -89,7 +91,7 @@ class Correct(Review):
89
91
  await review_res.supervisor_check()
90
92
 
91
93
  if "default" in kwargs:
92
- cast(ReviewKwargs[None], kwargs)["default"] = None
94
+ cast("ReviewKwargs[None]", kwargs)["default"] = None
93
95
  return await self.ageneric_string(
94
96
  TEMPLATE_MANAGER.render_template(
95
97
  configs.templates.correct_template, {"content": input_text, "review": review_res.display()}
@@ -113,3 +115,33 @@ class Correct(Review):
113
115
  Optional[Task[T]]: The corrected task, or None if correction fails.
114
116
  """
115
117
  return await self.correct_obj(task, **kwargs)
118
+
119
+ async def censor_obj[M: CensoredAble](
120
+ self, obj: M, **kwargs: Unpack[CensoredCorrectKwargs[ReviewResult[str]]]
121
+ ) -> M:
122
+ """Censor and correct an object based on defined criteria and templates.
123
+
124
+ Args:
125
+ obj (M): The object to be reviewed and corrected.
126
+ **kwargs (Unpack[CensoredCorrectKwargs]): Additional keyword
127
+
128
+ Returns:
129
+ M: The censored and corrected object.
130
+ """
131
+ last_modified_obj = obj
132
+ modified_obj = None
133
+ rprint(obj.finalized_dump())
134
+ while await confirm("Begin to correct obj above with human censorship?").ask_async():
135
+ while (topic := await text("What is the topic of the obj reviewing?").ask_async()) is not None and topic:
136
+ ...
137
+ if (
138
+ modified_obj := await self.correct_obj(
139
+ last_modified_obj,
140
+ topic=topic,
141
+ **kwargs,
142
+ )
143
+ ) is None:
144
+ break
145
+ last_modified_obj = modified_obj
146
+ rprint(last_modified_obj.finalized_dump())
147
+ return modified_obj or last_modified_obj
@@ -19,6 +19,7 @@ from fabricatio.models.kwargs_types import (
19
19
  EmbeddingKwargs,
20
20
  FetchKwargs,
21
21
  LLMKwargs,
22
+ RetrievalKwargs,
22
23
  )
23
24
  from fabricatio.models.usages import EmbeddingUsage
24
25
  from fabricatio.models.utils import MilvusData, ok
@@ -63,10 +64,18 @@ class RAG(EmbeddingUsage):
63
64
  uri=milvus_uri or ok(self.milvus_uri or configs.rag.milvus_uri).unicode_string(),
64
65
  token=milvus_token
65
66
  or (token.get_secret_value() if (token := (self.milvus_token or configs.rag.milvus_token)) else ""),
66
- timeout=milvus_timeout or self.milvus_timeout,
67
+ timeout=milvus_timeout or self.milvus_timeout or configs.rag.milvus_timeout,
67
68
  )
68
69
  return self
69
70
 
71
+ def check_client(self, init: bool = True) -> Self:
72
+ """Check if the client is initialized, and if not, initialize it."""
73
+ if self._client is None and init:
74
+ return self.init_client()
75
+ if self._client is None and not init:
76
+ raise RuntimeError("Client is not initialized. Have you called `self.init_client()`?")
77
+ return self
78
+
70
79
  @overload
71
80
  async def pack(
72
81
  self, input_text: List[str], subject: Optional[str] = None, **kwargs: Unpack[EmbeddingKwargs]
@@ -111,8 +120,15 @@ class RAG(EmbeddingUsage):
111
120
  create (bool): Whether to create the collection if it does not exist.
112
121
  **kwargs (Unpack[CollectionConfigKwargs]): Additional keyword arguments for collection configuration.
113
122
  """
114
- if create and collection_name and self.client.has_collection(collection_name):
115
- kwargs["dimension"] = kwargs.get("dimension") or self.milvus_dimensions or configs.rag.milvus_dimensions
123
+ if create and collection_name and not self.check_client().client.has_collection(collection_name):
124
+ kwargs["dimension"] = ok(
125
+ kwargs.get("dimension")
126
+ or self.milvus_dimensions
127
+ or configs.rag.milvus_dimensions
128
+ or self.embedding_dimensions
129
+ or configs.embedding.dimensions,
130
+ "`dimension` is not set at any level.",
131
+ )
116
132
  self.client.create_collection(collection_name, auto_id=True, **kwargs)
117
133
  logger.info(f"Creating collection {collection_name}")
118
134
 
@@ -158,7 +174,7 @@ class RAG(EmbeddingUsage):
158
174
  else:
159
175
  raise TypeError(f"Expected MilvusData or list of MilvusData, got {type(data)}")
160
176
  c_name = collection_name or self.safe_target_collection
161
- self.client.insert(c_name, prepared_data)
177
+ self.check_client().client.insert(c_name, prepared_data)
162
178
 
163
179
  if flush:
164
180
  logger.debug(f"Flushing collection {c_name}")
@@ -198,6 +214,25 @@ class RAG(EmbeddingUsage):
198
214
  self.add_document(await self.pack(text), collection_name or self.safe_target_collection, flush=True)
199
215
  return self
200
216
 
217
+ @overload
218
+ async def afetch_document[V: (int, str, float, bytes)](
219
+ self,
220
+ vecs: List[List[float]],
221
+ desired_fields: List[str],
222
+ collection_name: Optional[str] = None,
223
+ similarity_threshold: float = 0.37,
224
+ result_per_query: int = 10,
225
+ ) -> List[Dict[str, V]]: ...
226
+
227
+ @overload
228
+ async def afetch_document[V: (int, str, float, bytes)](
229
+ self,
230
+ vecs: List[List[float]],
231
+ desired_fields: str,
232
+ collection_name: Optional[str] = None,
233
+ similarity_threshold: float = 0.37,
234
+ result_per_query: int = 10,
235
+ ) -> List[V]: ...
201
236
  async def afetch_document[V: (int, str, float, bytes)](
202
237
  self,
203
238
  vecs: List[List[float]],
@@ -219,7 +254,7 @@ class RAG(EmbeddingUsage):
219
254
  List[Dict[str, Any]] | List[Any]: The retrieved data.
220
255
  """
221
256
  # Step 1: Search for vectors
222
- search_results = self.client.search(
257
+ search_results = self.check_client().client.search(
223
258
  collection_name or self.safe_target_collection,
224
259
  vecs,
225
260
  search_params={"radius": similarity_threshold},
@@ -260,7 +295,7 @@ class RAG(EmbeddingUsage):
260
295
  if isinstance(query, str):
261
296
  query = [query]
262
297
  return cast(
263
- List[str],
298
+ "List[str]",
264
299
  await self.afetch_document(
265
300
  vecs=(await self.vectorize(query)),
266
301
  desired_fields="text",
@@ -268,6 +303,24 @@ class RAG(EmbeddingUsage):
268
303
  ),
269
304
  )[:final_limit]
270
305
 
306
+ async def aretrieve_compact(
307
+ self,
308
+ query: List[str] | str,
309
+ **kwargs: Unpack[RetrievalKwargs],
310
+ ) -> str:
311
+ """Retrieve data from the collection and format it for display.
312
+
313
+ Args:
314
+ query (List[str] | str): The query to be used for retrieval.
315
+ **kwargs (Unpack[RetrievalKwargs]): Additional keyword arguments for retrieval.
316
+
317
+ Returns:
318
+ str: A formatted string containing the retrieved data.
319
+ """
320
+ return TEMPLATE_MANAGER.render_template(
321
+ configs.templates.retrieved_display_template, {"docs": (await self.aretrieve(query, **kwargs))}
322
+ )
323
+
271
324
  async def aask_retrieved(
272
325
  self,
273
326
  question: str,
@@ -298,16 +351,14 @@ class RAG(EmbeddingUsage):
298
351
  Returns:
299
352
  str: A string response generated after asking with the context of retrieved documents.
300
353
  """
301
- docs = await self.aretrieve(
354
+ rendered = await self.aretrieve_compact(
302
355
  query or question,
303
- final_limit,
356
+ final_limit=final_limit,
304
357
  collection_name=collection_name,
305
358
  result_per_query=result_per_query,
306
359
  similarity_threshold=similarity_threshold,
307
360
  )
308
361
 
309
- rendered = TEMPLATE_MANAGER.render_template(configs.templates.retrieved_display_template, {"docs": docs[::-1]})
310
-
311
362
  logger.debug(f"Retrieved Documents: \n{rendered}")
312
363
  return await self.aask(
313
364
  question,