langroid 0.31.2__py3-none-any.whl → 0.33.3__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 (163) hide show
  1. {langroid-0.31.2.dist-info → langroid-0.33.3.dist-info}/METADATA +150 -124
  2. langroid-0.33.3.dist-info/RECORD +7 -0
  3. {langroid-0.31.2.dist-info → langroid-0.33.3.dist-info}/WHEEL +1 -1
  4. langroid-0.33.3.dist-info/entry_points.txt +4 -0
  5. pyproject.toml +317 -212
  6. langroid/__init__.py +0 -106
  7. langroid/agent/.chainlit/config.toml +0 -121
  8. langroid/agent/.chainlit/translations/bn.json +0 -231
  9. langroid/agent/.chainlit/translations/en-US.json +0 -229
  10. langroid/agent/.chainlit/translations/gu.json +0 -231
  11. langroid/agent/.chainlit/translations/he-IL.json +0 -231
  12. langroid/agent/.chainlit/translations/hi.json +0 -231
  13. langroid/agent/.chainlit/translations/kn.json +0 -231
  14. langroid/agent/.chainlit/translations/ml.json +0 -231
  15. langroid/agent/.chainlit/translations/mr.json +0 -231
  16. langroid/agent/.chainlit/translations/ta.json +0 -231
  17. langroid/agent/.chainlit/translations/te.json +0 -231
  18. langroid/agent/.chainlit/translations/zh-CN.json +0 -229
  19. langroid/agent/__init__.py +0 -41
  20. langroid/agent/base.py +0 -1981
  21. langroid/agent/batch.py +0 -398
  22. langroid/agent/callbacks/__init__.py +0 -0
  23. langroid/agent/callbacks/chainlit.py +0 -598
  24. langroid/agent/chat_agent.py +0 -1899
  25. langroid/agent/chat_document.py +0 -454
  26. langroid/agent/helpers.py +0 -0
  27. langroid/agent/junk +0 -13
  28. langroid/agent/openai_assistant.py +0 -882
  29. langroid/agent/special/__init__.py +0 -59
  30. langroid/agent/special/arangodb/__init__.py +0 -0
  31. langroid/agent/special/arangodb/arangodb_agent.py +0 -656
  32. langroid/agent/special/arangodb/system_messages.py +0 -186
  33. langroid/agent/special/arangodb/tools.py +0 -107
  34. langroid/agent/special/arangodb/utils.py +0 -36
  35. langroid/agent/special/doc_chat_agent.py +0 -1466
  36. langroid/agent/special/lance_doc_chat_agent.py +0 -262
  37. langroid/agent/special/lance_rag/__init__.py +0 -9
  38. langroid/agent/special/lance_rag/critic_agent.py +0 -198
  39. langroid/agent/special/lance_rag/lance_rag_task.py +0 -82
  40. langroid/agent/special/lance_rag/query_planner_agent.py +0 -260
  41. langroid/agent/special/lance_tools.py +0 -61
  42. langroid/agent/special/neo4j/__init__.py +0 -0
  43. langroid/agent/special/neo4j/csv_kg_chat.py +0 -174
  44. langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -433
  45. langroid/agent/special/neo4j/system_messages.py +0 -120
  46. langroid/agent/special/neo4j/tools.py +0 -32
  47. langroid/agent/special/relevance_extractor_agent.py +0 -127
  48. langroid/agent/special/retriever_agent.py +0 -56
  49. langroid/agent/special/sql/__init__.py +0 -17
  50. langroid/agent/special/sql/sql_chat_agent.py +0 -654
  51. langroid/agent/special/sql/utils/__init__.py +0 -21
  52. langroid/agent/special/sql/utils/description_extractors.py +0 -190
  53. langroid/agent/special/sql/utils/populate_metadata.py +0 -85
  54. langroid/agent/special/sql/utils/system_message.py +0 -35
  55. langroid/agent/special/sql/utils/tools.py +0 -64
  56. langroid/agent/special/table_chat_agent.py +0 -263
  57. langroid/agent/structured_message.py +0 -9
  58. langroid/agent/task.py +0 -2093
  59. langroid/agent/tool_message.py +0 -393
  60. langroid/agent/tools/__init__.py +0 -38
  61. langroid/agent/tools/duckduckgo_search_tool.py +0 -50
  62. langroid/agent/tools/file_tools.py +0 -234
  63. langroid/agent/tools/google_search_tool.py +0 -39
  64. langroid/agent/tools/metaphor_search_tool.py +0 -67
  65. langroid/agent/tools/orchestration.py +0 -303
  66. langroid/agent/tools/recipient_tool.py +0 -235
  67. langroid/agent/tools/retrieval_tool.py +0 -32
  68. langroid/agent/tools/rewind_tool.py +0 -137
  69. langroid/agent/tools/segment_extract_tool.py +0 -41
  70. langroid/agent/typed_task.py +0 -19
  71. langroid/agent/xml_tool_message.py +0 -382
  72. langroid/agent_config.py +0 -0
  73. langroid/cachedb/__init__.py +0 -17
  74. langroid/cachedb/base.py +0 -58
  75. langroid/cachedb/momento_cachedb.py +0 -108
  76. langroid/cachedb/redis_cachedb.py +0 -153
  77. langroid/embedding_models/__init__.py +0 -39
  78. langroid/embedding_models/base.py +0 -74
  79. langroid/embedding_models/clustering.py +0 -189
  80. langroid/embedding_models/models.py +0 -461
  81. langroid/embedding_models/protoc/__init__.py +0 -0
  82. langroid/embedding_models/protoc/embeddings.proto +0 -19
  83. langroid/embedding_models/protoc/embeddings_pb2.py +0 -33
  84. langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -50
  85. langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -79
  86. langroid/embedding_models/remote_embeds.py +0 -153
  87. langroid/exceptions.py +0 -65
  88. langroid/experimental/team-save.py +0 -391
  89. langroid/language_models/.chainlit/config.toml +0 -121
  90. langroid/language_models/.chainlit/translations/en-US.json +0 -231
  91. langroid/language_models/__init__.py +0 -53
  92. langroid/language_models/azure_openai.py +0 -153
  93. langroid/language_models/base.py +0 -678
  94. langroid/language_models/config.py +0 -18
  95. langroid/language_models/mock_lm.py +0 -124
  96. langroid/language_models/openai_gpt.py +0 -1923
  97. langroid/language_models/prompt_formatter/__init__.py +0 -16
  98. langroid/language_models/prompt_formatter/base.py +0 -40
  99. langroid/language_models/prompt_formatter/hf_formatter.py +0 -132
  100. langroid/language_models/prompt_formatter/llama2_formatter.py +0 -75
  101. langroid/language_models/utils.py +0 -147
  102. langroid/mytypes.py +0 -84
  103. langroid/parsing/__init__.py +0 -52
  104. langroid/parsing/agent_chats.py +0 -38
  105. langroid/parsing/code-parsing.md +0 -86
  106. langroid/parsing/code_parser.py +0 -121
  107. langroid/parsing/config.py +0 -0
  108. langroid/parsing/document_parser.py +0 -718
  109. langroid/parsing/image_text.py +0 -32
  110. langroid/parsing/para_sentence_split.py +0 -62
  111. langroid/parsing/parse_json.py +0 -155
  112. langroid/parsing/parser.py +0 -313
  113. langroid/parsing/repo_loader.py +0 -790
  114. langroid/parsing/routing.py +0 -36
  115. langroid/parsing/search.py +0 -275
  116. langroid/parsing/spider.py +0 -102
  117. langroid/parsing/table_loader.py +0 -94
  118. langroid/parsing/url_loader.py +0 -111
  119. langroid/parsing/url_loader_cookies.py +0 -73
  120. langroid/parsing/urls.py +0 -273
  121. langroid/parsing/utils.py +0 -373
  122. langroid/parsing/web_search.py +0 -155
  123. langroid/prompts/__init__.py +0 -9
  124. langroid/prompts/chat-gpt4-system-prompt.md +0 -68
  125. langroid/prompts/dialog.py +0 -17
  126. langroid/prompts/prompts_config.py +0 -5
  127. langroid/prompts/templates.py +0 -141
  128. langroid/pydantic_v1/__init__.py +0 -10
  129. langroid/pydantic_v1/main.py +0 -4
  130. langroid/utils/.chainlit/config.toml +0 -121
  131. langroid/utils/.chainlit/translations/en-US.json +0 -231
  132. langroid/utils/__init__.py +0 -19
  133. langroid/utils/algorithms/__init__.py +0 -3
  134. langroid/utils/algorithms/graph.py +0 -103
  135. langroid/utils/configuration.py +0 -98
  136. langroid/utils/constants.py +0 -30
  137. langroid/utils/docker.py +0 -37
  138. langroid/utils/git_utils.py +0 -252
  139. langroid/utils/globals.py +0 -49
  140. langroid/utils/llms/__init__.py +0 -0
  141. langroid/utils/llms/strings.py +0 -8
  142. langroid/utils/logging.py +0 -135
  143. langroid/utils/object_registry.py +0 -66
  144. langroid/utils/output/__init__.py +0 -20
  145. langroid/utils/output/citations.py +0 -41
  146. langroid/utils/output/printing.py +0 -99
  147. langroid/utils/output/status.py +0 -40
  148. langroid/utils/pandas_utils.py +0 -30
  149. langroid/utils/pydantic_utils.py +0 -602
  150. langroid/utils/system.py +0 -286
  151. langroid/utils/types.py +0 -93
  152. langroid/utils/web/__init__.py +0 -0
  153. langroid/utils/web/login.py +0 -83
  154. langroid/vector_store/__init__.py +0 -50
  155. langroid/vector_store/base.py +0 -357
  156. langroid/vector_store/chromadb.py +0 -214
  157. langroid/vector_store/lancedb.py +0 -401
  158. langroid/vector_store/meilisearch.py +0 -299
  159. langroid/vector_store/momento.py +0 -278
  160. langroid/vector_store/qdrant_cloud.py +0 -6
  161. langroid/vector_store/qdrantdb.py +0 -468
  162. langroid-0.31.2.dist-info/RECORD +0 -162
  163. {langroid-0.31.2.dist-info → langroid-0.33.3.dist-info/licenses}/LICENSE +0 -0
@@ -1,598 +0,0 @@
1
- """
2
- Callbacks for Chainlit integration.
3
- """
4
-
5
- import json
6
- import logging
7
- import textwrap
8
- from typing import Any, Callable, Dict, List, Literal, Optional, no_type_check
9
-
10
- from langroid.exceptions import LangroidImportError
11
- from langroid.pydantic_v1 import BaseSettings
12
-
13
- try:
14
- import chainlit as cl
15
- except ImportError:
16
- raise LangroidImportError("chainlit", "chainlit")
17
-
18
- from chainlit import run_sync
19
- from chainlit.logger import logger
20
-
21
- import langroid as lr
22
- import langroid.language_models as lm
23
- from langroid.utils.configuration import settings
24
- from langroid.utils.constants import NO_ANSWER
25
-
26
- # Attempt to reconfigure the root logger to your desired settings
27
- log_level = logging.INFO if settings.debug else logging.WARNING
28
- logger.setLevel(log_level)
29
- logging.basicConfig(level=log_level)
30
-
31
- logging.getLogger().setLevel(log_level)
32
-
33
- USER_TIMEOUT = 60_000
34
- SYSTEM = "System 🖥️"
35
- LLM = "LLM 🧠"
36
- AGENT = "Agent <>"
37
- YOU = "You 😃"
38
- ERROR = "Error 🚫"
39
-
40
-
41
- @no_type_check
42
- async def ask_helper(func, **kwargs):
43
- res = await func(**kwargs).send()
44
- while not res:
45
- res = await func(**kwargs).send()
46
- return res
47
-
48
-
49
- @no_type_check
50
- async def setup_llm() -> None:
51
- """From the session `llm_settings`, create new LLMConfig and LLM objects,
52
- save them in session state."""
53
- llm_settings = cl.user_session.get("llm_settings", {})
54
- model = llm_settings.get("chat_model")
55
- context_length = llm_settings.get("context_length", 16_000)
56
- temperature = llm_settings.get("temperature", 0.2)
57
- timeout = llm_settings.get("timeout", 90)
58
- logger.info(f"Using model: {model}")
59
- llm_config = lm.OpenAIGPTConfig(
60
- chat_model=model or lm.OpenAIChatModel.GPT4o,
61
- # or, other possibilities for example:
62
- # "litellm/ollama_chat/mistral"
63
- # "litellm/ollama_chat/mistral:7b-instruct-v0.2-q8_0"
64
- # "litellm/ollama/llama2"
65
- # "local/localhost:8000/v1"
66
- # "local/localhost:8000"
67
- chat_context_length=context_length, # adjust based on model
68
- temperature=temperature,
69
- timeout=timeout,
70
- )
71
- llm = lm.OpenAIGPT(llm_config)
72
- cl.user_session.set("llm_config", llm_config)
73
- cl.user_session.set("llm", llm)
74
-
75
-
76
- @no_type_check
77
- async def update_llm(new_settings: Dict[str, Any]) -> None:
78
- """Update LLMConfig and LLM from settings, and save in session state."""
79
- cl.user_session.set("llm_settings", new_settings)
80
- await inform_llm_settings()
81
- await setup_llm()
82
-
83
-
84
- async def make_llm_settings_widgets(
85
- config: lm.OpenAIGPTConfig | None = None,
86
- ) -> None:
87
- config = config or lm.OpenAIGPTConfig()
88
- await cl.ChatSettings(
89
- [
90
- cl.input_widget.TextInput(
91
- id="chat_model",
92
- label="Model Name (Default GPT-4o)",
93
- initial="",
94
- placeholder="E.g. ollama/mistral or " "local/localhost:8000/v1",
95
- ),
96
- cl.input_widget.NumberInput(
97
- id="context_length",
98
- label="Chat Context Length",
99
- initial=config.chat_context_length,
100
- placeholder="E.g. 16000",
101
- ),
102
- cl.input_widget.Slider(
103
- id="temperature",
104
- label="LLM temperature",
105
- min=0.0,
106
- max=1.0,
107
- step=0.1,
108
- initial=config.temperature,
109
- tooltip="Adjust based on model",
110
- ),
111
- cl.input_widget.Slider(
112
- id="timeout",
113
- label="Timeout (seconds)",
114
- min=10,
115
- max=200,
116
- step=10,
117
- initial=config.timeout,
118
- tooltip="Timeout for LLM response, in seconds.",
119
- ),
120
- ]
121
- ).send() # type: ignore
122
-
123
-
124
- @no_type_check
125
- async def inform_llm_settings() -> None:
126
- llm_settings: Dict[str, Any] = cl.user_session.get("llm_settings", {})
127
- settings_dict = dict(
128
- model=llm_settings.get("chat_model"),
129
- context_length=llm_settings.get("context_length"),
130
- temperature=llm_settings.get("temperature"),
131
- timeout=llm_settings.get("timeout"),
132
- )
133
- await cl.Message(
134
- author=SYSTEM,
135
- content="LLM settings updated",
136
- elements=[
137
- cl.Text(
138
- name="settings",
139
- display="side",
140
- content=json.dumps(settings_dict, indent=4),
141
- language="json",
142
- )
143
- ],
144
- ).send()
145
-
146
-
147
- async def add_instructions(
148
- title: str = "Instructions",
149
- content: str = "Enter your question/response in the dialog box below.",
150
- display: Literal["side", "inline", "page"] = "inline",
151
- ) -> None:
152
- await cl.Message(
153
- author="",
154
- content=title if display == "side" else "",
155
- elements=[
156
- cl.Text(
157
- name=title,
158
- content=content,
159
- display=display,
160
- )
161
- ],
162
- ).send()
163
-
164
-
165
- async def add_image(
166
- path: str,
167
- name: str,
168
- display: Literal["side", "inline", "page"] = "inline",
169
- ) -> None:
170
- await cl.Message(
171
- author="",
172
- content=name if display == "side" else "",
173
- elements=[
174
- cl.Image(
175
- name=name,
176
- path=path,
177
- display=display,
178
- )
179
- ],
180
- ).send()
181
-
182
-
183
- async def get_text_files(
184
- message: cl.Message,
185
- extensions: List[str] = [".txt", ".pdf", ".doc", ".docx"],
186
- ) -> Dict[str, str]:
187
- """Get dict (file_name -> file_path) from files uploaded in chat msg"""
188
-
189
- files = [file for file in message.elements if file.path.endswith(tuple(extensions))]
190
- return {file.name: file.path for file in files}
191
-
192
-
193
- def wrap_text_preserving_structure(text: str, width: int = 90) -> str:
194
- """Wrap text preserving paragraph breaks. Typically used to
195
- format an agent_response output, which may have long lines
196
- with no newlines or paragraph breaks."""
197
-
198
- paragraphs = text.split("\n\n") # Split the text into paragraphs
199
- wrapped_text = []
200
-
201
- for para in paragraphs:
202
- if para.strip(): # If the paragraph is not just whitespace
203
- # Wrap this paragraph and add it to the result
204
- wrapped_paragraph = textwrap.fill(para, width=width)
205
- wrapped_text.append(wrapped_paragraph)
206
- else:
207
- # Preserve paragraph breaks
208
- wrapped_text.append("")
209
-
210
- return "\n\n".join(wrapped_text)
211
-
212
-
213
- class ChainlitCallbackConfig(BaseSettings):
214
- user_has_agent_name: bool = True # show agent name in front of "YOU" ?
215
- show_subtask_response: bool = True # show sub-task response as a step?
216
-
217
-
218
- class ChainlitAgentCallbacks:
219
- """Inject Chainlit callbacks into a Langroid Agent"""
220
-
221
- last_step: Optional[cl.Step] = None # used to display sub-steps under this
222
- curr_step: Optional[cl.Step] = None # used to update an initiated step
223
- stream: Optional[cl.Step] = None # pushed into openai_gpt.py to stream tokens
224
- parent_agent: Optional[lr.Agent] = None # used to get parent id, for step nesting
225
-
226
- def __init__(
227
- self,
228
- agent: lr.Agent,
229
- config: ChainlitCallbackConfig = ChainlitCallbackConfig(),
230
- ):
231
- """Add callbacks to the agent, and save the initial message,
232
- so we can alter the display of the first user message.
233
- """
234
- agent.callbacks.start_llm_stream = self.start_llm_stream
235
- agent.callbacks.start_llm_stream_async = self.start_llm_stream_async
236
- agent.callbacks.cancel_llm_stream = self.cancel_llm_stream
237
- agent.callbacks.finish_llm_stream = self.finish_llm_stream
238
- agent.callbacks.show_llm_response = self.show_llm_response
239
- agent.callbacks.show_agent_response = self.show_agent_response
240
- agent.callbacks.get_user_response = self.get_user_response
241
- agent.callbacks.get_user_response_async = self.get_user_response_async
242
- agent.callbacks.get_last_step = self.get_last_step
243
- agent.callbacks.set_parent_agent = self.set_parent_agent
244
- agent.callbacks.show_error_message = self.show_error_message
245
- agent.callbacks.show_start_response = self.show_start_response
246
- self.config = config
247
- self.agent: lr.Agent = agent
248
- if self.agent.llm is not None:
249
- # We don't want to suppress LLM output in async + streaming,
250
- # since we often use chainlit async callbacks to display LLM output
251
- self.agent.llm.config.async_stream_quiet = False
252
-
253
- def _get_parent_id(self) -> str | None:
254
- """Get step id under which we need to nest the current step:
255
- This should be the parent Agent's last_step.
256
- """
257
- if self.parent_agent is None:
258
- logger.info(f"No parent agent found for {self.agent.config.name}")
259
- return None
260
- logger.info(
261
- f"Parent agent found for {self.agent.config.name} = "
262
- f"{self.parent_agent.config.name}"
263
- )
264
- last_step = self.parent_agent.callbacks.get_last_step()
265
- if last_step is None:
266
- logger.info(f"No last step found for {self.parent_agent.config.name}")
267
- return None
268
- logger.info(
269
- f"Last step found for {self.parent_agent.config.name} = {last_step.id}"
270
- )
271
- return last_step.id # type: ignore
272
-
273
- def set_parent_agent(self, parent: lr.Agent) -> None:
274
- self.parent_agent = parent
275
-
276
- def get_last_step(self) -> Optional[cl.Step]:
277
- return self.last_step
278
-
279
- def start_llm_stream(self) -> Callable[[str], None]:
280
- """Returns a streaming fn that can be passed to the LLM class"""
281
- self.stream = cl.Message(
282
- content="",
283
- id=self.curr_step.id if self.curr_step is not None else None,
284
- author=self._entity_name("llm"),
285
- type="assistant_message",
286
- parent_id=self._get_parent_id(),
287
- )
288
- self.last_step = self.stream
289
- self.curr_step = None
290
- logger.info(
291
- f"""
292
- Starting LLM stream for {self.agent.config.name}
293
- id = {self.stream.id}
294
- under parent {self._get_parent_id()}
295
- """
296
- )
297
-
298
- def stream_token(t: str) -> None:
299
- if self.stream is None:
300
- raise ValueError("Stream not initialized")
301
- run_sync(self.stream.stream_token(t))
302
-
303
- return stream_token
304
-
305
- async def start_llm_stream_async(self) -> Callable[[str], None]:
306
- """Returns a streaming fn that can be passed to the LLM class"""
307
- self.stream = cl.Message(
308
- content="",
309
- id=self.curr_step.id if self.curr_step is not None else None,
310
- author=self._entity_name("llm"),
311
- type="assistant_message",
312
- parent_id=self._get_parent_id(),
313
- )
314
- self.last_step = self.stream
315
- self.curr_step = None
316
- logger.info(
317
- f"""
318
- Starting LLM stream for {self.agent.config.name}
319
- id = {self.stream.id}
320
- under parent {self._get_parent_id()}
321
- """
322
- )
323
-
324
- async def stream_token(t: str) -> None:
325
- if self.stream is None:
326
- raise ValueError("Stream not initialized")
327
- await self.stream.stream_token(t)
328
-
329
- return stream_token
330
-
331
- def cancel_llm_stream(self) -> None:
332
- """Called when cached response found."""
333
- self.last_step = None
334
- if self.stream is not None:
335
- run_sync(self.stream.remove()) # type: ignore
336
-
337
- def finish_llm_stream(self, content: str, is_tool: bool = False) -> None:
338
- """Update the stream, and display entire response in the right language."""
339
- if self.agent.llm is None or self.stream is None:
340
- raise ValueError("LLM or stream not initialized")
341
- if content == "":
342
- run_sync(self.stream.remove()) # type: ignore
343
- else:
344
- run_sync(self.stream.update()) # type: ignore
345
- stream_id = self.stream.id if content else None
346
- step = cl.Message(
347
- content=textwrap.dedent(content) or NO_ANSWER,
348
- id=stream_id,
349
- author=self._entity_name("llm", tool=is_tool),
350
- type="assistant_message",
351
- parent_id=self._get_parent_id(),
352
- language="json" if is_tool else None,
353
- )
354
- logger.info(
355
- f"""
356
- Finish STREAM LLM response for {self.agent.config.name}
357
- id = {step.id}
358
- under parent {self._get_parent_id()}
359
- """
360
- )
361
- run_sync(step.update()) # type: ignore
362
-
363
- def show_llm_response(
364
- self,
365
- content: str,
366
- is_tool: bool = False,
367
- cached: bool = False,
368
- language: str | None = None,
369
- ) -> None:
370
- """Show non-streaming LLM response."""
371
- step = cl.Message(
372
- content=textwrap.dedent(content) or NO_ANSWER,
373
- id=self.curr_step.id if self.curr_step is not None else None,
374
- author=self._entity_name("llm", tool=is_tool, cached=cached),
375
- type="assistant_message",
376
- language=language or ("json" if is_tool else None),
377
- parent_id=self._get_parent_id(),
378
- )
379
- self.last_step = step
380
- self.curr_step = None
381
- logger.info(
382
- f"""
383
- Showing NON-STREAM LLM response for {self.agent.config.name}
384
- id = {step.id}
385
- under parent {self._get_parent_id()}
386
- """
387
- )
388
- run_sync(step.send()) # type: ignore
389
-
390
- def show_error_message(self, error: str) -> None:
391
- """Show error message."""
392
- step = cl.Message(
393
- content=error,
394
- author=self.agent.config.name + f"({ERROR})",
395
- type="run",
396
- language="text",
397
- parent_id=self._get_parent_id(),
398
- )
399
- self.last_step = step
400
- run_sync(step.send())
401
-
402
- def show_agent_response(self, content: str, language="text") -> None:
403
- """Show message from agent (typically tool handler)."""
404
- if language == "text":
405
- content = wrap_text_preserving_structure(content, width=90)
406
- step = cl.Message(
407
- content=content,
408
- id=self.curr_step.id if self.curr_step is not None else None,
409
- author=self._entity_name("agent"),
410
- type="tool",
411
- language=language,
412
- parent_id=self._get_parent_id(),
413
- )
414
- self.last_step = step
415
- self.curr_step = None
416
- logger.info(
417
- f"""
418
- Showing AGENT response for {self.agent.config.name}
419
- id = {step.id}
420
- under parent {self._get_parent_id()}
421
- """
422
- )
423
- run_sync(step.send()) # type: ignore
424
-
425
- def show_start_response(self, entity: str) -> None:
426
- """When there's a potentially long-running process, start a step,
427
- so that the UI displays a spinner while the process is running."""
428
- if self.curr_step is not None:
429
- run_sync(self.curr_step.remove()) # type: ignore
430
- step = cl.Message(
431
- content="",
432
- author=self._entity_name(entity),
433
- type="run",
434
- parent_id=self._get_parent_id(),
435
- language="text",
436
- )
437
- self.last_step = step
438
- self.curr_step = step
439
- logger.info(
440
- f"""
441
- Showing START response for {self.agent.config.name} ({entity})
442
- id = {step.id}
443
- under parent {self._get_parent_id()}
444
- """
445
- )
446
- run_sync(step.send()) # type: ignore
447
-
448
- def _entity_name(
449
- self, entity: str, tool: bool = False, cached: bool = False
450
- ) -> str:
451
- """Construct name of entity to display as Author of a step"""
452
- tool_indicator = " => 🛠️" if tool else ""
453
- cached = "(cached)" if cached else ""
454
- match entity:
455
- case "llm":
456
- model = self.agent.config.llm.chat_model
457
- return (
458
- self.agent.config.name + f"({LLM} {model} {tool_indicator}){cached}"
459
- )
460
- case "agent":
461
- return self.agent.config.name + f"({AGENT})"
462
- case "user":
463
- if self.config.user_has_agent_name:
464
- return self.agent.config.name + f"({YOU})"
465
- else:
466
- return YOU
467
- case _:
468
- return self.agent.config.name + f"({entity})"
469
-
470
- def _get_user_response_buttons(self, prompt: str) -> str:
471
- """Not used. Save for future reference"""
472
- res = run_sync(
473
- ask_helper(
474
- cl.AskActionMessage,
475
- content="Continue, exit or say something?",
476
- actions=[
477
- cl.Action(
478
- name="continue",
479
- value="continue",
480
- label="✅ Continue",
481
- ),
482
- cl.Action(
483
- name="feedback",
484
- value="feedback",
485
- label="💬 Say something",
486
- ),
487
- cl.Action(name="exit", value="exit", label="🔚 Exit Conversation"),
488
- ],
489
- )
490
- )
491
- if res.get("value") == "continue":
492
- return ""
493
- if res.get("value") == "exit":
494
- return "x"
495
- if res.get("value") == "feedback":
496
- return self.get_user_response(prompt)
497
- return "" # process the "feedback" case here
498
-
499
- def get_user_response(self, prompt: str) -> str:
500
- """Ask for user response, wait for it, and return it"""
501
-
502
- return run_sync(self.ask_user(prompt=prompt, suppress_values=["c"]))
503
-
504
- async def get_user_response_async(self, prompt: str) -> str:
505
- """Ask for user response, wait for it, and return it"""
506
-
507
- return await self.ask_user(prompt=prompt, suppress_values=["c"])
508
-
509
- async def ask_user(
510
- self,
511
- prompt: str,
512
- timeout: int = USER_TIMEOUT,
513
- suppress_values: List[str] = ["c"],
514
- ) -> str:
515
- """
516
- Ask user for input.
517
-
518
- Args:
519
- prompt (str): Prompt to display to user
520
- timeout (int): Timeout in seconds
521
- suppress_values (List[str]): List of values to suppress from display
522
- (e.g. "c" for continue)
523
-
524
- Returns:
525
- str: User response
526
- """
527
- ask_msg = cl.AskUserMessage(
528
- content=prompt,
529
- author=f"{self.agent.config.name}(Awaiting user input...)",
530
- type="assistant_message",
531
- timeout=timeout,
532
- )
533
- res = await ask_msg.send()
534
- if prompt == "":
535
- # if there was no actual prompt, clear the row from the UI for clarity.
536
- await ask_msg.remove()
537
-
538
- if res is None:
539
- run_sync(
540
- cl.Message(
541
- content=f"Timed out after {USER_TIMEOUT} seconds. Exiting."
542
- ).send()
543
- )
544
- return "x"
545
-
546
- # Finally, reproduce the user response at right nesting level
547
- if res["output"] in suppress_values:
548
- return ""
549
-
550
- return res["output"]
551
-
552
-
553
- class ChainlitTaskCallbacks(ChainlitAgentCallbacks):
554
- """
555
- Recursively inject ChainlitAgentCallbacks into a Langroid Task's agent and
556
- agents of sub-tasks.
557
- """
558
-
559
- def __init__(
560
- self,
561
- task: lr.Task,
562
- config: ChainlitCallbackConfig = ChainlitCallbackConfig(),
563
- ):
564
- """Inject callbacks recursively, ensuring msg is passed to the
565
- top-level agent"""
566
-
567
- super().__init__(task.agent, config)
568
- self._inject_callbacks(task)
569
- self.task = task
570
- if config.show_subtask_response:
571
- self.task.callbacks.show_subtask_response = self.show_subtask_response
572
-
573
- @classmethod
574
- def _inject_callbacks(
575
- cls, task: lr.Task, config: ChainlitCallbackConfig = ChainlitCallbackConfig()
576
- ) -> None:
577
- # recursively apply ChainlitAgentCallbacks to agents of sub-tasks
578
- for t in task.sub_tasks:
579
- cls(t, config=config)
580
- # ChainlitTaskCallbacks(t, config=config)
581
-
582
- def show_subtask_response(
583
- self, task: lr.Task, content: str, is_tool: bool = False
584
- ) -> None:
585
- """Show sub-task response as a step, nested at the right level."""
586
-
587
- # The step should nest under the calling agent's last step
588
- step = cl.Message(
589
- content=content or NO_ANSWER,
590
- author=(
591
- self.task.agent.config.name + f"( ⏎ From {task.agent.config.name})"
592
- ),
593
- type="run",
594
- parent_id=self._get_parent_id(),
595
- language="json" if is_tool else None,
596
- )
597
- self.last_step = step
598
- run_sync(step.send())