langroid 0.18.1__tar.gz → 0.18.3__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 (143) hide show
  1. {langroid-0.18.1 → langroid-0.18.3}/PKG-INFO +2 -2
  2. {langroid-0.18.1 → langroid-0.18.3}/README.md +1 -1
  3. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/base.py +4 -0
  4. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/xml_tool_message.py +121 -24
  5. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/git_utils.py +3 -2
  6. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/system.py +32 -8
  7. {langroid-0.18.1 → langroid-0.18.3}/pyproject.toml +4 -1
  8. {langroid-0.18.1 → langroid-0.18.3}/LICENSE +0 -0
  9. {langroid-0.18.1 → langroid-0.18.3}/langroid/__init__.py +0 -0
  10. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/__init__.py +0 -0
  11. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/batch.py +0 -0
  12. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/callbacks/__init__.py +0 -0
  13. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/callbacks/chainlit.py +0 -0
  14. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/chat_agent.py +0 -0
  15. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/chat_document.py +0 -0
  16. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/helpers.py +0 -0
  17. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/junk +0 -0
  18. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/openai_assistant.py +0 -0
  19. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/__init__.py +0 -0
  20. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/doc_chat_agent.py +0 -0
  21. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  22. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/lance_rag/__init__.py +0 -0
  23. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  24. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  25. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  26. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/lance_tools.py +0 -0
  27. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/neo4j/__init__.py +0 -0
  28. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  29. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  30. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  31. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  32. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  33. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/retriever_agent.py +0 -0
  34. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/sql/__init__.py +0 -0
  35. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  36. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/sql/utils/__init__.py +0 -0
  37. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  38. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  39. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/sql/utils/system_message.py +0 -0
  40. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/sql/utils/tools.py +0 -0
  41. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/special/table_chat_agent.py +0 -0
  42. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/structured_message.py +0 -0
  43. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/task.py +0 -0
  44. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tool_message.py +0 -0
  45. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/__init__.py +0 -0
  46. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  47. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/file_tools.py +0 -0
  48. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/google_search_tool.py +0 -0
  49. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  50. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/orchestration.py +0 -0
  51. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/recipient_tool.py +0 -0
  52. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/retrieval_tool.py +0 -0
  53. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/rewind_tool.py +0 -0
  54. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/tools/segment_extract_tool.py +0 -0
  55. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent/typed_task.py +0 -0
  56. {langroid-0.18.1 → langroid-0.18.3}/langroid/agent_config.py +0 -0
  57. {langroid-0.18.1 → langroid-0.18.3}/langroid/cachedb/__init__.py +0 -0
  58. {langroid-0.18.1 → langroid-0.18.3}/langroid/cachedb/base.py +0 -0
  59. {langroid-0.18.1 → langroid-0.18.3}/langroid/cachedb/momento_cachedb.py +0 -0
  60. {langroid-0.18.1 → langroid-0.18.3}/langroid/cachedb/redis_cachedb.py +0 -0
  61. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/__init__.py +0 -0
  62. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/base.py +0 -0
  63. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/clustering.py +0 -0
  64. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/models.py +0 -0
  65. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/protoc/__init__.py +0 -0
  66. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  67. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  68. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  69. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  70. {langroid-0.18.1 → langroid-0.18.3}/langroid/embedding_models/remote_embeds.py +0 -0
  71. {langroid-0.18.1 → langroid-0.18.3}/langroid/exceptions.py +0 -0
  72. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/.chainlit/config.toml +0 -0
  73. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  74. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/__init__.py +0 -0
  75. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/azure_openai.py +0 -0
  76. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/base.py +0 -0
  77. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/config.py +0 -0
  78. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/mock_lm.py +0 -0
  79. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/openai_gpt.py +0 -0
  80. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  81. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/prompt_formatter/base.py +0 -0
  82. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  83. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  84. {langroid-0.18.1 → langroid-0.18.3}/langroid/language_models/utils.py +0 -0
  85. {langroid-0.18.1 → langroid-0.18.3}/langroid/mytypes.py +0 -0
  86. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/__init__.py +0 -0
  87. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/agent_chats.py +0 -0
  88. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/code-parsing.md +0 -0
  89. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/code_parser.py +0 -0
  90. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/config.py +0 -0
  91. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/document_parser.py +0 -0
  92. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/image_text.py +0 -0
  93. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/para_sentence_split.py +0 -0
  94. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/parse_json.py +0 -0
  95. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/parser.py +0 -0
  96. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/repo_loader.py +0 -0
  97. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/routing.py +0 -0
  98. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/search.py +0 -0
  99. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/spider.py +0 -0
  100. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/table_loader.py +0 -0
  101. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/url_loader.py +0 -0
  102. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/url_loader_cookies.py +0 -0
  103. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/urls.py +0 -0
  104. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/utils.py +0 -0
  105. {langroid-0.18.1 → langroid-0.18.3}/langroid/parsing/web_search.py +0 -0
  106. {langroid-0.18.1 → langroid-0.18.3}/langroid/prompts/__init__.py +0 -0
  107. {langroid-0.18.1 → langroid-0.18.3}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  108. {langroid-0.18.1 → langroid-0.18.3}/langroid/prompts/dialog.py +0 -0
  109. {langroid-0.18.1 → langroid-0.18.3}/langroid/prompts/prompts_config.py +0 -0
  110. {langroid-0.18.1 → langroid-0.18.3}/langroid/prompts/templates.py +0 -0
  111. {langroid-0.18.1 → langroid-0.18.3}/langroid/py.typed +0 -0
  112. {langroid-0.18.1 → langroid-0.18.3}/langroid/pydantic_v1/__init__.py +0 -0
  113. {langroid-0.18.1 → langroid-0.18.3}/langroid/pydantic_v1/main.py +0 -0
  114. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/.chainlit/config.toml +0 -0
  115. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  116. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/__init__.py +0 -0
  117. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/algorithms/__init__.py +0 -0
  118. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/algorithms/graph.py +0 -0
  119. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/configuration.py +0 -0
  120. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/constants.py +0 -0
  121. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/docker.py +0 -0
  122. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/globals.py +0 -0
  123. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/llms/__init__.py +0 -0
  124. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/llms/strings.py +0 -0
  125. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/logging.py +0 -0
  126. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/object_registry.py +0 -0
  127. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/output/__init__.py +0 -0
  128. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/output/citations.py +0 -0
  129. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/output/printing.py +0 -0
  130. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/output/status.py +0 -0
  131. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/pandas_utils.py +0 -0
  132. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/pydantic_utils.py +0 -0
  133. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/types.py +0 -0
  134. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/web/__init__.py +0 -0
  135. {langroid-0.18.1 → langroid-0.18.3}/langroid/utils/web/login.py +0 -0
  136. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/__init__.py +0 -0
  137. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/base.py +0 -0
  138. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/chromadb.py +0 -0
  139. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/lancedb.py +0 -0
  140. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/meilisearch.py +0 -0
  141. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/momento.py +0 -0
  142. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/qdrant_cloud.py +0 -0
  143. {langroid-0.18.1 → langroid-0.18.3}/langroid/vector_store/qdrantdb.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.18.1
3
+ Version: 0.18.3
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -170,7 +170,7 @@ blog post from the LanceDB team
170
170
  pharmacovigilance, see [blog post](https://langroid.github.io/langroid/blog/2024/08/12/malade-multi-agent-architecture-for-pharmacovigilance/)
171
171
 
172
172
 
173
- We welcome contributions -- See the [contributions](./CONTRIBUTING.md) document
173
+ We welcome contributions: See the [contributions](./CONTRIBUTING.md) document
174
174
  for ideas on what to contribute.
175
175
 
176
176
  Are you building LLM Applications, or want help with Langroid for your company,
@@ -61,7 +61,7 @@ blog post from the LanceDB team
61
61
  pharmacovigilance, see [blog post](https://langroid.github.io/langroid/blog/2024/08/12/malade-multi-agent-architecture-for-pharmacovigilance/)
62
62
 
63
63
 
64
- We welcome contributions -- See the [contributions](./CONTRIBUTING.md) document
64
+ We welcome contributions: See the [contributions](./CONTRIBUTING.md) document
65
65
  for ideas on what to contribute.
66
66
 
67
67
  Are you building LLM Applications, or want help with Langroid for your company,
@@ -91,6 +91,8 @@ class AgentConfig(BaseSettings):
91
91
  show_stats: bool = True # show token usage/cost stats?
92
92
  add_to_registry: bool = True # register agent in ObjectRegistry?
93
93
  respond_tools_only: bool = False # respond only to tool messages (not plain text)?
94
+ # allow multiple tool messages in a single response?
95
+ allow_multiple_tools: bool = True
94
96
 
95
97
  @validator("name")
96
98
  def check_name_alphanum(cls, v: str) -> str:
@@ -1120,6 +1122,8 @@ class Agent(ABC):
1120
1122
  # as a response to the tool message even though the tool was not intended
1121
1123
  # for this agent.
1122
1124
  return None
1125
+ if len(tools) > 1 and not self.config.allow_multiple_tools:
1126
+ return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
1123
1127
  if len(tools) == 0:
1124
1128
  fallback_result = self.handle_message_fallback(msg)
1125
1129
  if fallback_result is None:
@@ -3,12 +3,12 @@ from typing import Any, Dict, List, Optional
3
3
  from lxml import etree
4
4
 
5
5
  from langroid.agent.tool_message import ToolMessage
6
+ from langroid.pydantic_v1 import BaseModel
6
7
 
7
8
 
8
9
  class XMLToolMessage(ToolMessage):
9
10
  """
10
11
  Abstract class for tools formatted using XML instead of JSON.
11
- Mainly tested for non-nested tool structures.
12
12
 
13
13
  When a subclass defines a field with the attribute `verbatim=True`,
14
14
  instructions are sent to the LLM to ensure the field's content is:
@@ -50,8 +50,11 @@ class XMLToolMessage(ToolMessage):
50
50
  parser = etree.XMLParser(strip_cdata=False)
51
51
  root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)
52
52
 
53
- def parse_element(element: etree._Element) -> Any | Dict[str, Any] | str:
53
+ def parse_element(element: etree._Element) -> Any:
54
54
  # Skip elements starting with underscore
55
+ if element.tag.startswith("_"):
56
+ return {}
57
+
55
58
  field_info = cls.__fields__.get(element.tag)
56
59
  is_verbatim = field_info and field_info.field_info.extra.get(
57
60
  "verbatim", False
@@ -72,8 +75,18 @@ class XMLToolMessage(ToolMessage):
72
75
  # For non-code leaf elements, strip whitespace
73
76
  return element.text.strip() if element.text else ""
74
77
  else:
75
- # For branch elements, recurse
76
- return {child.tag: parse_element(child) for child in element}
78
+ # For branch elements, handle potential lists or nested structures
79
+ children = [parse_element(child) for child in element]
80
+ if all(child.tag == element[0].tag for child in element):
81
+ # If all children have the same tag, treat as a list
82
+ return children
83
+ else:
84
+ # Otherwise, treat as a dictionary
85
+ result = {child.tag: parse_element(child) for child in element}
86
+ # Check if this corresponds to a nested Pydantic model
87
+ if field_info and issubclass(field_info.type_, BaseModel):
88
+ return field_info.type_(**result)
89
+ return result
77
90
 
78
91
  result = parse_element(root)
79
92
  if not isinstance(result, dict):
@@ -100,6 +113,24 @@ class XMLToolMessage(ToolMessage):
100
113
  # Use Pydantic's parse_obj to create and validate the instance
101
114
  return cls.parse_obj(parsed_data)
102
115
 
116
+ @classmethod
117
+ def find_verbatim_fields(
118
+ cls, prefix: str = "", parent_cls: Optional["BaseModel"] = None
119
+ ) -> List[str]:
120
+ verbatim_fields = []
121
+ for field_name, field_info in (parent_cls or cls).__fields__.items():
122
+ full_name = f"{prefix}.{field_name}" if prefix else field_name
123
+ if (
124
+ field_info.field_info.extra.get("verbatim", False)
125
+ or field_name == "code"
126
+ ):
127
+ verbatim_fields.append(full_name)
128
+ if issubclass(field_info.type_, BaseModel):
129
+ verbatim_fields.extend(
130
+ cls.find_verbatim_fields(full_name, field_info.type_)
131
+ )
132
+ return verbatim_fields
133
+
103
134
  @classmethod
104
135
  def format_instructions(cls, tool: bool = False) -> str:
105
136
  """
@@ -123,14 +154,69 @@ class XMLToolMessage(ToolMessage):
123
154
  """
124
155
 
125
156
  preamble = "Placeholders:\n"
157
+ xml_format = f"Formatting example:\n\n<{cls.Config.root_element}>\n"
158
+
159
+ def format_field(
160
+ field_name: str,
161
+ field_type: type,
162
+ indent: str = "",
163
+ path: str = "",
164
+ ) -> None:
165
+ nonlocal preamble, xml_format
166
+ current_path = f"{path}.{field_name}" if path else field_name
167
+
168
+ if issubclass(field_type, BaseModel):
169
+ preamble += (
170
+ f"{field_name.upper()} = [nested structure for {field_name}]\n"
171
+ )
172
+ xml_format += f"{indent}<{field_name}>\n"
173
+ for sub_field, sub_field_info in field_type.__fields__.items():
174
+ format_field(
175
+ sub_field, sub_field_info.type_, indent + " ", current_path
176
+ )
177
+ xml_format += f"{indent}</{field_name}>\n"
178
+ elif issubclass(field_type, List):
179
+ item_type = getattr(field_type, "__args__", [Any])[0]
180
+ preamble += f"{field_name.upper()} = [list of {item_type.__name__}]\n"
181
+ xml_format += f"{indent}<{field_name}>\n"
182
+ xml_format += f"{indent} <item>[{item_type.__name__} value]</item>\n"
183
+ xml_format += f"{indent} ...\n"
184
+ xml_format += f"{indent}</{field_name}>\n"
185
+ elif issubclass(field_type, Dict):
186
+ key_type, value_type = getattr(field_type, "__args__", [Any, Any])
187
+ preamble += (
188
+ f"{field_name.upper()} = "
189
+ f"[dictionary with {key_type.__name__} keys and "
190
+ f"{value_type.__name__} values]\n"
191
+ )
192
+ xml_format += f"{indent}<{field_name}>\n"
193
+ xml_format += (
194
+ f"{indent} <{key_type.__name__}>"
195
+ f"[{value_type.__name__} value]"
196
+ f"</{key_type.__name__}>\n"
197
+ )
198
+ xml_format += f"{indent} ...\n"
199
+ xml_format += f"{indent}</{field_name}>\n"
200
+ else:
201
+ preamble += f"{field_name.upper()} = [value for {field_name}]\n"
202
+ if current_path in verbatim_fields:
203
+ xml_format += (
204
+ f"{indent}<{field_name}>"
205
+ f"<![CDATA[{{{field_name.upper()}}}]]></{field_name}>\n"
206
+ )
207
+ else:
208
+ xml_format += (
209
+ f"{indent}<{field_name}>"
210
+ f"{{{field_name.upper()}}}</{field_name}>\n"
211
+ )
212
+
213
+ verbatim_fields = cls.find_verbatim_fields()
214
+
126
215
  for field in fields:
127
- preamble += f"{field.upper()} = [value for {field}]\n"
216
+ field_type = cls.__fields__[field].type_
217
+ format_field(field, field_type)
128
218
 
129
- verbatim_fields = [
130
- field
131
- for field, field_info in cls.__fields__.items()
132
- if field_info.field_info.extra.get("verbatim", False)
133
- ]
219
+ xml_format += f"</{cls.Config.root_element}>"
134
220
 
135
221
  verbatim_alert = ""
136
222
  if len(verbatim_fields) > 0:
@@ -141,13 +227,6 @@ class XMLToolMessage(ToolMessage):
141
227
  must be written verbatim WITHOUT any modifications or escaping,
142
228
  such as spaces, tabs, indents, newlines, quotes, etc.
143
229
  """
144
- xml_format = f"Formatting example:\n\n<{cls.Config.root_element}>\n"
145
- for field in fields:
146
- if field == "code":
147
- xml_format += f" <{field}><![CDATA[{{{field.upper()}}}]]></{field}>\n"
148
- else:
149
- xml_format += f" <{field}>{{{field.upper()}}}</{field}>\n"
150
- xml_format += f"</{cls.Config.root_element}>"
151
230
 
152
231
  examples_str = ""
153
232
  if cls.examples():
@@ -177,17 +256,35 @@ class XMLToolMessage(ToolMessage):
177
256
  Raises:
178
257
  ValueError: If the result from etree.tostring is not a string.
179
258
  """
259
+
260
+ def create_element(
261
+ parent: etree._Element, name: str, value: Any, path: str = ""
262
+ ) -> None:
263
+ elem = etree.SubElement(parent, name)
264
+ current_path = f"{path}.{name}" if path else name
265
+
266
+ if isinstance(value, list):
267
+ for item in value:
268
+ create_element(elem, "item", item, current_path)
269
+ elif isinstance(value, dict):
270
+ for k, v in value.items():
271
+ create_element(elem, k, v, current_path)
272
+ elif isinstance(value, BaseModel):
273
+ # Handle nested Pydantic models
274
+ for field_name, field_value in value.dict().items():
275
+ create_element(elem, field_name, field_value, current_path)
276
+ else:
277
+ if current_path in self.__class__.find_verbatim_fields():
278
+ elem.text = etree.CDATA(str(value))
279
+ else:
280
+ elem.text = str(value)
281
+
180
282
  root = etree.Element(self.Config.root_element)
181
283
  exclude_fields = self.Config.schema_extra.get("exclude", set())
182
284
  for name, value in self.dict().items():
183
285
  if name not in exclude_fields:
184
- elem = etree.SubElement(root, name)
185
- field_info = self.__class__.__fields__[name]
186
- is_verbatim = field_info.field_info.extra.get("verbatim", False)
187
- if is_verbatim:
188
- elem.text = etree.CDATA(str(value))
189
- else:
190
- elem.text = str(value)
286
+ create_element(root, name, value)
287
+
191
288
  result = etree.tostring(root, encoding="unicode", pretty_print=True)
192
289
  if not isinstance(result, str):
193
290
  raise ValueError("Unexpected non-string result from etree.tostring")
@@ -127,8 +127,9 @@ def git_commit_file(repo: git.Repo, filepath: str, msg: str) -> None:
127
127
  """
128
128
  try:
129
129
  repo.index.add([filepath])
130
- repo.index.commit(f"{msg}; Updated {filepath}")
131
- logger.info(f"Successfully committed {filepath}")
130
+ commit_msg = msg or f"Updated {filepath}"
131
+ repo.index.commit(commit_msg)
132
+ logger.info(f"Successfully committed {filepath}: {commit_msg}")
132
133
  except git.GitCommandError as e:
133
134
  logger.error(f"An error occurred while committing: {e}")
134
135
 
@@ -10,7 +10,7 @@ import socket
10
10
  import traceback
11
11
  import uuid
12
12
  from pathlib import Path
13
- from typing import Any
13
+ from typing import Any, Literal
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
@@ -186,24 +186,48 @@ def generate_unique_id() -> str:
186
186
  return str(uuid.uuid4())
187
187
 
188
188
 
189
- def create_file(filepath: str | Path, content: str = "") -> None:
189
+ def create_file(
190
+ filepath: str | Path,
191
+ content: str = "",
192
+ if_exists: Literal["overwrite", "skip", "error", "append"] = "overwrite",
193
+ ) -> None:
190
194
  """
191
- Create a file with the given content in the specified directory.
195
+ Create, overwrite or append to a file, with the given content
196
+ at the specified filepath.
192
197
  If content is empty, it will simply touch to create an empty file.
193
198
 
194
199
  Args:
195
200
  filepath (str|Path): The relative path of the file to be created
196
201
  content (str): The content to be written to the file
202
+ if_exists (Literal["overwrite", "skip", "error", "append"]):
203
+ Action to take if file exists
197
204
  """
198
- Path(filepath).parent.mkdir(parents=True, exist_ok=True)
199
- if content == "":
200
- Path(filepath).touch()
205
+ filepath = Path(filepath)
206
+ filepath.parent.mkdir(parents=True, exist_ok=True)
207
+
208
+ if filepath.exists():
209
+ if if_exists == "skip":
210
+ logger.warning(f"File already exists, skipping: {filepath}")
211
+ return
212
+ elif if_exists == "error":
213
+ raise FileExistsError(f"File already exists: {filepath}")
214
+ elif if_exists == "append":
215
+ mode = "a"
216
+ else: # overwrite
217
+ mode = "w"
218
+ else:
219
+ mode = "w"
220
+
221
+ if content == "" and mode in ["a", "w"]:
222
+ filepath.touch()
223
+ logger.warning(f"Empty file created: {filepath}")
201
224
  else:
202
225
  # the newline = '\n` argument is used to ensure that
203
226
  # newlines in the content are written as actual line breaks
204
- with open(filepath, "w", newline="\n") as f:
227
+ with open(filepath, mode, newline="\n") as f:
205
228
  f.write(content)
206
- logger.warning(f"File created/updated: {filepath}")
229
+ action = "appended to" if mode == "a" else "created/updated in"
230
+ logger.warning(f"Content {action}: {filepath}")
207
231
 
208
232
 
209
233
  def read_file(path: str, line_numbers: bool = False) -> str:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.18.1"
3
+ version = "0.18.3"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"
@@ -163,6 +163,9 @@ pytest-asyncio = "^0.21.1"
163
163
  pytest-postgresql = "^5.0.0"
164
164
  pytest-mysql = "^2.4.2"
165
165
  coverage = "^7.2.5"
166
+ pytest-xdist = "^3.6.1"
167
+ pytest-timeout = "^2.3.1"
168
+ pytest-cov = "^5.0.0"
166
169
 
167
170
  [tool.poetry.group.docs]
168
171
  optional = true
File without changes
File without changes
File without changes
File without changes