langroid 0.59.0b3__py3-none-any.whl → 0.59.1__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.
Files changed (50) hide show
  1. langroid/agent/done_sequence_parser.py +46 -11
  2. langroid/agent/special/doc_chat_task.py +0 -0
  3. langroid/agent/task.py +44 -7
  4. langroid/language_models/model_info.py +51 -0
  5. langroid/mcp/__init__.py +1 -0
  6. langroid/mcp/server/__init__.py +1 -0
  7. langroid/pydantic_v1/__init__.py +1 -1
  8. {langroid-0.59.0b3.dist-info → langroid-0.59.1.dist-info}/METADATA +4 -1
  9. {langroid-0.59.0b3.dist-info → langroid-0.59.1.dist-info}/RECORD +11 -47
  10. langroid/agent/base.py-e +0 -2216
  11. langroid/agent/chat_agent.py-e +0 -2086
  12. langroid/agent/chat_document.py-e +0 -513
  13. langroid/agent/openai_assistant.py-e +0 -882
  14. langroid/agent/special/arangodb/arangodb_agent.py-e +0 -648
  15. langroid/agent/special/lance_tools.py-e +0 -61
  16. langroid/agent/special/neo4j/neo4j_chat_agent.py-e +0 -430
  17. langroid/agent/task.py-e +0 -2418
  18. langroid/agent/tool_message.py-e +0 -400
  19. langroid/agent/tools/file_tools.py-e +0 -234
  20. langroid/agent/tools/mcp/fastmcp_client.py-e +0 -584
  21. langroid/agent/tools/orchestration.py-e +0 -301
  22. langroid/agent/tools/task_tool.py-e +0 -249
  23. langroid/agent/xml_tool_message.py-e +0 -392
  24. langroid/embedding_models/models.py-e +0 -563
  25. langroid/language_models/azure_openai.py-e +0 -134
  26. langroid/language_models/base.py-e +0 -812
  27. langroid/language_models/config.py-e +0 -18
  28. langroid/language_models/model_info.py-e +0 -483
  29. langroid/language_models/openai_gpt.py-e +0 -2280
  30. langroid/language_models/provider_params.py-e +0 -153
  31. langroid/mytypes.py-e +0 -132
  32. langroid/parsing/file_attachment.py-e +0 -246
  33. langroid/parsing/md_parser.py-e +0 -574
  34. langroid/parsing/parser.py-e +0 -410
  35. langroid/parsing/repo_loader.py-e +0 -812
  36. langroid/parsing/url_loader.py-e +0 -683
  37. langroid/parsing/urls.py-e +0 -279
  38. langroid/pydantic_v1/__init__.py-e +0 -36
  39. langroid/pydantic_v1/main.py-e +0 -11
  40. langroid/utils/configuration.py-e +0 -141
  41. langroid/utils/constants.py-e +0 -32
  42. langroid/utils/globals.py-e +0 -49
  43. langroid/utils/html_logger.py-e +0 -825
  44. langroid/utils/object_registry.py-e +0 -66
  45. langroid/utils/pydantic_utils.py-e +0 -602
  46. langroid/utils/types.py-e +0 -113
  47. langroid/vector_store/lancedb.py-e +0 -404
  48. langroid/vector_store/pineconedb.py-e +0 -427
  49. {langroid-0.59.0b3.dist-info → langroid-0.59.1.dist-info}/WHEEL +0 -0
  50. {langroid-0.59.0b3.dist-info → langroid-0.59.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,279 +0,0 @@
1
- import logging
2
- import os
3
- import tempfile
4
- import urllib.parse
5
- import urllib.robotparser
6
- from typing import List, Optional, Set, Tuple
7
- from urllib.parse import urldefrag, urljoin, urlparse
8
-
9
- import fire
10
- import requests
11
- from bs4 import BeautifulSoup
12
- from rich import print
13
- from rich.prompt import Prompt
14
-
15
- from pydantic import BaseModel, HttpUrl, ValidationError, parse_obj_as
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- def url_to_tempfile(url: str) -> str:
21
- """
22
- Fetch content from the given URL and save it to a temporary local file.
23
-
24
- Args:
25
- url (str): The URL of the content to fetch.
26
-
27
- Returns:
28
- str: The path to the temporary file where the content is saved.
29
-
30
- Raises:
31
- HTTPError: If there's any issue fetching the content.
32
- """
33
-
34
- response = requests.get(url)
35
- response.raise_for_status() # Raise an exception for HTTP errors
36
-
37
- # Create a temporary file and write the content
38
- with tempfile.NamedTemporaryFile(delete=False, suffix=".tmp") as temp_file:
39
- temp_file.write(response.content)
40
- return temp_file.name
41
-
42
-
43
- def get_user_input(msg: str, color: str = "blue") -> str:
44
- """
45
- Prompt the user for input.
46
- Args:
47
- msg: printed prompt
48
- color: color of the prompt
49
- Returns:
50
- user input
51
- """
52
- color_str = f"[{color}]{msg} " if color else msg + " "
53
- print(color_str, end="")
54
- return input("")
55
-
56
-
57
- def get_list_from_user(
58
- prompt: str = "Enter input (type 'done' or hit return to finish)",
59
- n: int | None = None,
60
- ) -> List[str]:
61
- """
62
- Prompt the user for inputs.
63
- Args:
64
- prompt: printed prompt
65
- n: how many inputs to prompt for. If None, then prompt until done, otherwise
66
- quit after n inputs.
67
- Returns:
68
- list of input strings
69
- """
70
- # Create an empty set to store the URLs.
71
- input_set = set()
72
-
73
- # Use a while loop to continuously ask the user for URLs.
74
- for _ in range(n or 1000):
75
- # Prompt the user for input.
76
- input_str = Prompt.ask(f"[blue]{prompt}")
77
-
78
- # Check if the user wants to exit the loop.
79
- if input_str.lower() == "done" or input_str == "":
80
- break
81
-
82
- # if it is a URL, ask how many to crawl
83
- if is_url(input_str):
84
- url = input_str
85
- input_str = Prompt.ask("[blue] How many new URLs to crawl?", default="0")
86
- max_urls = int(input_str) + 1
87
- tot_urls = list(find_urls(url, max_links=max_urls, max_depth=2))
88
- tot_urls_str = "\n".join(tot_urls)
89
- print(
90
- f"""
91
- Found these {len(tot_urls)} links upto depth 2:
92
- {tot_urls_str}
93
- """
94
- )
95
-
96
- input_set.update(tot_urls)
97
- else:
98
- input_set.add(input_str.strip())
99
-
100
- return list(input_set)
101
-
102
-
103
- class Url(BaseModel):
104
- url: HttpUrl
105
-
106
-
107
- def is_url(s: str) -> bool:
108
- try:
109
- Url(url=parse_obj_as(HttpUrl, s))
110
- return True
111
- except ValidationError:
112
- return False
113
-
114
-
115
- def get_urls_paths_bytes_indices(
116
- inputs: List[str | bytes],
117
- ) -> Tuple[List[int], List[int], List[int]]:
118
- """
119
- Given a list of inputs, return a
120
- list of indices of URLs, list of indices of paths, list of indices of byte-contents.
121
- Args:
122
- inputs: list of strings or bytes
123
- Returns:
124
- list of Indices of URLs,
125
- list of indices of paths,
126
- list of indices of byte-contents
127
- """
128
- urls = []
129
- paths = []
130
- byte_list = []
131
- for i, item in enumerate(inputs):
132
- if isinstance(item, bytes):
133
- byte_list.append(i)
134
- continue
135
- try:
136
- Url(url=parse_obj_as(HttpUrl, item))
137
- urls.append(i)
138
- except ValidationError:
139
- if os.path.exists(item):
140
- paths.append(i)
141
- else:
142
- logger.warning(f"{item} is neither a URL nor a path.")
143
- return urls, paths, byte_list
144
-
145
-
146
- def crawl_url(url: str, max_urls: int = 1) -> List[str]:
147
- """
148
- Crawl starting at the url and return a list of URLs to be parsed,
149
- up to a maximum of `max_urls`.
150
- This has not been tested to work as intended. Ignore.
151
- """
152
- from trafilatura.spider import focused_crawler
153
-
154
- if max_urls == 1:
155
- # no need to crawl, just return the original list
156
- return [url]
157
-
158
- to_visit = None
159
- known_urls = None
160
-
161
- # Create a RobotFileParser object
162
- robots = urllib.robotparser.RobotFileParser()
163
- while True:
164
- if known_urls is not None and len(known_urls) >= max_urls:
165
- break
166
- # Set the RobotFileParser object to the website's robots.txt file
167
- robots.set_url(url + "/robots.txt")
168
- robots.read()
169
-
170
- if robots.can_fetch("*", url):
171
- # Start or resume the crawl
172
- to_visit, known_urls = focused_crawler(
173
- url,
174
- max_seen_urls=max_urls,
175
- max_known_urls=max_urls,
176
- todo=to_visit,
177
- known_links=known_urls,
178
- rules=robots,
179
- )
180
- if to_visit is None:
181
- break
182
-
183
- if known_urls is None:
184
- return [url]
185
- final_urls = [s.strip() for s in known_urls]
186
- return list(final_urls)[:max_urls]
187
-
188
-
189
- def find_urls(
190
- url: str = "https://en.wikipedia.org/wiki/Generative_pre-trained_transformer",
191
- max_links: int = 20,
192
- visited: Optional[Set[str]] = None,
193
- depth: int = 0,
194
- max_depth: int = 2,
195
- match_domain: bool = True,
196
- ) -> Set[str]:
197
- """
198
- Recursively find all URLs on a given page.
199
-
200
- Args:
201
- url (str): The URL to start from.
202
- max_links (int): The maximum number of links to find.
203
- visited (set): A set of URLs that have already been visited.
204
- depth (int): The current depth of the recursion.
205
- max_depth (int): The maximum depth of the recursion.
206
- match_domain (bool): Whether to only return URLs that are on the same domain.
207
-
208
- Returns:
209
- set: A set of URLs found on the page.
210
- """
211
-
212
- if visited is None:
213
- visited = set()
214
-
215
- if url in visited or depth > max_depth:
216
- return visited
217
-
218
- visited.add(url)
219
- base_domain = urlparse(url).netloc
220
-
221
- try:
222
- response = requests.get(url, timeout=5)
223
- response.raise_for_status()
224
- soup = BeautifulSoup(response.text, "html.parser")
225
- links = [
226
- urljoin(url, a["href"]) # type: ignore
227
- for a in soup.find_all("a", href=True)
228
- ]
229
-
230
- # Defrag links: discard links that are to portions of same page
231
- defragged_links = list(
232
- set(urldefrag(link).url for link in links) # type: ignore
233
- )
234
-
235
- # Filter links based on domain matching requirement
236
- domain_matching_links = [
237
- link for link in defragged_links if urlparse(link).netloc == base_domain
238
- ]
239
-
240
- # ensure url is first, since below we are taking first max_links urls
241
- domain_matching_links = [url] + [x for x in domain_matching_links if x != url]
242
-
243
- # If found links exceed max_links, return immediately
244
- if len(domain_matching_links) >= max_links:
245
- return set(domain_matching_links[:max_links])
246
-
247
- for link in domain_matching_links:
248
- if len(visited) >= max_links:
249
- break
250
-
251
- if link not in visited:
252
- visited.update(
253
- find_urls(
254
- link,
255
- max_links,
256
- visited,
257
- depth + 1,
258
- max_depth,
259
- match_domain,
260
- )
261
- )
262
-
263
- except (requests.RequestException, Exception) as e:
264
- print(f"Error fetching {url}. Error: {e}")
265
-
266
- return set(list(visited)[:max_links])
267
-
268
-
269
- def org_user_from_github(url: str) -> str:
270
- parsed = urllib.parse.urlparse(url)
271
- org, user = parsed.path.lstrip("/").split("/")
272
- return f"{org}-{user}"
273
-
274
-
275
- if __name__ == "__main__":
276
- # Example usage
277
- found_urls = set(fire.Fire(find_urls))
278
- for url in found_urls:
279
- print(url)
@@ -1,36 +0,0 @@
1
- """
2
- Compatibility layer for Pydantic v2 migration.
3
-
4
- This module now imports directly from Pydantic v2 since all internal code
5
- has been migrated to use Pydantic v2 patterns.
6
- """
7
-
8
- # Import everything from pydantic v2
9
- from pydantic import * # noqa: F403, F401
10
-
11
- # Import BaseSettings from pydantic-settings v2
12
- from pydantic_settings import BaseSettings # noqa: F401
13
-
14
- # Explicitly re-export commonly used items for better IDE support and type checking
15
- from pydantic import ( # noqa: F401
16
- BaseModel,
17
- Field,
18
- ConfigDict,
19
- ValidationError,
20
- field_validator,
21
- model_validator,
22
- create_model,
23
- HttpUrl,
24
- AnyUrl,
25
- TypeAdapter,
26
- )
27
-
28
- # Legacy names that map to v2 equivalents
29
- validator = field_validator # noqa: F401
30
- root_validator = model_validator # noqa: F401
31
-
32
-
33
- # For parse_obj_as, we need to create a wrapper function
34
- def parse_obj_as(type_, obj):
35
- """Compatibility wrapper for parse_obj_as which was removed in Pydantic v2."""
36
- return TypeAdapter(type_).validate_python(obj)
@@ -1,11 +0,0 @@
1
- """
2
- Compatibility layer for Pydantic v2 migration.
3
-
4
- This module now imports directly from Pydantic v2 since all internal code
5
- has been migrated to use Pydantic v2 patterns.
6
- """
7
-
8
- # Explicitly export BaseModel for better type checking
9
- from pydantic.main import * # noqa: F403, F401
10
-
11
- from pydantic import BaseModel # noqa: F401
@@ -1,141 +0,0 @@
1
- import os
2
- import threading
3
- from contextlib import contextmanager
4
- from typing import Any, Dict, Iterator, List, Literal, cast
5
-
6
- from dotenv import find_dotenv, load_dotenv
7
- from pydantic_settings import BaseSettings
8
-
9
- from pydantic import ConfigDict
10
-
11
- # Global reentrant lock to serialize any modifications to the global settings.
12
- _global_lock = threading.RLock()
13
-
14
-
15
- class Settings(BaseSettings):
16
- debug: bool = False # show debug messages?
17
- max_turns: int = -1 # maximum number of turns in a task (to avoid inf loop)
18
- progress: bool = False # show progress spinners/bars?
19
- stream: bool = True # stream output?
20
- cache: bool = True # use cache?
21
- cache_type: Literal["redis", "fakeredis", "none"] = "redis" # cache type
22
- chat_model: str = "" # language model name, e.g. litellm/ollama/llama2
23
- quiet: bool = False # quiet mode (i.e. suppress all output)?
24
- notebook: bool = False # running in a notebook?
25
-
26
- model_config = ConfigDict(extra="forbid")
27
-
28
-
29
- # Load environment variables from .env file.
30
- load_dotenv(find_dotenv(usecwd=True))
31
-
32
- # The global (default) settings instance.
33
- # This is updated by update_global_settings() and set_global().
34
- _global_settings = Settings()
35
-
36
- # Thread-local storage for temporary (per-thread) settings overrides.
37
- _thread_local = threading.local()
38
-
39
-
40
- class SettingsProxy:
41
- """
42
- A proxy for the settings that returns a thread‐local override if set,
43
- or else falls back to the global settings.
44
- """
45
-
46
- def __getattr__(self, name: str) -> Any:
47
- # If the calling thread has set an override, use that.
48
- if hasattr(_thread_local, "override"):
49
- return getattr(_thread_local.override, name)
50
- return getattr(_global_settings, name)
51
-
52
- def __setattr__(self, name: str, value: Any) -> None:
53
- # All writes go to the global settings.
54
- setattr(_global_settings, name, value)
55
-
56
- def update(self, new_settings: Settings) -> None:
57
- _global_settings.__dict__.update(new_settings.__dict__)
58
-
59
- def dict(self) -> Dict[str, Any]:
60
- # Return a dict view of the settings as seen by the caller.
61
- # Note that temporary overrides are not “merged” with global settings.
62
- if hasattr(_thread_local, "override"):
63
- return cast(
64
- Dict[str, Any], cast(Settings, _thread_local.override.model_dump())
65
- )
66
- return _global_settings.model_dump()
67
-
68
-
69
- settings = SettingsProxy()
70
-
71
-
72
- def update_global_settings(cfg: BaseSettings, keys: List[str]) -> None:
73
- """
74
- Update global settings so that modules can later access them via, e.g.,
75
-
76
- from langroid.utils.configuration import settings
77
- if settings.debug: ...
78
-
79
- This updates the global default.
80
- """
81
- config_dict = cfg.model_dump()
82
- filtered_config = {key: config_dict[key] for key in keys if key in config_dict}
83
- new_settings = Settings(**filtered_config)
84
- _global_settings.__dict__.update(new_settings.__dict__)
85
-
86
-
87
- def set_global(key_vals: Settings) -> None:
88
- """
89
- Update the global settings object.
90
- """
91
- _global_settings.__dict__.update(key_vals.__dict__)
92
-
93
-
94
- @contextmanager
95
- def temporary_settings(temp_settings: Settings) -> Iterator[None]:
96
- """
97
- Temporarily override the settings for the calling thread.
98
-
99
- Within the context, any access to "settings" will use the provided temporary
100
- settings. Once the context is exited, the thread reverts to the global settings.
101
- """
102
- saved = getattr(_thread_local, "override", None)
103
- _thread_local.override = temp_settings
104
- try:
105
- yield
106
- finally:
107
- if saved is not None:
108
- _thread_local.override = saved
109
- else:
110
- del _thread_local.override
111
-
112
-
113
- @contextmanager
114
- def quiet_mode(quiet: bool = True) -> Iterator[None]:
115
- """
116
- Temporarily override settings.quiet for the current thread.
117
- This implementation builds on the thread‑local temporary_settings context manager.
118
- The effective quiet mode is merged:
119
- if quiet is already True (from an outer context),
120
- then it remains True even if a nested context passes quiet=False.
121
- """
122
- current_effective = (
123
- settings.model_dump()
124
- ) # get the current thread's effective settings
125
- # Create a new settings instance from the current effective state.
126
- temp = Settings(**current_effective)
127
- # Merge the new flag: once quiet is enabled, it stays enabled.
128
- temp.quiet = settings.quiet or quiet
129
- with temporary_settings(temp):
130
- yield
131
-
132
-
133
- def set_env(settings_instance: BaseSettings) -> None:
134
- """
135
- Set environment variables from a BaseSettings instance.
136
-
137
- Each field in the settings is written to os.environ.
138
- """
139
- for field_name, field in settings_instance.__class__.__fields__.items():
140
- env_var_name = field.field_info.extra.get("env", field_name).upper()
141
- os.environ[env_var_name] = str(settings_instance.model_dump()[field_name])
@@ -1,32 +0,0 @@
1
- from pydantic 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
- GREEN_DIMMER: str = "\033[38;5;22m" # very dark green
10
- GREEN_DIM: str = "\033[38;5;28m" # medium-dim green
11
- ORANGE: str = "\033[33m" # no standard ANSI color for orange; using yellow
12
- CYAN: str = "\033[36m"
13
- MAGENTA: str = "\033[35m"
14
- YELLOW: str = "\033[33m"
15
- RESET: str = "\033[0m"
16
-
17
-
18
- NO_ANSWER = "DO-NOT-KNOW"
19
- DONE = "DONE"
20
- USER_QUIT_STRINGS = ["q", "x", "quit", "exit", "bye", DONE]
21
- PASS = "__PASS__"
22
- PASS_TO = PASS + ":"
23
- SEND_TO = "__SEND__:"
24
- TOOL = "TOOL"
25
- # This is a recommended setting for TaskConfig.addressing_prefix if using it at all;
26
- # prefer to use `RecipientTool` to allow agents addressing others.
27
- # Caution the AT string should NOT contain any 'word' characters, i.e.
28
- # it no letters, digits or underscores.
29
- # See tests/main/test_msg_routing for example usage
30
- AT = "|@|"
31
- TOOL_BEGIN = "TOOL_BEGIN"
32
- TOOL_END = "TOOL_END"
@@ -1,49 +0,0 @@
1
- from typing import Any, Dict, Optional, Type, TypeVar
2
-
3
- from pydantic import BaseModel
4
-
5
- T = TypeVar("T", bound="GlobalState")
6
-
7
-
8
- class GlobalState(BaseModel):
9
- """A base Pydantic model for global states."""
10
-
11
- _instance: Optional["GlobalState"] = None
12
-
13
- @classmethod
14
- def get_instance(cls: Type["GlobalState"]) -> "GlobalState":
15
- """
16
- Get the global instance of the specific subclass.
17
-
18
- Returns:
19
- The global instance of the subclass.
20
- """
21
- if cls._instance is None:
22
- cls._instance = cls()
23
- return cls._instance
24
-
25
- @classmethod
26
- def set_values(cls: Type[T], **kwargs: Dict[str, Any]) -> None:
27
- """
28
- Set values on the global instance of the specific subclass.
29
-
30
- Args:
31
- **kwargs: The fields and their values to set.
32
- """
33
- instance = cls.get_instance()
34
- for key, value in kwargs.items():
35
- setattr(instance, key, value)
36
-
37
- @classmethod
38
- def get_value(cls: Type[T], name: str) -> Any:
39
- """
40
- Retrieve the value of a specific field from the global instance.
41
-
42
- Args:
43
- name (str): The name of the field to retrieve.
44
-
45
- Returns:
46
- str: The value of the specified field.
47
- """
48
- instance = cls.get_instance()
49
- return getattr(instance, name)