langroid 0.33.6__py3-none-any.whl → 0.33.8__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/__init__.py +106 -0
- langroid/agent/__init__.py +41 -0
- langroid/agent/base.py +1983 -0
- langroid/agent/batch.py +398 -0
- langroid/agent/callbacks/__init__.py +0 -0
- langroid/agent/callbacks/chainlit.py +598 -0
- langroid/agent/chat_agent.py +1899 -0
- langroid/agent/chat_document.py +454 -0
- langroid/agent/openai_assistant.py +882 -0
- langroid/agent/special/__init__.py +59 -0
- langroid/agent/special/arangodb/__init__.py +0 -0
- langroid/agent/special/arangodb/arangodb_agent.py +656 -0
- langroid/agent/special/arangodb/system_messages.py +186 -0
- langroid/agent/special/arangodb/tools.py +107 -0
- langroid/agent/special/arangodb/utils.py +36 -0
- langroid/agent/special/doc_chat_agent.py +1466 -0
- langroid/agent/special/lance_doc_chat_agent.py +262 -0
- langroid/agent/special/lance_rag/__init__.py +9 -0
- langroid/agent/special/lance_rag/critic_agent.py +198 -0
- langroid/agent/special/lance_rag/lance_rag_task.py +82 -0
- langroid/agent/special/lance_rag/query_planner_agent.py +260 -0
- langroid/agent/special/lance_tools.py +61 -0
- langroid/agent/special/neo4j/__init__.py +0 -0
- langroid/agent/special/neo4j/csv_kg_chat.py +174 -0
- langroid/agent/special/neo4j/neo4j_chat_agent.py +433 -0
- langroid/agent/special/neo4j/system_messages.py +120 -0
- langroid/agent/special/neo4j/tools.py +32 -0
- langroid/agent/special/relevance_extractor_agent.py +127 -0
- langroid/agent/special/retriever_agent.py +56 -0
- langroid/agent/special/sql/__init__.py +17 -0
- langroid/agent/special/sql/sql_chat_agent.py +654 -0
- langroid/agent/special/sql/utils/__init__.py +21 -0
- langroid/agent/special/sql/utils/description_extractors.py +190 -0
- langroid/agent/special/sql/utils/populate_metadata.py +85 -0
- langroid/agent/special/sql/utils/system_message.py +35 -0
- langroid/agent/special/sql/utils/tools.py +64 -0
- langroid/agent/special/table_chat_agent.py +263 -0
- langroid/agent/task.py +2099 -0
- langroid/agent/tool_message.py +393 -0
- langroid/agent/tools/__init__.py +38 -0
- langroid/agent/tools/duckduckgo_search_tool.py +50 -0
- langroid/agent/tools/file_tools.py +234 -0
- langroid/agent/tools/google_search_tool.py +39 -0
- langroid/agent/tools/metaphor_search_tool.py +68 -0
- langroid/agent/tools/orchestration.py +303 -0
- langroid/agent/tools/recipient_tool.py +235 -0
- langroid/agent/tools/retrieval_tool.py +32 -0
- langroid/agent/tools/rewind_tool.py +137 -0
- langroid/agent/tools/segment_extract_tool.py +41 -0
- langroid/agent/xml_tool_message.py +382 -0
- langroid/cachedb/__init__.py +17 -0
- langroid/cachedb/base.py +58 -0
- langroid/cachedb/momento_cachedb.py +108 -0
- langroid/cachedb/redis_cachedb.py +153 -0
- langroid/embedding_models/__init__.py +39 -0
- langroid/embedding_models/base.py +74 -0
- langroid/embedding_models/models.py +461 -0
- langroid/embedding_models/protoc/__init__.py +0 -0
- langroid/embedding_models/protoc/embeddings.proto +19 -0
- langroid/embedding_models/protoc/embeddings_pb2.py +33 -0
- langroid/embedding_models/protoc/embeddings_pb2.pyi +50 -0
- langroid/embedding_models/protoc/embeddings_pb2_grpc.py +79 -0
- langroid/embedding_models/remote_embeds.py +153 -0
- langroid/exceptions.py +71 -0
- langroid/language_models/__init__.py +53 -0
- langroid/language_models/azure_openai.py +153 -0
- langroid/language_models/base.py +678 -0
- langroid/language_models/config.py +18 -0
- langroid/language_models/mock_lm.py +124 -0
- langroid/language_models/openai_gpt.py +1964 -0
- langroid/language_models/prompt_formatter/__init__.py +16 -0
- langroid/language_models/prompt_formatter/base.py +40 -0
- langroid/language_models/prompt_formatter/hf_formatter.py +132 -0
- langroid/language_models/prompt_formatter/llama2_formatter.py +75 -0
- langroid/language_models/utils.py +151 -0
- langroid/mytypes.py +84 -0
- langroid/parsing/__init__.py +52 -0
- langroid/parsing/agent_chats.py +38 -0
- langroid/parsing/code_parser.py +121 -0
- langroid/parsing/document_parser.py +718 -0
- langroid/parsing/para_sentence_split.py +62 -0
- langroid/parsing/parse_json.py +155 -0
- langroid/parsing/parser.py +313 -0
- langroid/parsing/repo_loader.py +790 -0
- langroid/parsing/routing.py +36 -0
- langroid/parsing/search.py +275 -0
- langroid/parsing/spider.py +102 -0
- langroid/parsing/table_loader.py +94 -0
- langroid/parsing/url_loader.py +115 -0
- langroid/parsing/urls.py +273 -0
- langroid/parsing/utils.py +373 -0
- langroid/parsing/web_search.py +156 -0
- langroid/prompts/__init__.py +9 -0
- langroid/prompts/dialog.py +17 -0
- langroid/prompts/prompts_config.py +5 -0
- langroid/prompts/templates.py +141 -0
- langroid/pydantic_v1/__init__.py +10 -0
- langroid/pydantic_v1/main.py +4 -0
- langroid/utils/__init__.py +19 -0
- langroid/utils/algorithms/__init__.py +3 -0
- langroid/utils/algorithms/graph.py +103 -0
- langroid/utils/configuration.py +98 -0
- langroid/utils/constants.py +30 -0
- langroid/utils/git_utils.py +252 -0
- langroid/utils/globals.py +49 -0
- langroid/utils/logging.py +135 -0
- langroid/utils/object_registry.py +66 -0
- langroid/utils/output/__init__.py +20 -0
- langroid/utils/output/citations.py +41 -0
- langroid/utils/output/printing.py +99 -0
- langroid/utils/output/status.py +40 -0
- langroid/utils/pandas_utils.py +30 -0
- langroid/utils/pydantic_utils.py +602 -0
- langroid/utils/system.py +286 -0
- langroid/utils/types.py +93 -0
- langroid/vector_store/__init__.py +50 -0
- langroid/vector_store/base.py +359 -0
- langroid/vector_store/chromadb.py +214 -0
- langroid/vector_store/lancedb.py +406 -0
- langroid/vector_store/meilisearch.py +299 -0
- langroid/vector_store/momento.py +278 -0
- langroid/vector_store/qdrantdb.py +468 -0
- {langroid-0.33.6.dist-info → langroid-0.33.8.dist-info}/METADATA +95 -94
- langroid-0.33.8.dist-info/RECORD +127 -0
- {langroid-0.33.6.dist-info → langroid-0.33.8.dist-info}/WHEEL +1 -1
- langroid-0.33.6.dist-info/RECORD +0 -7
- langroid-0.33.6.dist-info/entry_points.txt +0 -4
- pyproject.toml +0 -356
- {langroid-0.33.6.dist-info → langroid-0.33.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,156 @@
|
|
1
|
+
"""
|
2
|
+
Utilities for web search.
|
3
|
+
|
4
|
+
NOTE: Using Google Search requires setting the GOOGLE_API_KEY and GOOGLE_CSE_ID
|
5
|
+
environment variables in your `.env` file, as explained in the
|
6
|
+
[README](https://github.com/langroid/langroid#gear-installation-and-setup).
|
7
|
+
"""
|
8
|
+
|
9
|
+
import os
|
10
|
+
from typing import Dict, List
|
11
|
+
|
12
|
+
import requests
|
13
|
+
from bs4 import BeautifulSoup
|
14
|
+
from dotenv import load_dotenv
|
15
|
+
from duckduckgo_search import DDGS
|
16
|
+
from googleapiclient.discovery import Resource, build
|
17
|
+
from requests.models import Response
|
18
|
+
|
19
|
+
|
20
|
+
class WebSearchResult:
|
21
|
+
"""
|
22
|
+
Class representing a Web Search result, containing the title, link,
|
23
|
+
summary and full content of the result.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
title: str,
|
29
|
+
link: str,
|
30
|
+
max_content_length: int = 3500,
|
31
|
+
max_summary_length: int = 300,
|
32
|
+
):
|
33
|
+
"""
|
34
|
+
Args:
|
35
|
+
title (str): The title of the search result.
|
36
|
+
link (str): The link to the search result.
|
37
|
+
max_content_length (int): The maximum length of the full content.
|
38
|
+
max_summary_length (int): The maximum length of the summary.
|
39
|
+
"""
|
40
|
+
self.title = title
|
41
|
+
self.link = link
|
42
|
+
self.max_content_length = max_content_length
|
43
|
+
self.max_summary_length = max_summary_length
|
44
|
+
self.full_content = self.get_full_content()
|
45
|
+
self.summary = self.get_summary()
|
46
|
+
|
47
|
+
def get_summary(self) -> str:
|
48
|
+
return self.full_content[: self.max_summary_length]
|
49
|
+
|
50
|
+
def get_full_content(self) -> str:
|
51
|
+
try:
|
52
|
+
response: Response = requests.get(self.link)
|
53
|
+
soup: BeautifulSoup = BeautifulSoup(response.text, "lxml")
|
54
|
+
text = " ".join(soup.stripped_strings)
|
55
|
+
return text[: self.max_content_length]
|
56
|
+
except Exception as e:
|
57
|
+
return f"Error fetching content from {self.link}: {e}"
|
58
|
+
|
59
|
+
def __str__(self) -> str:
|
60
|
+
return f"Title: {self.title}\nLink: {self.link}\nSummary: {self.summary}"
|
61
|
+
|
62
|
+
def to_dict(self) -> Dict[str, str]:
|
63
|
+
return {
|
64
|
+
"title": self.title,
|
65
|
+
"link": self.link,
|
66
|
+
"summary": self.summary,
|
67
|
+
"full_content": self.full_content,
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
def google_search(query: str, num_results: int = 5) -> List[WebSearchResult]:
|
72
|
+
load_dotenv()
|
73
|
+
api_key = os.getenv("GOOGLE_API_KEY")
|
74
|
+
cse_id = os.getenv("GOOGLE_CSE_ID")
|
75
|
+
service: Resource = build("customsearch", "v1", developerKey=api_key)
|
76
|
+
raw_results = (
|
77
|
+
service.cse().list(q=query, cx=cse_id, num=num_results).execute()["items"]
|
78
|
+
)
|
79
|
+
|
80
|
+
return [
|
81
|
+
WebSearchResult(result["title"], result["link"], 3500, 300)
|
82
|
+
for result in raw_results
|
83
|
+
]
|
84
|
+
|
85
|
+
|
86
|
+
def metaphor_search(query: str, num_results: int = 5) -> List[WebSearchResult]:
|
87
|
+
"""
|
88
|
+
Method that makes an API call by Metaphor client that queries
|
89
|
+
the top num_results links that matches the query. Returns a list
|
90
|
+
of WebSearchResult objects.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
query (str): The query body that users wants to make.
|
94
|
+
num_results (int): Number of top matching results that we want
|
95
|
+
to grab
|
96
|
+
"""
|
97
|
+
|
98
|
+
load_dotenv()
|
99
|
+
|
100
|
+
api_key = os.getenv("METAPHOR_API_KEY") or os.getenv("EXA_API_KEY")
|
101
|
+
if not api_key:
|
102
|
+
raise ValueError(
|
103
|
+
"""
|
104
|
+
Neither METAPHOR_API_KEY nor EXA_API_KEY environment variables are set.
|
105
|
+
Please set one of them to your API key, and try again.
|
106
|
+
"""
|
107
|
+
)
|
108
|
+
|
109
|
+
try:
|
110
|
+
from metaphor_python import Metaphor
|
111
|
+
except ImportError:
|
112
|
+
raise ImportError(
|
113
|
+
"You are attempting to use the `metaphor_python` library;"
|
114
|
+
"To use it, please install langroid with the `metaphor` extra, e.g. "
|
115
|
+
"`pip install langroid[metaphor]` or `poetry add langroid[metaphor]` "
|
116
|
+
"or `uv add langroid[metaphor]`"
|
117
|
+
"(it installs the `metaphor_python` package from pypi)."
|
118
|
+
)
|
119
|
+
|
120
|
+
client = Metaphor(api_key=api_key)
|
121
|
+
|
122
|
+
response = client.search(
|
123
|
+
query=query,
|
124
|
+
num_results=num_results,
|
125
|
+
)
|
126
|
+
raw_results = response.results
|
127
|
+
|
128
|
+
return [
|
129
|
+
WebSearchResult(result.title, result.url, 3500, 300) for result in raw_results
|
130
|
+
]
|
131
|
+
|
132
|
+
|
133
|
+
def duckduckgo_search(query: str, num_results: int = 5) -> List[WebSearchResult]:
|
134
|
+
"""
|
135
|
+
Method that makes an API call by DuckDuckGo client that queries
|
136
|
+
the top `num_results` links that matche the query. Returns a list
|
137
|
+
of WebSearchResult objects.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
query (str): The query body that users wants to make.
|
141
|
+
num_results (int): Number of top matching results that we want
|
142
|
+
to grab
|
143
|
+
"""
|
144
|
+
|
145
|
+
with DDGS() as ddgs:
|
146
|
+
search_results = [r for r in ddgs.text(query, max_results=num_results)]
|
147
|
+
|
148
|
+
return [
|
149
|
+
WebSearchResult(
|
150
|
+
title=result["title"],
|
151
|
+
link=result["href"],
|
152
|
+
max_content_length=3500,
|
153
|
+
max_summary_length=300,
|
154
|
+
)
|
155
|
+
for result in search_results
|
156
|
+
]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
|
4
|
+
def collate_chat_history(inputs: List[tuple[str, str]]) -> str:
|
5
|
+
"""
|
6
|
+
Collate (human, ai) pairs into a single, string
|
7
|
+
Args:
|
8
|
+
inputs:
|
9
|
+
Returns:
|
10
|
+
"""
|
11
|
+
pairs = [
|
12
|
+
f"""Human:{human}
|
13
|
+
AI:{ai}
|
14
|
+
"""
|
15
|
+
for human, ai in inputs
|
16
|
+
]
|
17
|
+
return "\n".join(pairs)
|
@@ -0,0 +1,141 @@
|
|
1
|
+
from langroid.utils.constants import NO_ANSWER
|
2
|
+
|
3
|
+
EXTRACT_RELEVANT = """
|
4
|
+
Here is a passage from a long document, followed by a question.
|
5
|
+
In case the passage contains any text relevant to answer the question, return it
|
6
|
+
verbatim.
|
7
|
+
{passage}
|
8
|
+
Question:{question}
|
9
|
+
Relevant text, if any: """.strip()
|
10
|
+
|
11
|
+
EXTRACTION_PROMPT_GPT4 = f"""
|
12
|
+
Given the content and question below, extract COMPLETE SENTENCES OR PHRASES
|
13
|
+
VERBATIM from the content, that are relevant to answering the question (if such text
|
14
|
+
exists), even if it contradicts your knowledge, and even if it is factually incorrect.
|
15
|
+
Do not make up an answer that is not supported by the content.
|
16
|
+
When you answer, be concise, no need to explain anything. If there is no relevant text,
|
17
|
+
simply say {NO_ANSWER}.
|
18
|
+
|
19
|
+
Content: {{content}}
|
20
|
+
Question: {{question}}
|
21
|
+
Relevant text, if any:
|
22
|
+
"""
|
23
|
+
|
24
|
+
EXTRACTION_PROMPT = f"""
|
25
|
+
Given the content and question below, extract a COMPLETE SENTENCE verbatim from the
|
26
|
+
content that is relevant to answering the question (if such text exists). Do not
|
27
|
+
make up an answer.
|
28
|
+
|
29
|
+
Content: The Eiffel Tower is a wrought-iron lattice tower on the Champ de Mars in
|
30
|
+
Paris, France. It is named after Gustave Eiffel, whose company designed and built
|
31
|
+
the tower. It is a recognizable landmark.
|
32
|
+
Question: Where is the Eiffel Tower located?
|
33
|
+
Relevant text, if any: on the Champ de Mars in Paris, France.
|
34
|
+
|
35
|
+
Content: Apples and oranges are both fruits, but differ in taste and texture.
|
36
|
+
Apples are sweet and crisp, while oranges are citrusy and juicy. Both are
|
37
|
+
nutritious and commonly consumed worldwide.
|
38
|
+
Question: What are the similarities between apples and oranges?
|
39
|
+
Relevant text, if any: both fruits
|
40
|
+
|
41
|
+
Content: The sun rises in the east and sets in the west. It is a source of light
|
42
|
+
and warmth for the Earth.
|
43
|
+
Question: What is the color of the sun?
|
44
|
+
Relevant text, if any: {NO_ANSWER}
|
45
|
+
|
46
|
+
Content: {{content}}
|
47
|
+
Question: {{question}}
|
48
|
+
Relevant text (COMPLETE SENTENCE), if any:
|
49
|
+
""".strip()
|
50
|
+
|
51
|
+
SUMMARY_ANSWER_PROMPT_GPT4 = f"""
|
52
|
+
|
53
|
+
Use the provided NUMBERED extracts (with sources) to answer the QUESTION.
|
54
|
+
If there's not enough information, respond with {NO_ANSWER}. Use only the
|
55
|
+
information in these extracts, even if your answer is factually incorrect,
|
56
|
+
and even if the answer contradicts other parts of the document. The only
|
57
|
+
important thing is that your answer is consistent with and supported by the
|
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.
|
67
|
+
|
68
|
+
NUMBERED EXTRACTS:
|
69
|
+
|
70
|
+
{{extracts}}
|
71
|
+
|
72
|
+
QUESTION:
|
73
|
+
{{question}}
|
74
|
+
|
75
|
+
""".strip()
|
76
|
+
|
77
|
+
ANSWER_PROMPT_USE_HISTORY_GPT4 = f"""
|
78
|
+
|
79
|
+
Use ANY of the information earlier, as well as the extracts provided below
|
80
|
+
(with sources) to answer the question. If there's not
|
81
|
+
enough information, respond with {NO_ANSWER}.
|
82
|
+
Use only the information in this conversation or these extracts,
|
83
|
+
even if your answer is factually incorrect, and even
|
84
|
+
if the answer contracts other parts of the document.
|
85
|
+
The only important thing is that your answer is
|
86
|
+
consistent with information provided here or earlier.
|
87
|
+
Compose your complete answer and cite all supporting sources
|
88
|
+
on a separate separate line as "SOURCE:".
|
89
|
+
When citing a SOURCE: be concise, whether it refers to a source in these
|
90
|
+
extracts, or info provided earlier.
|
91
|
+
|
92
|
+
{{extracts}}
|
93
|
+
{{question}}
|
94
|
+
Answer:
|
95
|
+
""".strip()
|
96
|
+
|
97
|
+
|
98
|
+
SUMMARY_ANSWER_PROMPT = f"""
|
99
|
+
Use the provided extracts (with sources) to answer the question.
|
100
|
+
If there's not enough information, respond with {NO_ANSWER}.
|
101
|
+
Use only the information in these extracts, even if it contradicts your prior
|
102
|
+
knowledge. Justify your answer by citing your sources, as in these examples:
|
103
|
+
|
104
|
+
Extract: The tree species in the garden include oak, maple, and birch.
|
105
|
+
Source: https://en.wikipedia.org/wiki/Tree
|
106
|
+
Extract: The oak trees are known for their longevity and strength.
|
107
|
+
Source: https://en.wikipedia.org/wiki/Oak
|
108
|
+
Question: What types of trees are in the garden?
|
109
|
+
Answer: The types of trees in the garden include oak, maple, and birch.
|
110
|
+
SOURCE: https://en.wikipedia.org/wiki/Tree
|
111
|
+
TEXT: The tree species in the garden include oak, maple, and birch.
|
112
|
+
|
113
|
+
Extract: The experiment involved three groups: control, low dose, and high
|
114
|
+
dose.
|
115
|
+
Source: https://en.wikipedia.org/wiki/Experiment
|
116
|
+
Extract: The high dose group showed significant improvement in symptoms.
|
117
|
+
Source: https://en.wikipedia.org/wiki/Experiment
|
118
|
+
Extract: The control group did not receive any
|
119
|
+
treatment and served as a baseline.
|
120
|
+
Source: https://en.wikipedia.org/wiki/Experiment
|
121
|
+
Question: How many groups were involved which group showed significant
|
122
|
+
improvement?
|
123
|
+
Answer: There were three groups and the high dose group showed significant
|
124
|
+
improvement in symptoms.
|
125
|
+
SOURCE: https://en.wikipedia.org/wiki/Experiment
|
126
|
+
TEXT: The experiment involved three groups: control, low dose, and high dose.
|
127
|
+
SOURCE: https://en.wikipedia.org/wiki/Experiment
|
128
|
+
TEXT: The high dose group showed significant improvement in symptoms.
|
129
|
+
|
130
|
+
|
131
|
+
Extract: The CEO announced several new initiatives during the company meeting.
|
132
|
+
Source: https://en.wikipedia.org/wiki/CEO
|
133
|
+
Extract: The financial performance of the company has been strong this year.
|
134
|
+
Source: https://en.wikipedia.org/wiki/CEO
|
135
|
+
Question: What new initiatives did the CEO announce?
|
136
|
+
Answer: {NO_ANSWER}
|
137
|
+
|
138
|
+
{{extracts}}
|
139
|
+
{{question}}
|
140
|
+
Answer:
|
141
|
+
""".strip()
|
@@ -0,0 +1,10 @@
|
|
1
|
+
"""
|
2
|
+
If we're on Pydantic v2, use the v1 namespace, else just use the main namespace.
|
3
|
+
|
4
|
+
This allows compatibility with both Pydantic v1 and v2
|
5
|
+
"""
|
6
|
+
|
7
|
+
try:
|
8
|
+
from pydantic.v1 import * # noqa: F403, F401
|
9
|
+
except ImportError:
|
10
|
+
from pydantic import * # type: ignore # noqa: F403, F401
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from . import configuration
|
2
|
+
from . import globals
|
3
|
+
from . import constants
|
4
|
+
from . import logging
|
5
|
+
from . import pydantic_utils
|
6
|
+
from . import system
|
7
|
+
from . import output
|
8
|
+
from . import object_registry
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"configuration",
|
12
|
+
"globals",
|
13
|
+
"constants",
|
14
|
+
"logging",
|
15
|
+
"pydantic_utils",
|
16
|
+
"system",
|
17
|
+
"output",
|
18
|
+
"object_registry",
|
19
|
+
]
|
@@ -0,0 +1,103 @@
|
|
1
|
+
"""
|
2
|
+
Graph algos.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, List, no_type_check
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
|
10
|
+
@no_type_check
|
11
|
+
def topological_sort(order: np.array) -> List[int]:
|
12
|
+
"""
|
13
|
+
Given a directed adjacency matrix, return a topological sort of the nodes.
|
14
|
+
order[i,j] = -1 means there is an edge from i to j.
|
15
|
+
order[i,j] = 0 means there is no edge from i to j.
|
16
|
+
order[i,j] = 1 means there is an edge from j to i.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
order (np.array): The adjacency matrix.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
List[int]: The topological sort of the nodes.
|
23
|
+
|
24
|
+
"""
|
25
|
+
n = order.shape[0]
|
26
|
+
|
27
|
+
# Calculate the in-degrees
|
28
|
+
in_degree = [0] * n
|
29
|
+
for i in range(n):
|
30
|
+
for j in range(n):
|
31
|
+
if order[i, j] == -1:
|
32
|
+
in_degree[j] += 1
|
33
|
+
|
34
|
+
# Initialize the queue with nodes of in-degree 0
|
35
|
+
queue = [i for i in range(n) if in_degree[i] == 0]
|
36
|
+
result = []
|
37
|
+
|
38
|
+
while queue:
|
39
|
+
node = queue.pop(0)
|
40
|
+
result.append(node)
|
41
|
+
|
42
|
+
for i in range(n):
|
43
|
+
if order[node, i] == -1:
|
44
|
+
in_degree[i] -= 1
|
45
|
+
if in_degree[i] == 0:
|
46
|
+
queue.append(i)
|
47
|
+
|
48
|
+
assert len(result) == n, "Cycle detected"
|
49
|
+
return result
|
50
|
+
|
51
|
+
|
52
|
+
@no_type_check
|
53
|
+
def components(order: np.ndarray) -> List[List[int]]:
|
54
|
+
"""
|
55
|
+
Find the connected components in an undirected graph represented by a matrix.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
order (np.ndarray): A matrix with values 0 or 1 indicating
|
59
|
+
undirected graph edges. `order[i][j] = 1` means an edge between `i`
|
60
|
+
and `j`, and `0` means no edge.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
List[List[int]]: A list of List where each List contains the indices of
|
64
|
+
nodes in the same connected component.
|
65
|
+
|
66
|
+
Example:
|
67
|
+
order = np.array([
|
68
|
+
[1, 1, 0, 0],
|
69
|
+
[1, 1, 1, 0],
|
70
|
+
[0, 1, 1, 0],
|
71
|
+
[0, 0, 0, 1]
|
72
|
+
])
|
73
|
+
components(order)
|
74
|
+
# [[0, 1, 2], [3]]
|
75
|
+
"""
|
76
|
+
|
77
|
+
i2g: Dict[int, int] = {} # index to group mapping
|
78
|
+
next_group = 0
|
79
|
+
n = order.shape[0]
|
80
|
+
for i in range(n):
|
81
|
+
connected_groups = {i2g[j] for j in np.nonzero(order[i, :])[0] if j in i2g}
|
82
|
+
|
83
|
+
# If the node is not part of any group
|
84
|
+
# and is not connected to any groups, assign a new group
|
85
|
+
if not connected_groups:
|
86
|
+
i2g[i] = next_group
|
87
|
+
next_group += 1
|
88
|
+
else:
|
89
|
+
# If the node is connected to multiple groups, we merge them
|
90
|
+
main_group = min(connected_groups)
|
91
|
+
for j in np.nonzero(order[i, :])[0]:
|
92
|
+
if i2g.get(j) in connected_groups:
|
93
|
+
i2g[j] = main_group
|
94
|
+
i2g[i] = main_group
|
95
|
+
|
96
|
+
# Convert i2g to a list of Lists
|
97
|
+
groups: Dict[int, List[int]] = {}
|
98
|
+
for index, group in i2g.items():
|
99
|
+
if group not in groups:
|
100
|
+
groups[group] = []
|
101
|
+
groups[group].append(index)
|
102
|
+
|
103
|
+
return list(groups.values())
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import copy
|
2
|
+
import os
|
3
|
+
from contextlib import contextmanager
|
4
|
+
from typing import Iterator, List, Literal
|
5
|
+
|
6
|
+
from dotenv import find_dotenv, load_dotenv
|
7
|
+
|
8
|
+
from langroid.pydantic_v1 import BaseSettings
|
9
|
+
|
10
|
+
|
11
|
+
class Settings(BaseSettings):
|
12
|
+
# NOTE all of these can be overridden in your .env file with upper-case names,
|
13
|
+
# for example CACHE_TYPE=momento
|
14
|
+
debug: bool = False # show debug messages?
|
15
|
+
max_turns: int = -1 # maximum number of turns in a task (to avoid inf loop)
|
16
|
+
progress: bool = False # show progress spinners/bars?
|
17
|
+
stream: bool = True # stream output?
|
18
|
+
cache: bool = True # use cache?
|
19
|
+
cache_type: Literal["redis", "fakeredis", "momento", "none"] = "redis" # cache type
|
20
|
+
chat_model: str = "" # language model name, e.g. litellm/ollama/llama2
|
21
|
+
quiet: bool = False # quiet mode (i.e. suppress all output)?
|
22
|
+
notebook: bool = False # running in a notebook?
|
23
|
+
|
24
|
+
class Config:
|
25
|
+
extra = "forbid"
|
26
|
+
|
27
|
+
|
28
|
+
load_dotenv(find_dotenv(usecwd=True)) # get settings from .env file
|
29
|
+
settings = Settings()
|
30
|
+
|
31
|
+
|
32
|
+
def update_global_settings(cfg: BaseSettings, keys: List[str]) -> None:
|
33
|
+
"""
|
34
|
+
Update global settings so modules can access them via (as an example):
|
35
|
+
```
|
36
|
+
from langroid.utils.configuration import settings
|
37
|
+
if settings.debug...
|
38
|
+
```
|
39
|
+
Caution we do not want to have too many such global settings!
|
40
|
+
Args:
|
41
|
+
cfg: pydantic config, typically from a main script
|
42
|
+
keys: which keys from cfg to use, to update the global settings object
|
43
|
+
"""
|
44
|
+
config_dict = cfg.dict()
|
45
|
+
|
46
|
+
# Filter the config_dict based on the keys
|
47
|
+
filtered_config = {key: config_dict[key] for key in keys if key in config_dict}
|
48
|
+
|
49
|
+
# create a new Settings() object to let pydantic validate it
|
50
|
+
new_settings = Settings(**filtered_config)
|
51
|
+
|
52
|
+
# Update the unique global settings object
|
53
|
+
settings.__dict__.update(new_settings.__dict__)
|
54
|
+
|
55
|
+
|
56
|
+
def set_global(key_vals: Settings) -> None:
|
57
|
+
"""Update the unique global settings object"""
|
58
|
+
settings.__dict__.update(key_vals.__dict__)
|
59
|
+
|
60
|
+
|
61
|
+
@contextmanager
|
62
|
+
def temporary_settings(temp_settings: Settings) -> Iterator[None]:
|
63
|
+
"""Temporarily update the global settings and restore them afterward."""
|
64
|
+
original_settings = copy.deepcopy(settings)
|
65
|
+
|
66
|
+
set_global(temp_settings)
|
67
|
+
|
68
|
+
try:
|
69
|
+
yield
|
70
|
+
finally:
|
71
|
+
settings.__dict__.update(original_settings.__dict__)
|
72
|
+
|
73
|
+
|
74
|
+
@contextmanager
|
75
|
+
def quiet_mode(quiet: bool = True) -> Iterator[None]:
|
76
|
+
"""Temporarily set quiet=True in global settings and restore afterward."""
|
77
|
+
original_settings = copy.deepcopy(settings)
|
78
|
+
if quiet:
|
79
|
+
temp_settings = original_settings.copy(update={"quiet": True})
|
80
|
+
set_global(temp_settings)
|
81
|
+
|
82
|
+
try:
|
83
|
+
yield
|
84
|
+
finally:
|
85
|
+
if quiet:
|
86
|
+
settings.__dict__.update(original_settings.__dict__)
|
87
|
+
|
88
|
+
|
89
|
+
def set_env(settings: BaseSettings) -> None:
|
90
|
+
"""
|
91
|
+
Set environment variables from a BaseSettings instance
|
92
|
+
Args:
|
93
|
+
settings (BaseSettings): desired settings
|
94
|
+
Returns:
|
95
|
+
"""
|
96
|
+
for field_name, field in settings.__class__.__fields__.items():
|
97
|
+
env_var_name = field.field_info.extra.get("env", field_name).upper()
|
98
|
+
os.environ[env_var_name] = str(settings.dict()[field_name])
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from langroid.pydantic_v1 import BaseModel
|
2
|
+
|
3
|
+
|
4
|
+
# Define the ANSI escape sequences for various colors and reset
|
5
|
+
class Colors(BaseModel):
|
6
|
+
RED: str = "\033[31m"
|
7
|
+
BLUE: str = "\033[34m"
|
8
|
+
GREEN: str = "\033[32m"
|
9
|
+
ORANGE: str = "\033[33m" # no standard ANSI color for orange; using yellow
|
10
|
+
CYAN: str = "\033[36m"
|
11
|
+
MAGENTA: str = "\033[35m"
|
12
|
+
YELLOW: str = "\033[33m"
|
13
|
+
RESET: str = "\033[0m"
|
14
|
+
|
15
|
+
|
16
|
+
NO_ANSWER = "DO-NOT-KNOW"
|
17
|
+
DONE = "DONE"
|
18
|
+
USER_QUIT_STRINGS = ["q", "x", "quit", "exit", "bye", DONE]
|
19
|
+
PASS = "__PASS__"
|
20
|
+
PASS_TO = PASS + ":"
|
21
|
+
SEND_TO = "__SEND__:"
|
22
|
+
TOOL = "TOOL"
|
23
|
+
# This is a recommended setting for TaskConfig.addressing_prefix if using it at all;
|
24
|
+
# prefer to use `RecipientTool` to allow agents addressing others.
|
25
|
+
# Caution the AT string should NOT contain any 'word' characters, i.e.
|
26
|
+
# it no letters, digits or underscores.
|
27
|
+
# See tests/main/test_msg_routing for example usage
|
28
|
+
AT = "|@|"
|
29
|
+
TOOL_BEGIN = "TOOL_BEGIN"
|
30
|
+
TOOL_END = "TOOL_END"
|