langroid 0.16.2__tar.gz → 0.16.4__tar.gz

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 (144) hide show
  1. {langroid-0.16.2 → langroid-0.16.4}/PKG-INFO +1 -1
  2. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/base.py +2 -0
  3. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/openai_gpt.py +7 -2
  4. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/parse_json.py +23 -65
  5. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/logging.py +2 -1
  6. {langroid-0.16.2 → langroid-0.16.4}/pyproject.toml +1 -1
  7. {langroid-0.16.2 → langroid-0.16.4}/LICENSE +0 -0
  8. {langroid-0.16.2 → langroid-0.16.4}/README.md +0 -0
  9. {langroid-0.16.2 → langroid-0.16.4}/langroid/__init__.py +0 -0
  10. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/__init__.py +0 -0
  11. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/base.py +0 -0
  12. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/batch.py +0 -0
  13. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/callbacks/__init__.py +0 -0
  14. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/callbacks/chainlit.py +0 -0
  15. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/chat_agent.py +0 -0
  16. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/chat_document.py +0 -0
  17. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/helpers.py +0 -0
  18. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/junk +0 -0
  19. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/openai_assistant.py +0 -0
  20. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/__init__.py +0 -0
  21. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/doc_chat_agent.py +0 -0
  22. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  23. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/lance_rag/__init__.py +0 -0
  24. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  25. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  26. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  27. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/lance_tools.py +0 -0
  28. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/neo4j/__init__.py +0 -0
  29. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  30. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  31. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  32. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  33. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  34. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/retriever_agent.py +0 -0
  35. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/sql/__init__.py +0 -0
  36. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  37. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/sql/utils/__init__.py +0 -0
  38. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  39. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  40. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/sql/utils/system_message.py +0 -0
  41. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/sql/utils/tools.py +0 -0
  42. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/special/table_chat_agent.py +0 -0
  43. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/structured_message.py +0 -0
  44. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/task.py +0 -0
  45. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tool_message.py +0 -0
  46. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/__init__.py +0 -0
  47. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  48. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/extract_tool.py +0 -0
  49. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/generator_tool.py +0 -0
  50. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/google_search_tool.py +0 -0
  51. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  52. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/note_tool.py +0 -0
  53. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/orchestration.py +0 -0
  54. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/recipient_tool.py +0 -0
  55. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/retrieval_tool.py +0 -0
  56. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/rewind_tool.py +0 -0
  57. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/run_python_code.py +0 -0
  58. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/tools/segment_extract_tool.py +0 -0
  59. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent/typed_task.py +0 -0
  60. {langroid-0.16.2 → langroid-0.16.4}/langroid/agent_config.py +0 -0
  61. {langroid-0.16.2 → langroid-0.16.4}/langroid/cachedb/__init__.py +0 -0
  62. {langroid-0.16.2 → langroid-0.16.4}/langroid/cachedb/base.py +0 -0
  63. {langroid-0.16.2 → langroid-0.16.4}/langroid/cachedb/momento_cachedb.py +0 -0
  64. {langroid-0.16.2 → langroid-0.16.4}/langroid/cachedb/redis_cachedb.py +0 -0
  65. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/__init__.py +0 -0
  66. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/base.py +0 -0
  67. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/clustering.py +0 -0
  68. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/models.py +0 -0
  69. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/protoc/__init__.py +0 -0
  70. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  71. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  72. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  73. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  74. {langroid-0.16.2 → langroid-0.16.4}/langroid/embedding_models/remote_embeds.py +0 -0
  75. {langroid-0.16.2 → langroid-0.16.4}/langroid/exceptions.py +0 -0
  76. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/.chainlit/config.toml +0 -0
  77. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  78. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/__init__.py +0 -0
  79. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/azure_openai.py +0 -0
  80. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/config.py +0 -0
  81. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/mock_lm.py +0 -0
  82. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  83. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/prompt_formatter/base.py +0 -0
  84. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  85. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  86. {langroid-0.16.2 → langroid-0.16.4}/langroid/language_models/utils.py +0 -0
  87. {langroid-0.16.2 → langroid-0.16.4}/langroid/mytypes.py +0 -0
  88. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/__init__.py +0 -0
  89. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/agent_chats.py +0 -0
  90. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/code-parsing.md +0 -0
  91. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/code_parser.py +0 -0
  92. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/config.py +0 -0
  93. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/document_parser.py +0 -0
  94. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/image_text.py +0 -0
  95. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/para_sentence_split.py +0 -0
  96. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/parser.py +0 -0
  97. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/repo_loader.py +0 -0
  98. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/routing.py +0 -0
  99. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/search.py +0 -0
  100. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/spider.py +0 -0
  101. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/table_loader.py +0 -0
  102. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/url_loader.py +0 -0
  103. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/url_loader_cookies.py +0 -0
  104. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/urls.py +0 -0
  105. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/utils.py +0 -0
  106. {langroid-0.16.2 → langroid-0.16.4}/langroid/parsing/web_search.py +0 -0
  107. {langroid-0.16.2 → langroid-0.16.4}/langroid/prompts/__init__.py +0 -0
  108. {langroid-0.16.2 → langroid-0.16.4}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  109. {langroid-0.16.2 → langroid-0.16.4}/langroid/prompts/dialog.py +0 -0
  110. {langroid-0.16.2 → langroid-0.16.4}/langroid/prompts/prompts_config.py +0 -0
  111. {langroid-0.16.2 → langroid-0.16.4}/langroid/prompts/templates.py +0 -0
  112. {langroid-0.16.2 → langroid-0.16.4}/langroid/py.typed +0 -0
  113. {langroid-0.16.2 → langroid-0.16.4}/langroid/pydantic_v1/__init__.py +0 -0
  114. {langroid-0.16.2 → langroid-0.16.4}/langroid/pydantic_v1/main.py +0 -0
  115. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/.chainlit/config.toml +0 -0
  116. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  117. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/__init__.py +0 -0
  118. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/algorithms/__init__.py +0 -0
  119. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/algorithms/graph.py +0 -0
  120. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/configuration.py +0 -0
  121. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/constants.py +0 -0
  122. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/docker.py +0 -0
  123. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/globals.py +0 -0
  124. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/llms/__init__.py +0 -0
  125. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/llms/strings.py +0 -0
  126. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/object_registry.py +0 -0
  127. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/output/__init__.py +0 -0
  128. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/output/citations.py +0 -0
  129. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/output/printing.py +0 -0
  130. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/output/status.py +0 -0
  131. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/pandas_utils.py +0 -0
  132. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/pydantic_utils.py +0 -0
  133. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/system.py +0 -0
  134. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/types.py +0 -0
  135. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/web/__init__.py +0 -0
  136. {langroid-0.16.2 → langroid-0.16.4}/langroid/utils/web/login.py +0 -0
  137. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/__init__.py +0 -0
  138. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/base.py +0 -0
  139. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/chromadb.py +0 -0
  140. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/lancedb.py +0 -0
  141. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/meilisearch.py +0 -0
  142. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/momento.py +0 -0
  143. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/qdrant_cloud.py +0 -0
  144. {langroid-0.16.2 → langroid-0.16.4}/langroid/vector_store/qdrantdb.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.16.2
3
+ Version: 0.16.4
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -219,6 +219,8 @@ class LLMMessage(BaseModel):
219
219
  # in case has_system_role is False
220
220
  if not has_system_role and "role" in d and d["role"] == "system":
221
221
  d["role"] = "user"
222
+ if "content" in d:
223
+ d["content"] = "[ADDITIONAL SYSTEM MESSAGE:]\n\n" + d["content"]
222
224
  # drop None values since API doesn't accept them
223
225
  dict_no_none = {k: v for k, v in d.items() if v is not None}
224
226
  if "name" in dict_no_none and dict_no_none["name"] == "":
@@ -1123,8 +1123,13 @@ class OpenAIGPT(LanguageModel):
1123
1123
  ) -> LLMTokenUsage:
1124
1124
  """
1125
1125
  Extracts token usage from ``response`` and computes cost, only when NOT
1126
- in streaming mode, since the LLM API (OpenAI currently) does not populate the
1127
- usage fields in streaming mode. In streaming mode, these are set to zero for
1126
+ in streaming mode, since the LLM API (OpenAI currently) was not
1127
+ populating the usage fields in streaming mode (but as of Sep 2024, streaming
1128
+ responses include usage info as well, so we should update the code
1129
+ to directly use usage information from the streaming response, which is more
1130
+ accurate, esp with "thinking" LLMs like o1 series which consume
1131
+ thinking tokens).
1132
+ In streaming mode, these are set to zero for
1128
1133
  now, and will be updated later by the fn ``update_token_usage``.
1129
1134
  """
1130
1135
  cost = 0.0
@@ -4,6 +4,7 @@ from datetime import datetime
4
4
  from typing import Any, Dict, Iterator, List, Union
5
5
 
6
6
  import yaml
7
+ from json_repair import repair_json
7
8
  from pyparsing import nestedExpr, originalTextFor
8
9
 
9
10
 
@@ -47,34 +48,6 @@ def get_json_candidates(s: str) -> List[str]:
47
48
  return []
48
49
 
49
50
 
50
- def add_quotes(s: str) -> str:
51
- """
52
- Replace accidentally un-quoted string-like keys and values in a potential json str.
53
- Intended to handle cases where a weak LLM may produce a JSON-like string
54
- containing, e.g. "rent": DO-NOT-KNOW, where it "forgot" to put quotes on the value,
55
- or city: "New York" where it "forgot" to put quotes on the key.
56
- It will even handle cases like 'address: do not know'.
57
-
58
- Got this fiendishly clever solution from
59
- https://stackoverflow.com/a/66053900/10940584
60
- Far better/safer than trying to do it with regexes.
61
-
62
- Args:
63
- - s (str): The potential JSON string to parse.
64
-
65
- Returns:
66
- - str: The (potential) JSON string with un-quoted string-like values
67
- replaced by quoted values.
68
- """
69
- if is_valid_json(s):
70
- return s
71
- try:
72
- dct = yaml.load(s, yaml.SafeLoader)
73
- return json.dumps(dct)
74
- except Exception:
75
- return s
76
-
77
-
78
51
  def parse_imperfect_json(json_string: str) -> Union[Dict[str, Any], List[Any]]:
79
52
  if not json_string.strip():
80
53
  raise ValueError("Empty string is not valid JSON")
@@ -89,7 +62,7 @@ def parse_imperfect_json(json_string: str) -> Union[Dict[str, Any], List[Any]]:
89
62
 
90
63
  # If ast.literal_eval fails or returns non-dict/list, try json.loads
91
64
  try:
92
- json_string = add_quotes(json_string)
65
+ json_string = repair_json(json_string)
93
66
  result = json.loads(json_string)
94
67
  if isinstance(result, (dict, list)):
95
68
  return result
@@ -102,39 +75,33 @@ def parse_imperfect_json(json_string: str) -> Union[Dict[str, Any], List[Any]]:
102
75
  except yaml.YAMLError:
103
76
  pass
104
77
 
105
- try:
106
- # last resort: try to repair the json using a lib
107
- from json_repair import repair_json
108
-
109
- repaired_json = repair_json(json_string)
110
- result = json.loads(repaired_json)
111
- if isinstance(result, (dict, list)):
112
- return result
113
- except Exception:
114
- pass
115
-
116
78
  # If all methods fail, raise ValueError
117
79
  raise ValueError(f"Unable to parse as JSON: {json_string}")
118
80
 
119
81
 
120
- def repair_newlines(s: str) -> str:
82
+ def try_repair_json_yaml(s: str) -> str | None:
121
83
  """
122
- Attempt to load as json, and if it fails, try with newlines replaced by space.
123
- Intended to handle cases where weak LLMs produce JSON-like strings where
124
- some string-values contain explicit newlines, e.g.:
125
- {"text": "This is a text\n with a newline"}
126
- These would not be valid JSON, so we try to clean them up here.
84
+ Attempt to load as json, and if it fails, try repairing the JSON.
85
+ If that fails, replace any \n with space as a last resort.
86
+ NOTE - replacing \n with space will result in format loss,
87
+ which may matter in generated code (e.g. python, toml, etc)
127
88
  """
128
- try:
129
- json.loads(s)
130
- return s
131
- except Exception:
89
+ s_repaired = repair_json(s)
90
+ if s_repaired != "":
91
+ return s_repaired # type: ignore
92
+ else:
132
93
  try:
133
- s = s.replace("\n", " ")
134
- json.loads(s)
135
- return s
136
- except Exception:
94
+ yaml_result = yaml.safe_load(s)
95
+ if isinstance(yaml_result, dict):
96
+ return json.dumps(yaml_result)
97
+ except yaml.YAMLError:
98
+ pass
99
+ # If it still fails, replace any \n with space as a last resort
100
+ s = s.replace("\n", " ")
101
+ if is_valid_json(s):
137
102
  return s
103
+ else:
104
+ return None # all failed
138
105
 
139
106
 
140
107
  def extract_top_level_json(s: str) -> List[str]:
@@ -148,18 +115,9 @@ def extract_top_level_json(s: str) -> List[str]:
148
115
  """
149
116
  # Find JSON object and array candidates
150
117
  json_candidates = get_json_candidates(s)
118
+ maybe_repaired_jsons = map(try_repair_json_yaml, json_candidates)
151
119
 
152
- normalized_candidates = [
153
- candidate.replace("\\{", "{").replace("\\}", "}").replace("\\_", "_")
154
- for candidate in json_candidates
155
- ]
156
- candidates = [add_quotes(candidate) for candidate in normalized_candidates]
157
- candidates = [repair_newlines(candidate) for candidate in candidates]
158
- top_level_jsons = [
159
- candidate for candidate in candidates if is_valid_json(candidate)
160
- ]
161
-
162
- return top_level_jsons
120
+ return [candidate for candidate in maybe_repaired_jsons if candidate is not None]
163
121
 
164
122
 
165
123
  def top_level_json_field(s: str, f: str) -> Any:
@@ -4,6 +4,7 @@ from typing import no_type_check
4
4
 
5
5
  import colorlog
6
6
  from rich.console import Console
7
+ from rich.markup import escape
7
8
 
8
9
 
9
10
  # Define a function to set up the colored logger
@@ -129,6 +130,6 @@ class RichFileLogger:
129
130
  with open(self.log_file, "a") as f:
130
131
  if self.color:
131
132
  console = Console(file=f, force_terminal=True, width=200)
132
- console.print(message)
133
+ console.print(escape(message))
133
134
  else:
134
135
  print(message, file=f)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.16.2"
3
+ version = "0.16.4"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes