agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.1.6__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 (90) hide show
  1. agentscope_runtime/engine/agents/agentscope_agent.py +447 -0
  2. agentscope_runtime/engine/agents/agno_agent.py +19 -18
  3. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  4. agentscope_runtime/engine/agents/utils.py +53 -0
  5. agentscope_runtime/engine/deployers/__init__.py +0 -13
  6. agentscope_runtime/engine/deployers/local_deployer.py +501 -356
  7. agentscope_runtime/engine/helpers/helper.py +60 -41
  8. agentscope_runtime/engine/runner.py +11 -36
  9. agentscope_runtime/engine/schemas/agent_schemas.py +2 -70
  10. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  11. agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
  12. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  13. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  14. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  15. agentscope_runtime/sandbox/__init__.py +2 -0
  16. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  17. agentscope_runtime/sandbox/box/base/base_sandbox.py +4 -3
  18. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  19. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +8 -13
  20. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  21. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  22. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +8 -6
  23. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  24. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +80 -0
  25. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  26. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  27. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  28. agentscope_runtime/sandbox/box/training_box/training_box.py +10 -15
  29. agentscope_runtime/sandbox/build.py +143 -58
  30. agentscope_runtime/sandbox/client/http_client.py +43 -49
  31. agentscope_runtime/sandbox/client/training_client.py +0 -1
  32. agentscope_runtime/sandbox/constant.py +24 -1
  33. agentscope_runtime/sandbox/custom/custom_sandbox.py +5 -5
  34. agentscope_runtime/sandbox/custom/example.py +2 -2
  35. agentscope_runtime/sandbox/enums.py +1 -0
  36. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +11 -6
  37. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +25 -9
  38. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  39. agentscope_runtime/sandbox/manager/container_clients/agentrun_client.py +1098 -0
  40. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +33 -205
  41. agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +8 -555
  42. agentscope_runtime/sandbox/manager/sandbox_manager.py +187 -88
  43. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  44. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  45. agentscope_runtime/sandbox/model/container.py +6 -23
  46. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  47. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  48. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  49. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  50. agentscope_runtime/sandbox/utils.py +124 -0
  51. agentscope_runtime/version.py +1 -1
  52. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/METADATA +168 -77
  53. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/RECORD +59 -78
  54. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/entry_points.txt +0 -1
  55. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  56. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  57. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  58. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  59. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +0 -2886
  60. agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +0 -51
  61. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +0 -314
  62. agentscope_runtime/engine/deployers/cli_fc_deploy.py +0 -184
  63. agentscope_runtime/engine/deployers/kubernetes_deployer.py +0 -265
  64. agentscope_runtime/engine/deployers/modelstudio_deployer.py +0 -677
  65. agentscope_runtime/engine/deployers/utils/deployment_modes.py +0 -14
  66. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +0 -8
  67. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +0 -429
  68. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +0 -240
  69. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +0 -297
  70. agentscope_runtime/engine/deployers/utils/package_project_utils.py +0 -932
  71. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +0 -9
  72. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +0 -504
  73. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +0 -157
  74. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +0 -268
  75. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +0 -75
  76. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +0 -220
  77. agentscope_runtime/engine/deployers/utils/wheel_packager.py +0 -389
  78. agentscope_runtime/engine/helpers/agent_api_builder.py +0 -651
  79. agentscope_runtime/engine/llms/__init__.py +0 -3
  80. agentscope_runtime/engine/llms/base_llm.py +0 -60
  81. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  82. agentscope_runtime/engine/schemas/embedding.py +0 -37
  83. agentscope_runtime/engine/schemas/modelstudio_llm.py +0 -310
  84. agentscope_runtime/engine/schemas/oai_llm.py +0 -538
  85. agentscope_runtime/engine/schemas/realtime.py +0 -254
  86. /agentscope_runtime/engine/{deployers/adapter/responses → services/utils}/__init__.py +0 -0
  87. /agentscope_runtime/{engine/deployers/utils → sandbox/box/gui/box}/__init__.py +0 -0
  88. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/WHEEL +0 -0
  89. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/licenses/LICENSE +0 -0
  90. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/top_level.txt +0 -0
@@ -1,401 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # pylint:disable=too-many-nested-blocks, too-many-branches, too-many-statements
3
- # pylint:disable=line-too-long, protected-access
4
-
5
- import json
6
- import threading
7
- import uuid
8
- from functools import partial
9
- from typing import Optional, Type
10
-
11
- from agentscope import setup_logger
12
- from agentscope.agent import ReActAgent
13
- from agentscope.formatter import (
14
- FormatterBase,
15
- DashScopeChatFormatter,
16
- OpenAIChatFormatter,
17
- AnthropicChatFormatter,
18
- OllamaChatFormatter,
19
- GeminiChatFormatter,
20
- )
21
- from agentscope.memory import InMemoryMemory
22
- from agentscope.message import Msg, ToolUseBlock, ToolResultBlock
23
- from agentscope.model import (
24
- ChatModelBase,
25
- DashScopeChatModel,
26
- OpenAIChatModel,
27
- AnthropicChatModel,
28
- OllamaChatModel,
29
- GeminiChatModel,
30
- )
31
- from agentscope.tool import (
32
- Toolkit,
33
- ToolResponse,
34
- )
35
- from agentscope.tool._toolkit import RegisteredToolFunction
36
-
37
- from .hooks import (
38
- pre_speak_msg_buffer_hook,
39
- get_msg_instances,
40
- clear_msg_instances,
41
- run_async_in_thread,
42
- )
43
- from ...agents import Agent
44
- from ...schemas.agent_schemas import (
45
- Message,
46
- TextContent,
47
- DataContent,
48
- FunctionCall,
49
- FunctionCallOutput,
50
- MessageType,
51
- )
52
- from ...schemas.context import Context
53
-
54
- # Disable logging from agentscope
55
- setup_logger(level="CRITICAL")
56
-
57
-
58
- class AgentScopeContextAdapter:
59
- def __init__(self, context: Context, attr: dict):
60
- self.context = context
61
- self.attr = attr
62
-
63
- # Adapted attribute
64
- self.toolkit = None
65
- self.model = None
66
- self.memory = None
67
- self.new_message = None
68
-
69
- async def initialize(self):
70
- self.model, self.formatter = await self.adapt_model()
71
- self.memory = await self.adapt_memory()
72
- self.new_message = await self.adapt_new_message()
73
- self.toolkit = await self.adapt_tools()
74
-
75
- async def adapt_memory(self):
76
- memory = self.attr["agent_config"].get("memory", InMemoryMemory())
77
- messages = []
78
-
79
- # Build context
80
- for msg in self.context.session.messages[:-1]: # Exclude the last one
81
- messages.append(AgentScopeContextAdapter.converter(msg))
82
-
83
- memory.load_state_dict({"content": messages})
84
-
85
- return memory
86
-
87
- @staticmethod
88
- def converter(message: Message):
89
- # TODO: support more message type
90
- if message.role not in ["user", "system", "assistant"]:
91
- role_label = "user"
92
- else:
93
- role_label = message.role
94
- result = {
95
- "name": message.role,
96
- "role": role_label,
97
- }
98
- if message.type == MessageType.PLUGIN_CALL:
99
- result["content"] = [
100
- ToolUseBlock(
101
- type="tool_use",
102
- id=message.content[0].data["call_id"],
103
- name=message.role,
104
- input=json.loads(message.content[0].data["arguments"]),
105
- ),
106
- ]
107
- elif message.type == MessageType.PLUGIN_CALL_OUTPUT:
108
- result["content"] = [
109
- ToolResultBlock(
110
- type="tool_result",
111
- id=message.content[0].data["call_id"],
112
- name=message.role,
113
- output=message.content[0].data["output"],
114
- ),
115
- ]
116
- else:
117
- result["content"] = (
118
- message.content[0].text if message.content else ""
119
- )
120
- return result
121
-
122
- async def adapt_new_message(self):
123
- last_message = self.context.session.messages[-1]
124
- return Msg(**AgentScopeContextAdapter.converter(last_message))
125
-
126
- async def adapt_model(self):
127
- model = self.attr["model"]
128
- formatter = self.attr["agent_config"].get("formatter")
129
- if formatter and isinstance(formatter, FormatterBase):
130
- return model, formatter
131
-
132
- if isinstance(model, OpenAIChatModel):
133
- formatter = OpenAIChatFormatter()
134
- elif isinstance(model, DashScopeChatModel):
135
- formatter = DashScopeChatFormatter()
136
- elif isinstance(model, AnthropicChatModel):
137
- formatter = AnthropicChatFormatter()
138
- elif isinstance(model, OllamaChatModel):
139
- formatter = OllamaChatFormatter()
140
- elif isinstance(model, GeminiChatModel):
141
- formatter = GeminiChatFormatter()
142
-
143
- return model, formatter
144
-
145
- async def adapt_tools(self):
146
- def func_wrapper(func, **kwargs):
147
- func_res = func(**kwargs)
148
- return ToolResponse(
149
- content=func_res["content"],
150
- )
151
-
152
- toolkit = self.attr["agent_config"].get("toolkit", Toolkit())
153
- tools = self.attr["tools"]
154
-
155
- # in case, tools is None and tools == []
156
- if not tools:
157
- return toolkit
158
-
159
- if self.context.activate_tools:
160
- # Only add activated tool
161
- activated_tools = self.context.activate_tools
162
- else:
163
- # Lazy import
164
- from ....sandbox.tools.utils import setup_tools
165
-
166
- activated_tools = setup_tools(
167
- tools=self.attr["tools"],
168
- environment_manager=self.context.environment_manager,
169
- session_id=self.context.session.id,
170
- user_id=self.context.session.user_id,
171
- include_schemas=False,
172
- )
173
-
174
- for tool in activated_tools:
175
- function = RegisteredToolFunction(
176
- name=tool.name,
177
- source="mcp_server",
178
- mcp_name=tool.tool_type,
179
- original_func=partial(
180
- func_wrapper,
181
- tool,
182
- ),
183
- json_schema=tool.schema,
184
- group="basic",
185
- )
186
- toolkit.tools[tool.name] = function
187
-
188
- return toolkit
189
-
190
-
191
- class AgentScopeAgent(Agent):
192
- def __init__(
193
- self,
194
- name: str,
195
- model: ChatModelBase,
196
- tools=None,
197
- agent_config=None,
198
- agent_builder: Optional[Type[ReActAgent]] = ReActAgent,
199
- ):
200
- super().__init__(name=name, agent_config=agent_config)
201
- assert isinstance(
202
- model,
203
- ChatModelBase,
204
- ), "model must be a subclass of ChatModelBase in AgentScope"
205
-
206
- # Set default agent_builder
207
- if agent_builder is None:
208
- agent_builder = ReActAgent
209
-
210
- assert issubclass(
211
- agent_builder,
212
- ReActAgent,
213
- ), "agent_builder must be a subclass of AgentBase in AgentScope"
214
-
215
- # Replace name if not exists
216
- self.agent_config["name"] = self.agent_config.get("name") or name
217
-
218
- self._attr = {
219
- "model": model,
220
- "tools": tools,
221
- "agent_config": self.agent_config,
222
- "agent_builder": agent_builder,
223
- }
224
- self._agent = None
225
- self.tools = tools
226
-
227
- def copy(self) -> "AgentScopeAgent":
228
- return AgentScopeAgent(**self._attr)
229
-
230
- def build(self, as_context):
231
- self._agent = self._attr["agent_builder"](
232
- **self._attr["agent_config"],
233
- model=as_context.model,
234
- formatter=as_context.formatter,
235
- memory=as_context.memory,
236
- toolkit=as_context.toolkit,
237
- )
238
- self._agent._disable_console_output = True
239
-
240
- self._agent.register_instance_hook(
241
- "pre_print",
242
- "pre_speak_msg_buffer_hook",
243
- pre_speak_msg_buffer_hook,
244
- )
245
-
246
- return self._agent
247
-
248
- async def run(self, context):
249
- as_context = AgentScopeContextAdapter(context=context, attr=self._attr)
250
- await as_context.initialize()
251
- local_truncate_memory = ""
252
-
253
- # We should always build a new agent since the state is manage outside
254
- # the agent
255
- self._agent = self.build(as_context)
256
-
257
- # Make the output a generator
258
- thread_id = "pipeline" + str(uuid.uuid4())
259
- try:
260
- # Run the main function in a separate thread
261
- thread = threading.Thread(
262
- target=run_async_in_thread,
263
- args=(self._agent.reply(msg=as_context.new_message),),
264
- name=thread_id,
265
- )
266
- clear_msg_instances(thread_id=thread_id)
267
- thread.start()
268
-
269
- # Yield new Msg instances as they are logged
270
- last_content = ""
271
-
272
- message = Message(type=MessageType.MESSAGE, role="assistant")
273
- yield message.in_progress()
274
- index = None
275
-
276
- for msg, msg_len in get_msg_instances(thread_id=thread_id):
277
- if msg:
278
- content = msg.content
279
- if isinstance(content, str):
280
- last_content = content
281
- else:
282
- for element in content:
283
- if isinstance(element, str) and element:
284
- text_delta_content = TextContent(
285
- delta=True,
286
- index=index,
287
- text=element,
288
- )
289
- text_delta_content = message.add_delta_content(
290
- new_content=text_delta_content,
291
- )
292
- index = text_delta_content.index
293
- yield text_delta_content
294
- elif isinstance(element, dict):
295
- if element.get("type") == "text":
296
- text = element.get(
297
- "text",
298
- "",
299
- )
300
- if text:
301
- text_delta_content = TextContent(
302
- delta=True,
303
- index=index,
304
- text=text.removeprefix(
305
- local_truncate_memory,
306
- ),
307
- )
308
- local_truncate_memory = element.get(
309
- "text",
310
- "",
311
- )
312
- text_delta_content = (
313
- message.add_delta_content(
314
- new_content=text_delta_content,
315
- )
316
- )
317
- index = text_delta_content.index
318
- yield text_delta_content
319
- if hasattr(msg, "is_last"):
320
- yield message.content_completed(
321
- index,
322
- )
323
- yield message.completed()
324
- message = Message(
325
- type=MessageType.MESSAGE,
326
- role="assistant",
327
- )
328
- index = None
329
-
330
- elif element.get("type") == "tool_use":
331
- json_str = json.dumps(element.get("input"))
332
- data_delta_content = DataContent(
333
- index=index,
334
- data=FunctionCall(
335
- call_id=element.get("id"),
336
- name=element.get("name"),
337
- arguments=json_str,
338
- ).model_dump(),
339
- )
340
- plugin_call_message = Message(
341
- type=MessageType.PLUGIN_CALL,
342
- role="assistant",
343
- content=[data_delta_content],
344
- )
345
- yield plugin_call_message.completed()
346
- elif element.get("type") == "tool_result":
347
- data_delta_content = DataContent(
348
- index=index,
349
- data=FunctionCallOutput(
350
- call_id=element.get("id"),
351
- output=str(element.get("output")),
352
- ).model_dump(),
353
- )
354
- plugin_output_message = Message(
355
- type=MessageType.PLUGIN_CALL_OUTPUT,
356
- role="assistant",
357
- content=[data_delta_content],
358
- )
359
- yield plugin_output_message.completed()
360
- else:
361
- text_delta_content = TextContent(
362
- delta=True,
363
- index=index,
364
- text=f"{element}",
365
- )
366
- text_delta_content = (
367
- message.add_delta_content(
368
- new_content=text_delta_content,
369
- )
370
- )
371
- index = text_delta_content.index
372
- yield text_delta_content
373
-
374
- # Break if the thread is dead and no more messages are expected
375
- if not thread.is_alive() and msg_len == 0:
376
- break
377
-
378
- if last_content:
379
- text_delta_content = TextContent(
380
- delta=True,
381
- index=index,
382
- text=last_content,
383
- )
384
- text_delta_content = message.add_delta_content(
385
- new_content=text_delta_content,
386
- )
387
- yield text_delta_content
388
- yield message.completed()
389
-
390
- # Wait for the function to finish
391
- thread.join()
392
- finally:
393
- pass
394
-
395
- async def run_async(
396
- self,
397
- context,
398
- **kwargs,
399
- ):
400
- async for event in self.run(context):
401
- yield event
@@ -1,169 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """ Hooks for stream output """
3
- # pylint: disable=unused-argument,too-many-nested-blocks
4
- import asyncio
5
- import os
6
- import time
7
- import threading
8
- import logging
9
-
10
- from collections import defaultdict
11
- from typing import Union, Optional, Generator, Any, List
12
-
13
- from agentscope.agent import AgentBase
14
- from agentscope.message import Msg
15
-
16
- _MSG_INSTANCE = defaultdict(list)
17
- _LOCKS = defaultdict(threading.Lock)
18
- TIMEOUT = int(os.getenv("AGENTSCOPE_AGENT_TIMEOUT", "30"))
19
-
20
-
21
- def run_async_in_thread(coro):
22
- """
23
- Run an async coroutine in a thread-safe manner.
24
- """
25
- try:
26
- return asyncio.run(coro)
27
- except RuntimeError as e:
28
- if "cannot be called from a running event loop" in str(e):
29
- # If we're somehow in a context with an existing event loop,
30
- # fall back to the manual approach with better error handling.
31
- return _run_with_manual_loop(coro)
32
- else:
33
- logging.error(f"Runtime error in async thread: {e}")
34
- return None
35
- except Exception as e:
36
- logging.error(f"Error in async thread: {e}")
37
- return None
38
-
39
-
40
- def _run_with_manual_loop(coro):
41
- """Fallback method using manual event loop management.
42
-
43
- This is only used when asyncio.run cannot be used due to
44
- an existing event loop in the current context.
45
- """
46
- loop = None
47
- try:
48
- loop = asyncio.new_event_loop()
49
- return loop.run_until_complete(coro)
50
- finally:
51
- if loop:
52
- try:
53
- # Cancel all remaining tasks with better error handling
54
- pending_tasks = asyncio.all_tasks(loop)
55
- if pending_tasks:
56
- for task in pending_tasks:
57
- if not task.done():
58
- task.cancel()
59
- # Wait for cancellation to complete
60
- try:
61
- loop.run_until_complete(
62
- asyncio.gather(
63
- *pending_tasks,
64
- return_exceptions=True,
65
- ),
66
- )
67
- except Exception:
68
- pass # Ignore exceptions during cleanup
69
- except Exception as e:
70
- logging.error(f"Error during task cleanup: {e}")
71
- finally:
72
- try:
73
- loop.close()
74
- except Exception as e:
75
- logging.error(f"Error closing event loop: {e}")
76
-
77
-
78
- def pre_speak_msg_buffer_hook(
79
- self: AgentBase,
80
- kwargs: dict[str, Any],
81
- ) -> Union[Msg, None]:
82
- """Hook for pre speak msg buffer"""
83
- msg = kwargs["msg"]
84
- thread_id = threading.current_thread().name
85
- if thread_id.startswith("pipeline"):
86
- with _LOCKS[thread_id]:
87
- if kwargs.get("last", True):
88
- msg.is_last = True
89
- _MSG_INSTANCE[thread_id].append(msg)
90
- else:
91
- new_blocks = []
92
- if isinstance(msg.content, List):
93
- for block in msg.content:
94
- if block.get("type", "") != "tool_use":
95
- new_blocks.append(block)
96
- msg.content = new_blocks
97
- if msg.content:
98
- _MSG_INSTANCE[thread_id].append(msg)
99
-
100
- return kwargs
101
-
102
-
103
- def clear_msg_instances(thread_id: Optional[str] = None) -> None:
104
- """
105
- Clears all message instances for a specific thread ID.
106
- This function removes all message instances associated with a given
107
- thread ID (`thread_id`). It ensures thread safety through the use of a
108
- threading lock when accessing the shared message instance list. This
109
- prevents race conditions in concurrent environments.
110
- Args:
111
- thread_id (optional): The thread ID for which to clear message
112
- instances. If `None`, the function will do nothing.
113
- Notes:
114
- - It assumes the existence of a global `_LOCKS` for synchronization and
115
- a dictionary `_MSG_INSTANCE` where each thread ID maps to a list of
116
- message instances.
117
- """
118
- if not thread_id:
119
- return
120
-
121
- with _LOCKS[thread_id]:
122
- _MSG_INSTANCE[thread_id].clear()
123
-
124
-
125
- def get_msg_instances(thread_id: Optional[str] = None) -> Generator:
126
- """
127
- A generator function that yields message instances for a specific thread ID
128
- This function is designed to continuously monitor and yield new message
129
- instances associated with a given thread ID (`thread_id`). It ensures
130
- thread safety through the use of a threading lock when accessing the shared
131
- message instance list. This prevents race conditions in concurrent
132
- environments.
133
- Args:
134
- thread_id (optional): The thread ID for which to monitor and yield
135
- message instances. If `None`, the function will yield `None` and
136
- terminate.
137
- Yields:
138
- The next available message instance for the specified thread ID. If no
139
- message is available, it will wait and check periodically.
140
- Notes:
141
- - The function uses a small delay (`time.sleep(0.1)`) to prevent busy
142
- waiting. This ensures efficient CPU usage while waiting for new
143
- messages.
144
- - It assumes the existence of a global `_LOCK` for synchronization and
145
- a dictionary `_MSG_INSTANCE` where each thread ID maps to a list of
146
- message instances.
147
- Example:
148
- for msg in get_msg_instances(thread_id=123):
149
- process_message(msg)
150
- """
151
- cnt = 0
152
-
153
- if not thread_id:
154
- yield
155
- return
156
-
157
- while True:
158
- with _LOCKS[thread_id]:
159
- if _MSG_INSTANCE[thread_id]:
160
- cnt = 0
161
- yield _MSG_INSTANCE[thread_id].pop(0), len(
162
- _MSG_INSTANCE[thread_id],
163
- )
164
- else:
165
- cnt += 0.1
166
- if cnt > TIMEOUT:
167
- raise TimeoutError
168
- yield None, None
169
- time.sleep(0.1) # Avoid busy waiting
@@ -1,51 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from .base_agent import Agent
4
- from ..llms import BaseLLM
5
- from ..schemas.agent_schemas import (
6
- Message,
7
- TextContent,
8
- convert_to_openai_messages,
9
- MessageType,
10
- convert_to_openai_tools,
11
- )
12
-
13
-
14
- class LLMAgent(Agent):
15
- def __init__(
16
- self,
17
- model: BaseLLM,
18
- **kwargs,
19
- ):
20
- super().__init__(
21
- **kwargs,
22
- )
23
- self.model = model
24
-
25
- async def run_async(
26
- self,
27
- context,
28
- **kwargs,
29
- ):
30
- # agent request --> model request
31
- openai_messages = convert_to_openai_messages(context.session.messages)
32
- tools = convert_to_openai_tools(context.request.tools)
33
-
34
- # Step 3: Create initial Message
35
- message = Message(type=MessageType.MESSAGE, role="assistant")
36
- yield message.in_progress()
37
-
38
- # Step 4: LLM Content delta
39
- text_delta_content = TextContent(delta=True)
40
- async for chunk in self.model.chat_stream(openai_messages, tools):
41
- delta = chunk.choices[0].delta
42
-
43
- if delta.content:
44
- text_delta_content.text = delta.content
45
- text_delta_content = message.add_delta_content(
46
- new_content=text_delta_content,
47
- )
48
- yield text_delta_content
49
-
50
- message.completed()
51
- yield message