langroid 0.1.248__py3-none-any.whl → 0.1.250__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.
langroid/agent/base.py CHANGED
@@ -35,7 +35,7 @@ from langroid.language_models.base import (
35
35
  LLMTokenUsage,
36
36
  StreamingIfAllowed,
37
37
  )
38
- from langroid.language_models.openai_gpt import OpenAIGPTConfig
38
+ from langroid.language_models.openai_gpt import OpenAIGPT, OpenAIGPTConfig
39
39
  from langroid.mytypes import Entity
40
40
  from langroid.parsing.parse_json import extract_top_level_json
41
41
  from langroid.parsing.parser import Parser, ParsingConfig
@@ -94,12 +94,12 @@ class Agent(ABC):
94
94
  self._indent = ""
95
95
  self.llm = LanguageModel.create(config.llm)
96
96
  self.vecdb = VectorStore.create(config.vecdb) if config.vecdb else None
97
- # token_encoding_model is used to obtain the tokenizer,
98
- # so we ensure that the tokenizer corresponding to the model is used.
99
97
  if config.parsing is not None and self.config.llm is not None:
100
- config.parsing.token_encoding_model = (
101
- self.config.llm.chat_model or self.config.llm.completion_model
102
- )
98
+ # token_encoding_model is used to obtain the tokenizer,
99
+ # so in case it's an OpenAI model, we ensure that the tokenizer
100
+ # corresponding to the model is used.
101
+ if isinstance(self.llm, OpenAIGPT) and self.llm.is_openai_chat_model():
102
+ config.parsing.token_encoding_model = self.llm.config.chat_model
103
103
  self.parser: Optional[Parser] = (
104
104
  Parser(config.parsing) if config.parsing else None
105
105
  )
@@ -98,7 +98,7 @@ async def make_llm_settings_widgets(
98
98
  [
99
99
  cl.input_widget.TextInput(
100
100
  id="chat_model",
101
- label="Model Name (Default GPT4-Turbo)",
101
+ label="Model Name (Default GPT-4o)",
102
102
  initial="",
103
103
  placeholder="E.g. ollama/mistral or " "local/localhost:8000/v1",
104
104
  ),
@@ -337,6 +337,7 @@ class ChainlitAgentCallbacks:
337
337
  content: str,
338
338
  is_tool: bool = False,
339
339
  cached: bool = False,
340
+ language: str | None = None,
340
341
  ) -> None:
341
342
  """Show non-streaming LLM response."""
342
343
  step = cl.Step(
@@ -344,7 +345,7 @@ class ChainlitAgentCallbacks:
344
345
  name=self._entity_name("llm", tool=is_tool, cached=cached),
345
346
  type="llm",
346
347
  parent_id=self._get_parent_id(),
347
- language="json" if is_tool else None,
348
+ language=language or ("json" if is_tool else None),
348
349
  )
349
350
  self.last_step = step
350
351
  self.curr_step = None
@@ -15,6 +15,7 @@ from langroid.agent.tool_message import ToolMessage
15
15
  from langroid.language_models.base import (
16
16
  LLMFunctionSpec,
17
17
  LLMMessage,
18
+ LLMResponse,
18
19
  Role,
19
20
  StreamingIfAllowed,
20
21
  )
@@ -681,8 +682,11 @@ class ChatAgent(Agent):
681
682
  streamer = self.callbacks.start_llm_stream()
682
683
  self.llm.config.streamer = streamer
683
684
  with ExitStack() as stack: # for conditionally using rich spinner
684
- if not self.llm.get_stream():
685
+ if not self.llm.get_stream() and not settings.quiet:
685
686
  # show rich spinner only if not streaming!
687
+ # (Why? b/c the intent of showing a spinner is to "show progress",
688
+ # and we don't need to do that when streaming, since
689
+ # streaming output already shows progress.)
686
690
  cm = status(
687
691
  "LLM responding to messages...",
688
692
  log_if_quiet=False,
@@ -702,35 +706,22 @@ class ChatAgent(Agent):
702
706
  self.callbacks.finish_llm_stream(
703
707
  content=str(response),
704
708
  is_tool=self.has_tool_message_attempt(
705
- ChatDocument.from_LLMResponse(response, displayed=True)
709
+ ChatDocument.from_LLMResponse(response, displayed=True),
706
710
  ),
707
711
  )
708
712
  self.llm.config.streamer = noop_fn
709
713
  if response.cached:
710
714
  self.callbacks.cancel_llm_stream()
711
-
712
- if not self.llm.get_stream() or response.cached:
713
- # We would have already displayed the msg "live" ONLY if
714
- # streaming was enabled, AND we did not find a cached response.
715
- # If we are here, it means the response has not yet been displayed.
716
- cached = f"[red]{self.indent}(cached)[/red]" if response.cached else ""
717
- if not settings.quiet:
718
- print(cached + "[green]" + escape(str(response)))
719
- self.callbacks.show_llm_response(
720
- content=str(response),
721
- is_tool=self.has_tool_message_attempt(
722
- ChatDocument.from_LLMResponse(response, displayed=True)
723
- ),
724
- cached=response.cached,
725
- )
715
+ self._render_llm_response(response)
726
716
  self.update_token_usage(
727
- response,
717
+ response, # .usage attrib is updated!
728
718
  messages,
729
719
  self.llm.get_stream(),
730
720
  chat=True,
731
721
  print_response_stats=self.config.show_stats and not settings.quiet,
732
722
  )
733
- return ChatDocument.from_LLMResponse(response, displayed=True)
723
+ chat_doc = ChatDocument.from_LLMResponse(response, displayed=True)
724
+ return chat_doc
734
725
 
735
726
  async def llm_response_messages_async(
736
727
  self, messages: List[LLMMessage], output_len: Optional[int] = None
@@ -764,35 +755,65 @@ class ChatAgent(Agent):
764
755
  self.callbacks.finish_llm_stream(
765
756
  content=str(response),
766
757
  is_tool=self.has_tool_message_attempt(
767
- ChatDocument.from_LLMResponse(response, displayed=True)
758
+ ChatDocument.from_LLMResponse(response, displayed=True),
768
759
  ),
769
760
  )
770
761
  self.llm.config.streamer = noop_fn
771
762
  if response.cached:
772
763
  self.callbacks.cancel_llm_stream()
773
- if not self.llm.get_stream() or response.cached:
764
+ self._render_llm_response(response)
765
+ self.update_token_usage(
766
+ response, # .usage attrib is updated!
767
+ messages,
768
+ self.llm.get_stream(),
769
+ chat=True,
770
+ print_response_stats=self.config.show_stats and not settings.quiet,
771
+ )
772
+ chat_doc = ChatDocument.from_LLMResponse(response, displayed=True)
773
+ return chat_doc
774
+
775
+ def _render_llm_response(
776
+ self, response: ChatDocument | LLMResponse, citation_only: bool = False
777
+ ) -> None:
778
+ is_cached = (
779
+ response.cached
780
+ if isinstance(response, LLMResponse)
781
+ else response.metadata.cached
782
+ )
783
+ if self.llm is None:
784
+ return
785
+ if not citation_only and (not self.llm.get_stream() or is_cached):
786
+ # We expect response to be LLMResponse in this context
787
+ if not isinstance(response, LLMResponse):
788
+ raise ValueError(
789
+ "Expected response to be LLMResponse, but got "
790
+ f"{type(response)} instead."
791
+ )
774
792
  # We would have already displayed the msg "live" ONLY if
775
793
  # streaming was enabled, AND we did not find a cached response.
776
794
  # If we are here, it means the response has not yet been displayed.
777
- cached = f"[red]{self.indent}(cached)[/red]" if response.cached else ""
795
+ cached = f"[red]{self.indent}(cached)[/red]" if is_cached else ""
778
796
  if not settings.quiet:
779
797
  print(cached + "[green]" + escape(str(response)))
780
798
  self.callbacks.show_llm_response(
781
799
  content=str(response),
782
800
  is_tool=self.has_tool_message_attempt(
783
- ChatDocument.from_LLMResponse(response, displayed=True)
801
+ ChatDocument.from_LLMResponse(response, displayed=True),
784
802
  ),
785
- cached=response.cached,
803
+ cached=is_cached,
786
804
  )
787
-
788
- self.update_token_usage(
789
- response,
790
- messages,
791
- self.llm.get_stream(),
792
- chat=True,
793
- print_response_stats=self.config.show_stats and not settings.quiet,
794
- )
795
- return ChatDocument.from_LLMResponse(response, displayed=True)
805
+ if isinstance(response, LLMResponse):
806
+ # we are in the context immediately after an LLM responded,
807
+ # we won't have citations yet, so we're done
808
+ return
809
+ if response.metadata.has_citation and not settings.quiet:
810
+ print("[grey37]SOURCES:\n" + response.metadata.source + "[/grey37]")
811
+ self.callbacks.show_llm_response(
812
+ content=str(response.metadata.source),
813
+ is_tool=False,
814
+ cached=False,
815
+ language="text",
816
+ )
796
817
 
797
818
  def _llm_response_temp_context(self, message: str, prompt: str) -> ChatDocument:
798
819
  """
@@ -53,6 +53,7 @@ class ChatDocMetaData(DocMetaData):
53
53
  usage: Optional[LLMTokenUsage]
54
54
  cached: bool = False
55
55
  displayed: bool = False
56
+ has_citation: bool = False
56
57
  status: Optional[StatusCode] = None
57
58
 
58
59
 
@@ -14,7 +14,6 @@ pip install "langroid[hf-embeddings]"
14
14
  """
15
15
 
16
16
  import logging
17
- from contextlib import ExitStack
18
17
  from functools import cache
19
18
  from typing import Any, Dict, List, Optional, Set, Tuple, no_type_check
20
19
 
@@ -31,6 +30,7 @@ from langroid.agent.special.relevance_extractor_agent import (
31
30
  RelevanceExtractorAgentConfig,
32
31
  )
33
32
  from langroid.agent.task import Task
33
+ from langroid.agent.tools.retrieval_tool import RetrievalTool
34
34
  from langroid.embedding_models.models import OpenAIEmbeddingsConfig
35
35
  from langroid.language_models.base import StreamingIfAllowed
36
36
  from langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig
@@ -49,7 +49,6 @@ from langroid.parsing.urls import get_list_from_user, get_urls_paths_bytes_indic
49
49
  from langroid.parsing.utils import batched
50
50
  from langroid.prompts.prompts_config import PromptsConfig
51
51
  from langroid.prompts.templates import SUMMARY_ANSWER_PROMPT_GPT4
52
- from langroid.utils.configuration import settings
53
52
  from langroid.utils.constants import NO_ANSWER
54
53
  from langroid.utils.output import show_if_debug, status
55
54
  from langroid.utils.pydantic_utils import dataframe_to_documents, extract_fields
@@ -83,6 +82,49 @@ except ImportError:
83
82
  pass
84
83
 
85
84
 
85
+ def extract_markdown_references(md_string: str) -> list[int]:
86
+ """
87
+ Extracts markdown references (e.g., [^1], [^2]) from a string and returns
88
+ them as a sorted list of integers.
89
+
90
+ Args:
91
+ md_string (str): The markdown string containing references.
92
+
93
+ Returns:
94
+ list[int]: A sorted list of unique integers from the markdown references.
95
+ """
96
+ import re
97
+
98
+ # Regex to find all occurrences of [^<number>]
99
+ matches = re.findall(r"\[\^(\d+)\]", md_string)
100
+ # Convert matches to integers, remove duplicates with set, and sort
101
+ return sorted(set(int(match) for match in matches))
102
+
103
+
104
+ def format_footnote_text(content: str, width: int = 80) -> str:
105
+ """
106
+ Formats the content part of a footnote (i.e. not the first line that
107
+ appears right after the reference [^4])
108
+ It wraps the text so that no line is longer than the specified width and indents
109
+ lines as necessary for markdown footnotes.
110
+
111
+ Args:
112
+ content (str): The text of the footnote to be formatted.
113
+ width (int): Maximum width of the text lines.
114
+
115
+ Returns:
116
+ str: Properly formatted markdown footnote text.
117
+ """
118
+ import textwrap
119
+
120
+ # Wrap the text to the specified width
121
+ wrapped_lines = textwrap.wrap(content, width)
122
+ if len(wrapped_lines) == 0:
123
+ return ""
124
+ indent = " " # Indentation for markdown footnotes
125
+ return indent + ("\n" + indent).join(wrapped_lines)
126
+
127
+
86
128
  class DocChatAgentConfig(ChatAgentConfig):
87
129
  system_message: str = DEFAULT_DOC_CHAT_SYSTEM_MESSAGE
88
130
  user_message: str = DEFAULT_DOC_CHAT_INSTRUCTIONS
@@ -422,6 +464,13 @@ class DocChatAgent(ChatAgent):
422
464
  self.setup_documents(docs, filter=self.config.filter)
423
465
  return len(docs)
424
466
 
467
+ def retrieval_tool(self, msg: RetrievalTool) -> str:
468
+ """Handle the RetrievalTool message"""
469
+ self.config.retrieve_only = True
470
+ self.config.parsing.n_similar_docs = msg.num_results
471
+ content_doc = self.answer_from_docs(msg.query)
472
+ return content_doc.content
473
+
425
474
  @staticmethod
426
475
  def document_compatible_dataframe(
427
476
  df: pd.DataFrame,
@@ -658,8 +707,7 @@ class DocChatAgent(ChatAgent):
658
707
  query_str = query_str[1:] if query_str is not None else None
659
708
  if self.llm is None:
660
709
  raise ValueError("LLM not set")
661
- with StreamingIfAllowed(self.llm, self.llm.get_stream()):
662
- response = super().llm_response(query_str)
710
+ response = super().llm_response(query_str)
663
711
  if query_str is not None:
664
712
  self.update_dialog(
665
713
  query_str, "" if response is None else response.content
@@ -676,6 +724,10 @@ class DocChatAgent(ChatAgent):
676
724
  else:
677
725
  self.callbacks.show_start_response(entity="llm")
678
726
  response = self.answer_from_docs(query_str)
727
+ # Citation details (if any) are NOT generated by LLM
728
+ # (We extract these from LLM's numerical citations),
729
+ # so render them here
730
+ self._render_llm_response(response, citation_only=True)
679
731
  return ChatDocument(
680
732
  content=response.content,
681
733
  metadata=ChatDocMetaData(
@@ -701,8 +753,7 @@ class DocChatAgent(ChatAgent):
701
753
  query_str = query_str[1:] if query_str is not None else None
702
754
  if self.llm is None:
703
755
  raise ValueError("LLM not set")
704
- with StreamingIfAllowed(self.llm, self.llm.get_stream()):
705
- response = await super().llm_response_async(query_str)
756
+ response = await super().llm_response_async(query_str)
706
757
  if query_str is not None:
707
758
  self.update_dialog(
708
759
  query_str, "" if response is None else response.content
@@ -719,6 +770,7 @@ class DocChatAgent(ChatAgent):
719
770
  else:
720
771
  self.callbacks.show_start_response(entity="llm")
721
772
  response = self.answer_from_docs(query_str)
773
+ self._render_llm_response(response, citation_only=True)
722
774
  return ChatDocument(
723
775
  content=response.content,
724
776
  metadata=ChatDocMetaData(
@@ -742,10 +794,11 @@ class DocChatAgent(ChatAgent):
742
794
  return "\n".join(
743
795
  [
744
796
  f"""
797
+ [{i+1}]
745
798
  {content}
746
799
  {source}
747
800
  """
748
- for (content, source) in zip(contents, sources)
801
+ for i, (content, source) in enumerate(zip(contents, sources))
749
802
  ]
750
803
  )
751
804
 
@@ -769,7 +822,7 @@ class DocChatAgent(ChatAgent):
769
822
  # Substitute Q and P into the templatized prompt
770
823
 
771
824
  final_prompt = self.config.summarize_prompt.format(
772
- question=f"Question:{question}", extracts=passages_str
825
+ question=question, extracts=passages_str
773
826
  )
774
827
  show_if_debug(final_prompt, "SUMMARIZE_PROMPT= ")
775
828
 
@@ -788,24 +841,25 @@ class DocChatAgent(ChatAgent):
788
841
  final_answer = answer_doc.content.strip()
789
842
  show_if_debug(final_answer, "SUMMARIZE_RESPONSE= ")
790
843
 
791
- if final_answer.startswith("SOURCE"):
792
- # sometimes SOURCE may be shown first,
793
- # in this case just use final_answer as-is for both content and source
794
- content = final_answer
795
- sources = final_answer
796
- else:
797
- parts = final_answer.split("SOURCE:", maxsplit=1)
798
- if len(parts) > 1:
799
- content = parts[0].strip()
800
- sources = parts[1].strip()
801
- else:
802
- content = final_answer
803
- sources = ""
844
+ citations = extract_markdown_references(final_answer)
845
+
846
+ citations_str = ""
847
+ if len(citations) > 0:
848
+ # append [i] source, content for each citation
849
+ citations_str = "\n".join(
850
+ [
851
+ f"[^{c}] {passages[c-1].metadata.source}"
852
+ f"\n{format_footnote_text(passages[c-1].content)}"
853
+ for c in citations
854
+ ]
855
+ )
856
+
804
857
  return ChatDocument(
805
- content=content,
858
+ content=final_answer, # does not contain citations
806
859
  metadata=ChatDocMetaData(
807
- source="SOURCE: " + sources,
860
+ source=citations_str, # only the citations
808
861
  sender=Entity.LLM,
862
+ has_citation=len(citations) > 0,
809
863
  cached=getattr(answer_doc.metadata, "cached", False),
810
864
  ),
811
865
  )
@@ -1277,15 +1331,7 @@ class DocChatAgent(ChatAgent):
1277
1331
  content="\n\n".join([e.content for e in extracts]),
1278
1332
  metadata=ChatDocMetaData(**meta),
1279
1333
  )
1280
- with ExitStack() as stack:
1281
- # conditionally use Streaming or rich console context
1282
- cm = (
1283
- StreamingIfAllowed(self.llm)
1284
- if settings.stream
1285
- else (status("LLM Generating final answer..."))
1286
- )
1287
- stack.enter_context(cm) # type: ignore
1288
- response = self.get_summary_answer(query, extracts)
1334
+ response = self.get_summary_answer(query, extracts)
1289
1335
 
1290
1336
  self.update_dialog(query, response.content)
1291
1337
  self.response = response # save last response
@@ -58,7 +58,7 @@ class LanceQueryPlanAgentConfig(ChatAgentConfig):
58
58
  - a possibly REPHRASED QUERY to be answerable given the FILTER.
59
59
  Keep in mind that the ASSISTANT does NOT know anything about the FILTER fields,
60
60
  so the REPHRASED QUERY should NOT mention ANY FILTER fields.
61
- The answer will answer based on documents whose CONTENTS match the QUERY,
61
+ The assistant will answer based on documents whose CONTENTS match the QUERY,
62
62
  possibly REPHRASED.
63
63
  - an OPTIONAL SINGLE-LINE Pandas-dataframe calculation/aggregation string
64
64
  that can be used to calculate the answer to the original query,
@@ -70,6 +70,10 @@ class LanceQueryPlanAgentConfig(ChatAgentConfig):
70
70
 
71
71
  IMPORTANT: The DataFrame `df` in this calculation is the result of
72
72
  applying the FILTER AND REPHRASED QUERY to the documents.
73
+
74
+ WATCH OUT!! When deciding the dataframe calc, if any, CAREFULLY
75
+ note what the query is asking, and ensure that the result of your
76
+ dataframe calc expression would answer the query.
73
77
 
74
78
 
75
79
  EXAMPLE:
@@ -25,7 +25,7 @@ from langroid.agent.tool_message import ToolMessage
25
25
  from langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig
26
26
  from langroid.parsing.table_loader import read_tabular_data
27
27
  from langroid.prompts.prompts_config import PromptsConfig
28
- from langroid.utils.constants import DONE
28
+ from langroid.utils.constants import DONE, PASS
29
29
  from langroid.vector_store.base import VectorStoreConfig
30
30
 
31
31
  logger = logging.getLogger(__name__)
@@ -52,7 +52,8 @@ in your code, and try another way, or use `pandas_eval` to explore the dataframe
52
52
  before submitting your final code.
53
53
 
54
54
  Once you have the answer to the question, possibly after a few steps,
55
- say {DONE} and show me the answer. If you receive an error message,
55
+ say {DONE} and PRESENT THE ANSWER TO ME; do not just say {DONE}.
56
+ If you receive an error message,
56
57
  try using the `pandas_eval` tool/function again with the corrected code.
57
58
 
58
59
  VERY IMPORTANT: When using the `pandas_eval` tool/function, DO NOT EXPLAIN ANYTHING,
@@ -238,12 +239,22 @@ class TableChatAgent(ChatAgent):
238
239
  def handle_message_fallback(
239
240
  self, msg: str | ChatDocument
240
241
  ) -> str | ChatDocument | None:
241
- """Handle scenario where LLM forgets to say DONE or
242
- forgets to use pandas_eval"""
242
+ """Handle various LLM deviations"""
243
243
  if isinstance(msg, ChatDocument) and msg.metadata.sender == lr.Entity.LLM:
244
+ if msg.content.strip() == DONE and self.sent_expression:
245
+ # LLM sent an expression (i.e. used the `pandas_eval` tool)
246
+ # but upon receiving the results, simply said DONE without
247
+ # narrating the result as instructed.
248
+ return """
249
+ You forgot to PRESENT the answer to the user's query
250
+ based on the results from `pandas_eval` tool.
251
+ """
244
252
  if self.sent_expression:
245
- return DONE
253
+ # LLM forgot to say DONE
254
+ self.sent_expression = False
255
+ return DONE + " " + PASS
246
256
  else:
257
+ # LLM forgot to use the `pandas_eval` tool
247
258
  return """
248
259
  You forgot to use the `pandas_eval` tool/function
249
260
  to find the answer.
@@ -0,0 +1,29 @@
1
+ from typing import List
2
+
3
+ from langroid.agent.tool_message import ToolMessage
4
+
5
+
6
+ class RetrievalTool(ToolMessage):
7
+ """Retrieval tool, only to be used by a DocChatAgent."""
8
+
9
+ request: str = "retrieval_tool"
10
+ purpose: str = """
11
+ To retrieve up to <num_results> passages from a document-set, that are
12
+ relevant to a <query>, which could be a question or simply a topic or
13
+ search phrase.
14
+ """
15
+ query: str
16
+ num_results: int
17
+
18
+ @classmethod
19
+ def examples(cls) -> List["ToolMessage"]:
20
+ return [
21
+ cls(
22
+ query="What are the eligibility criteria for the scholarship?",
23
+ num_results=3,
24
+ ),
25
+ cls(
26
+ query="Self-Attention mechanism in RNNs",
27
+ num_results=5,
28
+ ),
29
+ ]
@@ -475,10 +475,12 @@ class LanguageModel(ABC):
475
475
  history = collate_chat_history(chat_history)
476
476
 
477
477
  prompt = f"""
478
- Given the conversationn below, and a follow-up question, rephrase the follow-up
479
- question as a standalone question.
478
+ Given the CHAT HISTORY below, and a follow-up QUESTION or SEARCH PHRASE,
479
+ rephrase the follow-up question/phrase as a STANDALONE QUESTION that
480
+ can be understood without the context of the chat history.
480
481
 
481
482
  Chat history: {history}
483
+
482
484
  Follow-up question: {question}
483
485
  """.strip()
484
486
  show_if_debug(prompt, "FOLLOWUP->STANDALONE-PROMPT= ")
@@ -582,7 +582,7 @@ class OpenAIGPT(LanguageModel):
582
582
 
583
583
  def get_stream(self) -> bool:
584
584
  """Get streaming status"""
585
- return self.config.stream
585
+ return self.config.stream and settings.stream
586
586
 
587
587
  @no_type_check
588
588
  def _process_stream_event(
@@ -50,26 +50,28 @@ EXTRACTION_PROMPT = f"""
50
50
 
51
51
  SUMMARY_ANSWER_PROMPT_GPT4 = f"""
52
52
 
53
- Use the provided extracts (with sources) to answer the question.
53
+ Use the provided NUMBERED extracts (with sources) to answer the QUESTION.
54
54
  If there's not enough information, respond with {NO_ANSWER}. Use only the
55
55
  information in these extracts, even if your answer is factually incorrect,
56
56
  and even if the answer contradicts other parts of the document. The only
57
57
  important thing is that your answer is consistent with and supported by the
58
- extracts. Compose your complete answer and cite all supporting sources on a
59
- separate separate line as follows. For each SOURCE, (a) show the source name,
60
- and (b) the exact EXTRACT from the source that supports your answer.
61
- DO NOT SHOW THE FULL EXTRACT; only show the FIRST 3 words and LAST 3 words.
62
- For example:
63
- SOURCE: https://en.wikipedia.org/wiki/Tree
64
- EXTRACTS: "The tree species ... oak, maple, and birch."
58
+ extracts. Compose your complete answer, inserting CITATIONS in MARKDOWN format
59
+ [^i][^j] where i,j,... are the extract NUMBERS you are
60
+ citing.
61
+ For example your answer might look like this (NOTE HOW multiple citations
62
+ are grouped as [^2][^5]):
63
+
64
+ Beethoven composed the 9th symphony in 1824.[^1] After that he became deaf
65
+ and could not hear his own music. [^2][^5]. He was a prolific composer and
66
+ wrote many famous pieces.
65
67
 
66
- SOURCE: https://en.wikipedia.org/wiki/Oak
67
- EXTRACTS: "The oak trees ... longevity and strength."
68
+ NUMBERED EXTRACTS:
68
69
 
69
70
  {{extracts}}
70
71
 
72
+ QUESTION:
71
73
  {{question}}
72
- Answer:
74
+
73
75
  """.strip()
74
76
 
75
77
  ANSWER_PROMPT_USE_HISTORY_GPT4 = f"""
@@ -3,6 +3,7 @@ from contextlib import AbstractContextManager, ExitStack
3
3
  from typing import Any
4
4
 
5
5
  from rich.console import Console
6
+ from rich.errors import LiveError
6
7
 
7
8
  from langroid.utils.configuration import quiet_mode, settings
8
9
 
@@ -19,15 +20,21 @@ def status(
19
20
  Displays a rich spinner if not in quiet mode, else optionally logs the message.
20
21
  """
21
22
  stack = ExitStack()
22
-
23
- if settings.quiet:
24
- if log_if_quiet:
25
- logger.info(msg)
23
+ logged = False
26
24
  if settings.quiet and log_if_quiet:
25
+ logged = True
27
26
  logger.info(msg)
28
- else:
29
- stack.enter_context(console.status(msg))
30
27
 
28
+ if not settings.quiet:
29
+ try:
30
+ stack.enter_context(console.status(msg))
31
+ except LiveError:
32
+ if not logged:
33
+ logger.info(msg)
34
+
35
+ # When using rich spinner, we enforce quiet mode
36
+ # (since output will be messy otherwise);
37
+ # We make an exception to this when debug is enabled.
31
38
  stack.enter_context(quiet_mode(not settings.debug))
32
39
 
33
40
  return stack
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.1.248
3
+ Version: 0.1.250
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -237,6 +237,8 @@ teacher_task.run()
237
237
  <summary> <b>Click to expand</b></summary>
238
238
 
239
239
  - **May 2024:**
240
+ - [Much-Improved Citation](https://github.com/langroid/langroid/issues/477)
241
+ generation and display when using `DocChatAgent`.
240
242
  - `gpt-4o` is now the default LLM throughout; Update tests and examples to work
241
243
  with this LLM; use tokenizer corresponding to the LLM.
242
244
  - `gemini 1.5 pro` support via `litellm`
@@ -1,21 +1,21 @@
1
1
  langroid/__init__.py,sha256=zsYpGiAUsvyzZzjm964NUamsJImrXSJPVGz9a2jE_uY,1679
2
2
  langroid/agent/__init__.py,sha256=_D8dxnfdr92ch1CIrUkKjrB5HVvsQdn62b1Fb2kBxV8,785
3
- langroid/agent/base.py,sha256=Aa8e_dHibXfpOXnldGCrVvObFKhO0P05Is97AuVGFLo,35569
3
+ langroid/agent/base.py,sha256=5HQ9fAFTQL771rhx7UkVkL90bjxSVA1DjtvxV8_1RJA,35652
4
4
  langroid/agent/batch.py,sha256=feRA_yRG768ElOQjrKEefcRv6Aefd_yY7qktuYUQDwc,10040
5
5
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- langroid/agent/callbacks/chainlit.py,sha256=uIfdmoOKIfzBVeM2ce0V5SCfHjcvYyaz13dfjFXCEN4,21020
7
- langroid/agent/chat_agent.py,sha256=X5uVMm9qdw3j-FRf4hbN8k8ByaSdtQCTuU8olKE0sbs,38750
8
- langroid/agent/chat_document.py,sha256=1ug58JXV3PEGjzRU9vQPjdZbRtza73ZFZksNwWBPziQ,9285
6
+ langroid/agent/callbacks/chainlit.py,sha256=LboE3zlLLzClKxpBkzHX4XU6fW4lNZW97zwwN97uuaU,21067
7
+ langroid/agent/chat_agent.py,sha256=YwlRMWoK_7vEl9pxgMeUjQAgsukrnZjDGJR0WVomSuQ,39592
8
+ langroid/agent/chat_document.py,sha256=uwCq53SHRyxQw6qyhjzPYuJG48VHBgOf2122Ew3fk6c,9316
9
9
  langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
11
11
  langroid/agent/openai_assistant.py,sha256=kIVDI4r-xGvplLU5s0nShPVHs6Jq-wOsfWE0kcMhAdQ,33056
12
12
  langroid/agent/special/__init__.py,sha256=NG0JkB5y4K0bgnd9Q9UIvFExun3uTfVOWEVLVymff1M,1207
13
- langroid/agent/special/doc_chat_agent.py,sha256=LwWNb_1s5n9rOk9OpOFPuuY1VnVX5DjzQmPwBanKRrM,53763
13
+ langroid/agent/special/doc_chat_agent.py,sha256=MTUrUyCZ7_wksTo11AwSTHMOKZf1WX-cAJowi_sfT2o,55320
14
14
  langroid/agent/special/lance_doc_chat_agent.py,sha256=USp0U3eTaJzwF_3bdqE7CedSLbaqAi2tm-VzygcyLaA,10175
15
15
  langroid/agent/special/lance_rag/__init__.py,sha256=QTbs0IVE2ZgDg8JJy1zN97rUUg4uEPH7SLGctFNumk4,174
16
16
  langroid/agent/special/lance_rag/critic_agent.py,sha256=OsOcpcU_AmU2MagpZ5X5yxFeXyteKN9QJMzJGqIITig,6871
17
17
  langroid/agent/special/lance_rag/lance_rag_task.py,sha256=l_HQgrYY-CX2FwIsS961aEF3bYog3GDYo98fj0C0mSk,2889
18
- langroid/agent/special/lance_rag/query_planner_agent.py,sha256=M2ry4PL_UkAzDNFvdl_Kqb6YtOnWqqA9Qz361AAb_b4,9584
18
+ langroid/agent/special/lance_rag/query_planner_agent.py,sha256=U_2V8l3M44R3mX-El3wG1k-u2lTToU-HGfKvRkEWoEA,9816
19
19
  langroid/agent/special/lance_tools.py,sha256=btMwKdcT8RdwAjmzbtN1xxm3s1H7ipO9GSpUamryYx8,1456
20
20
  langroid/agent/special/neo4j/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  langroid/agent/special/neo4j/csv_kg_chat.py,sha256=koL3sKtHm3aRkLTiARs54ngrcU3lOR1WaLLc_i8rWOU,6374
@@ -31,7 +31,7 @@ langroid/agent/special/sql/utils/description_extractors.py,sha256=RZ2R3DmASxB1ij
31
31
  langroid/agent/special/sql/utils/populate_metadata.py,sha256=x2OMKfmIBnJESBG3qKt6gvr3H3L4ZQcoxHfNdWfHjZs,2987
32
32
  langroid/agent/special/sql/utils/system_message.py,sha256=qKLHkvQWRQodTtPLPxr1GSLUYUFASZU8x-ybV67cB68,1885
33
33
  langroid/agent/special/sql/utils/tools.py,sha256=6uB2424SLtmapui9ggcEr0ZTiB6_dL1-JRGgN8RK9Js,1332
34
- langroid/agent/special/table_chat_agent.py,sha256=v8gRYj5frhYgfCSzmwL1DMmvyC5Qb5z_Ira_fwld5vk,9024
34
+ langroid/agent/special/table_chat_agent.py,sha256=xz4nWndTYTykET_oaveHcZUQ8IEpmA5yB8QGTXfOifw,9624
35
35
  langroid/agent/task.py,sha256=b_d46txohISETxXJoWpmIX0hinvt1wjHbK08LZRBEz8,54020
36
36
  langroid/agent/tool_message.py,sha256=2kPsQUwi3ZzINTUNj10huKnZLjLp5SXmefacTHx8QDc,8304
37
37
  langroid/agent/tools/__init__.py,sha256=q-maq3k2BXhPAU99G0H6-j_ozoRvx15I1RFpPVicQIU,304
@@ -41,6 +41,7 @@ langroid/agent/tools/generator_tool.py,sha256=y0fB0ZObjA0b3L0uSTtrqRCKHDUR95arBf
41
41
  langroid/agent/tools/google_search_tool.py,sha256=cQxcNtb8XCNpOo_yCeYRwG_y-OATjPgkbr01kea9qWE,1421
42
42
  langroid/agent/tools/metaphor_search_tool.py,sha256=NKHss-AkI942_XhfMgUctAwHjIHpqp5NfYIebKV4UcE,2454
43
43
  langroid/agent/tools/recipient_tool.py,sha256=61vdKv06qgVdtnE3gxjzV8RvUEy8JhbC9eWa0J0BPdw,9171
44
+ langroid/agent/tools/retrieval_tool.py,sha256=6uvRNg-kG_ItPa3sF9NWkthQ5frHn8bkB1Z3GSd3Oas,836
44
45
  langroid/agent/tools/run_python_code.py,sha256=V3mHdHQYn0M0PAtyoHxjNvk6KvWWcQ4ugo0TOKc8HyI,1752
45
46
  langroid/agent/tools/segment_extract_tool.py,sha256=W39poS7Av2EuJ34tGKhLhzgj3zEyZnBplpSt2goRAp4,1285
46
47
  langroid/agent_config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -60,10 +61,10 @@ langroid/embedding_models/protoc/embeddings_pb2_grpc.py,sha256=9dYQqkW3JPyBpSEje
60
61
  langroid/embedding_models/remote_embeds.py,sha256=6_kjXByVbqhY9cGwl9R83ZcYC2km-nGieNNAo1McHaY,5151
61
62
  langroid/language_models/__init__.py,sha256=5L9ndEEC8iLJHjDJmYFTnv6-2-3xsxWUMHcugR8IeDs,821
62
63
  langroid/language_models/azure_openai.py,sha256=ncRCbKooqLVOY-PWQUIo9C3yTuKEFbAwyngXT_M4P7k,5989
63
- langroid/language_models/base.py,sha256=B6dX43ZR65mIvjD95W4RcfpT-WpmiuEcstR3eMrr56Y,21029
64
+ langroid/language_models/base.py,sha256=2JhacnbQ-DJzLbOaJqyZPnl867xyiz_W-ODiAlEcp98,21131
64
65
  langroid/language_models/config.py,sha256=5UF3DzO1a-Dfsc3vghE0XGq7g9t_xDsRCsuRiU4dgBg,366
65
66
  langroid/language_models/openai_assistants.py,sha256=9K-DEAL2aSWHeXj2hwCo2RAlK9_1oCPtqX2u1wISCj8,36
66
- langroid/language_models/openai_gpt.py,sha256=Re7n8Vtb-03HktyxewCD9-gcgXyuN5tWjyW_TgJwees,50648
67
+ langroid/language_models/openai_gpt.py,sha256=5O-roChpUDZ6OmRTOkR9e2AHHZL8w12Yzs9Q_xviDZ0,50668
67
68
  langroid/language_models/prompt_formatter/__init__.py,sha256=9JXFF22QNMmbQV1q4nrIeQVTtA3Tx8tEZABLtLBdFyc,352
68
69
  langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
69
70
  langroid/language_models/prompt_formatter/hf_formatter.py,sha256=TFL6ppmeQWnzr6CKQzRZFYY810zE1mr8DZnhw6i85ok,5217
@@ -93,7 +94,7 @@ langroid/prompts/__init__.py,sha256=B0vpJzIJlMR3mFRtoQwyALsFzBHvLp9f92acD8xJA_0,
93
94
  langroid/prompts/chat-gpt4-system-prompt.md,sha256=Q3uLCJTPQvmUkZN2XDnkBC7M2K3X0F3C3GIQBaFvYvw,5329
94
95
  langroid/prompts/dialog.py,sha256=SpfiSyofSgy2pwD1YboHR_yHO3LEEMbv6j2sm874jKo,331
95
96
  langroid/prompts/prompts_config.py,sha256=XRQHzod7KBnoKn3B_V878jZiqBA7rcn-CtGPkuAe_yM,131
96
- langroid/prompts/templates.py,sha256=4X-07tnmUQ8Z_zaWRQAUUyKiErGztp3tERujqnG8sGA,6369
97
+ langroid/prompts/templates.py,sha256=kz0rPiM6iLGhhpDonF3Y87OznSe9FRI6A0pHU0wgW4Q,6314
97
98
  langroid/prompts/transforms.py,sha256=GsQo1klGxUy0fACh6j0lTblk6XEl2erRnhRWlN2M4-c,2706
98
99
  langroid/utils/__init__.py,sha256=ARx5To4Hsv1K5QAzK4uUqdEoB_iq5HK797vae1AcMBI,300
99
100
  langroid/utils/algorithms/__init__.py,sha256=WylYoZymA0fnzpB4vrsH_0n7WsoLhmuZq8qxsOCjUpM,41
@@ -107,7 +108,7 @@ langroid/utils/llms/strings.py,sha256=CSAX9Z6FQOLXOzbLMe_Opqtc3ruDAKTTk7cPqc6Blh
107
108
  langroid/utils/logging.py,sha256=R8TN-FqVpwZ4Ajgls9TDMthLvPpQd0QVNXK-PJDj1Z8,3917
108
109
  langroid/utils/output/__init__.py,sha256=4X8Hdo1SEm06NUnggMJrLtW8i1owdDQPrS7J08BaTec,341
109
110
  langroid/utils/output/printing.py,sha256=5EsYB1O4qKhocW19aebOUzK82RD9U5nygbY21yo8gfg,2872
110
- langroid/utils/output/status.py,sha256=VoSXmWDuddo1ipCzDAA6qlgffr5E4lSmBD0rIdNxxcs,774
111
+ langroid/utils/output/status.py,sha256=rzbE7mDJcgNNvdtylCseQcPGCGghtJvVq3lB-OPJ49E,1049
111
112
  langroid/utils/pandas_utils.py,sha256=UctS986Jtl_MvU5rA7-GfrjEHXP7MNu8ePhepv0bTn0,755
112
113
  langroid/utils/pydantic_utils.py,sha256=yb-ghaQYL7EIYeiZ0tailvZvAuJZNF7UBXkd3z35OYc,21728
113
114
  langroid/utils/system.py,sha256=tWoEbzHzJ6ywdsoa9EwsQrZfGk2t7q87_zKNwau2C8s,4546
@@ -121,7 +122,7 @@ langroid/vector_store/meilisearch.py,sha256=d2huA9P-NoYRuAQ9ZeXJmMKr7ry8u90RUSR2
121
122
  langroid/vector_store/momento.py,sha256=9cui31TTrILid2KIzUpBkN2Ey3g_CZWOQVdaFsA4Ors,10045
122
123
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
123
124
  langroid/vector_store/qdrantdb.py,sha256=sk5Qb2ZNbooi0rorsMuqIMokF7WADw6PJ0D6goM2XBw,16802
124
- langroid-0.1.248.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
125
- langroid-0.1.248.dist-info/METADATA,sha256=RkEwgd3nRozDym_Ey1aguztAAgRpTrzh6E4uOeHUDdo,49426
126
- langroid-0.1.248.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
127
- langroid-0.1.248.dist-info/RECORD,,
125
+ langroid-0.1.250.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
126
+ langroid-0.1.250.dist-info/METADATA,sha256=FOv0qbbaZxR3TJWncDj2NvvslRp7RXOI0Xahb4yBT3I,49559
127
+ langroid-0.1.250.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
128
+ langroid-0.1.250.dist-info/RECORD,,