Skip to content

Commit

Permalink
Merge pull request #186 from Maplemx/dev
Browse files Browse the repository at this point in the history
v3.4.1.0
  • Loading branch information
Maplemx authored Dec 7, 2024
2 parents 80cba54 + 9048bb6 commit 6988ae8
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 74 deletions.
7 changes: 4 additions & 3 deletions Agently/Agent/Agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import threading
import queue
from ..Request import Request
from ..utils import RuntimeCtx, RuntimeCtxNamespace, StorageDelegate, PluginManager, AliasManager, ToolManager, IdGenerator, DataGenerator,check_version, load_json
from ..utils import RuntimeCtx, RuntimeCtxNamespace, StorageDelegate, PluginManager, AliasManager, ToolManager, IdGenerator, DataGenerator, check_version, load_json

class Agent(object):
def __init__(
Expand All @@ -17,7 +17,7 @@ def __init__(
global_websocket_server: object,
parent_plugin_manager: object,
parent_settings: object,
is_debug: bool=False
is_debug: bool=None,
):
# Integrate
self.global_storage = global_storage
Expand All @@ -39,7 +39,8 @@ def __init__(
parent_settings = self.settings,
)
# Debug
self.settings.set("is_debug", is_debug)
if is_debug:
self.settings.set("is_debug", is_debug)
# Agent Id
if agent_id == None or agent_id == "":
self.agent_id = IdGenerator("agent").create()
Expand Down
119 changes: 50 additions & 69 deletions Agently/plugins/agent_component/ResponseGenerator.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
from itertools import combinations
import asyncio
import threading
import queue
from .utils import ComponentABC
from Agently.utils.Stage import Stage, Tunnel

class ResponseGenerator(ComponentABC):
def __init__(self, agent):
self.agent = agent
self.data_queue = queue.Queue()
self._tunnel = Tunnel()

def put_data_to_generator(self, event, data):
self.data_queue.put((event, data))
if (event, data) != (None, None):
self._tunnel.put((event, data))
else:
self._tunnel.put_stop()

def get_complete_generator(self):
thread = threading.Thread(target=self.agent.start)
thread.start()
while True:
try:
item = self.data_queue.get()
if item == (None, None):
break
yield item
except:
continue
self.data_queue = queue.Queue()
thread.join()
stage = Stage()
stage.go(self.agent.start)
response = self._tunnel.get()
for item in response:
yield item
stage.close()

def get_instant_keys_generator(self, keys):
if not isinstance(keys, list):
Expand All @@ -50,68 +49,50 @@ def get_instant_keys_generator(self, keys):
indexes.append(int(index))
key_indexes_list.append((key, indexes))
self.agent.settings.set("use_instant", True)
thread = threading.Thread(target=self.agent.start)
thread.start()
while True:
try:
item = self.data_queue.get()
if item == (None, None):
break
if item[0] == "instant":
indexes = item[1]["indexes"]
if (item[1]["key"], indexes) in key_indexes_list or (item[1]["key"], []) in key_indexes_list:
yield item[1]
continue
indexes_len = len(indexes)
for r in range(1, indexes_len + 1):
for indices in combinations(range(indexes_len), r):
possible_indexes = indexes[:]
for i in indices:
possible_indexes[i] = -1
if (item[1]["key"], possible_indexes) in key_indexes_list:
yield item[1]
break
except:
continue
self.data_queue = queue.Queue()
thread.join()

stage = Stage()
stage.go(self.agent.start)
response = self._tunnel.get()
for item in response:
if item[0] == "instant":
indexes = item[1]["indexes"]
if (item[1]["key"], indexes) in key_indexes_list or (item[1]["key"], []) in key_indexes_list:
yield item[1]
continue
indexes_len = len(indexes)
for r in range(1, indexes_len + 1):
for indices in combinations(range(indexes_len), r):
possible_indexes = indexes[:]
for i in indices:
possible_indexes[i] = -1
if (item[1]["key"], possible_indexes) in key_indexes_list:
yield item[1]
break
stage.close()

def get_instant_generator(self):
self.agent.settings.set("use_instant", True)
thread = threading.Thread(target=self.agent.start)
thread.start()
while True:
try:
item = self.data_queue.get()
if item == (None, None):
break
if item[0] == "instant":
yield item[1]
except Exception as e:
continue
self.data_queue = queue.Queue()
thread.join()
stage = Stage()
stage.go(self.agent.start)
response = self._tunnel.get()
for item in response:
if item[0] == "instant":
yield item[1]
stage.close()

def get_generator(self):
thread = threading.Thread(target=self.agent.start)
thread.start()
while True:
try:
item = self.data_queue.get()
if item == (None, None):
break
if not item[0].endswith(("_origin")):
yield item
except:
continue
self.data_queue = queue.Queue()
thread.join()
stage = Stage()
stage.go(self.agent.start)
response = self._tunnel.get()
for item in response:
if not item[0].endswith(("_origin")):
yield item
stage.close()

def _suffix(self, event, data):
async def _suffix(self, event, data):
if event != "response:finally":
self.put_data_to_generator(event, data)
self._tunnel.put((event, data))
else:
self.put_data_to_generator(None, None)
self._tunnel.put_stop()

def export(self):
return {
Expand Down
2 changes: 1 addition & 1 deletion Agently/plugins/tool/Web.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def search(self, keywords: str, *, options: dict={}, proxy: str=None, type: int=
'''
search_kwargs = {}
if proxy:
search_kwargs["proxies"] = proxy
search_kwargs["proxy"] = proxy
if "max_results" not in options:
options.update({ "max_results": 5 })
try:
Expand Down
157 changes: 157 additions & 0 deletions Agently/utils/Stage/Stage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import inspect
import threading
import asyncio
from concurrent.futures import ThreadPoolExecutor
from .StageResponse import StageResponse
from .StageHybridGenerator import StageHybridGenerator
from .StageFunction import StageFunctionMixin

class BaseStage:
def __init__(self, max_workers=5, max_concurrent_tasks=None, on_error=None, close_when_exception=False):
self._max_workers = max_workers
self._max_concurrent_tasks = max_concurrent_tasks
self._on_error = on_error
self._semaphore = None
self._loop_thread = None
self._loop = None
self._executor = None
self._responses = []
#self._initialize()

def _initialize(self):
self._loop_ready = threading.Event()
self._loop_thread = threading.Thread(target=self._start_loop)
self._loop_thread.start()
self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
self._loop_ready.wait()
del self._loop_ready

def _loop_exception_handler(self, loop, context):
if self._on_error is not None:
loop.call_soon_threadsafe(self._on_error, context["exception"])
else:
raise context["exception"]

def _start_loop(self):
self._loop = asyncio.new_event_loop()
self._loop.set_exception_handler(self._loop_exception_handler)
asyncio.set_event_loop(self._loop)
if self._max_concurrent_tasks:
self._semaphore = asyncio.Semaphore(self._max_concurrent_tasks)
self._loop_ready.set()
self._loop.run_forever()

def go(self, task, *args, on_success=None, on_error=None, lazy=False, async_gen_interval=0.1, **kwargs):
if not self._executor or not self._loop or not self._loop.is_running():
self._initialize()
response_kwargs = {
"on_success": on_success,
"on_error": on_error,
}
hybrid_generator_kwargs = {
"on_success": on_success,
"on_error": on_error,
"lazy": lazy,
"async_gen_interval": async_gen_interval,
}
if inspect.iscoroutine(task):
if self._semaphore:
if not self._semaphore.locked():
return StageResponse(self, task, **response_kwargs)
else:
self._semaphore.acquire()
try:
return StageResponse(self, task, **response_kwargs)
finally:
self._semaphore.release()
else:
return StageResponse(self, task, **response_kwargs)
elif inspect.isasyncgen(task):
if self._semaphore:
if not self._semaphore.locked():
return StageHybridGenerator(self, task, **hybrid_generator_kwargs)
else:
self._semaphore.acquire()
try:
return StageHybridGenerator(self, task, **hybrid_generator_kwargs)
finally:
self._semaphore.release()
else:
return StageHybridGenerator(self, task, **hybrid_generator_kwargs)
elif asyncio.isfuture(task):
return StageResponse(self, task, **response_kwargs)
elif inspect.isgenerator(task):
async def async_generator():
for item in task:
result = await self._loop.run_in_executor(self._executor, lambda: item)
yield result
return self.go(async_generator())
elif inspect.iscoroutinefunction(task) or inspect.isasyncgenfunction(task):
return self.go(task(*args, **kwargs), **hybrid_generator_kwargs)
elif inspect.isgeneratorfunction(task):
return self.go(task(*args, **kwargs), **hybrid_generator_kwargs)
elif inspect.isfunction(task) or inspect.ismethod(task):
return StageResponse(self, self._loop.run_in_executor(self._executor, lambda: task(*args, **kwargs)), **response_kwargs)
else:
return task

def go_all(self, *task_list):
response_list = []
for task in task_list:
if isinstance(task, (tuple, list)):
func, *args, kwargs = task
response = self.go(func, *args, **kwargs)
else:
response = self.go(task)
response_list.append(response)
return response_list

def get(self, task, *args, **kwargs):
response = self.go(task, *args, **kwargs)
if isinstance(response, StageResponse):
return response.get()
elif isinstance(response, StageHybridGenerator):
result = []
for item in response:
result.append(item)
return result

def get_all(self, *task_list):
result_list = []
for task in task_list:
if isinstance(task, (tuple, list)):
func, *args, kwargs = task
response = self.go(func, *args, **kwargs)
else:
response = self.go(task)
if isinstance(response, StageResponse):
result_list.append(response.get())
else:
result_list.append(response)
return result_list

def on_error(self, handler):
self._on_error = handler

def close(self):
for response in self._responses:
response._result_ready.wait()

if self._loop and self._loop.is_running():
self._loop.call_soon_threadsafe(self._loop.stop)
if self._loop:
pending = asyncio.all_tasks(self._loop)
if pending:
self._loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
if self._loop_thread and self._loop_thread.is_alive():
self._loop_thread.join()
self._loop_thread = None
if self._loop and not self._loop.is_closed:
self._loop.close()
self._loop = None
if self._executor:
self._executor.shutdown(wait=True)
self._executor = None

class Stage(BaseStage, StageFunctionMixin):
pass
53 changes: 53 additions & 0 deletions Agently/utils/Stage/StageFunction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
class StageFunction:
def __init__(self, stage, func, *args, **kwargs):
self._stage = stage
self._func = func
self._args = args
self._kwargs = kwargs
self._on_success_handler = None
self._on_error_handler = None

def set(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
return self

def args(self, *args):
self._args = args
return self

def kwargs(self, **kwargs):
self._kwargs = kwargs
return self

def on_success(self, on_success_handler):
self._on_success_handler = on_success_handler
return self

def on_error(self, on_error_handler):
self._on_error_handler = on_error_handler
return self

def go(self):
return self._stage.go(
self._func,
*self._args,
on_success=self._on_success_handler,
on_error=self._on_error_handler,
**self._kwargs
)

def get(self):
return self._stage.get(
self._func,
*self._args,
on_success=self._on_success_handler,
on_error=self._on_error_handler,
**self._kwargs
)

class StageFunctionMixin:
def func(self, func):
def wrapper(*args, **kwargs):
return StageFunction(self, func, *args, **kwargs)
return wrapper
Loading

0 comments on commit 6988ae8

Please sign in to comment.