LLHUB commited on
Commit
d10bda8
·
1 Parent(s): 9db8d5f

Upload 237 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +58 -0
  2. .gitattributes +2 -0
  3. .gitignore +40 -0
  4. GeneralAgent/__init__.py +1 -0
  5. GeneralAgent/agent/__init__.py +14 -0
  6. GeneralAgent/agent/abs_agent.py +83 -0
  7. GeneralAgent/agent/normal_agent.py +189 -0
  8. GeneralAgent/agent/stack_agent.py +221 -0
  9. GeneralAgent/cli.py +39 -0
  10. GeneralAgent/interpreter/__init__.py +22 -0
  11. GeneralAgent/interpreter/applescript_interpreter.py +46 -0
  12. GeneralAgent/interpreter/embedding_retrieve_interpreter.py +77 -0
  13. GeneralAgent/interpreter/file_interpreter.py +120 -0
  14. GeneralAgent/interpreter/interpreter.py +80 -0
  15. GeneralAgent/interpreter/link_retrieve_interpreter.py +57 -0
  16. GeneralAgent/interpreter/plan_interpreter.py +59 -0
  17. GeneralAgent/interpreter/python_interpreter.py +326 -0
  18. GeneralAgent/interpreter/role_interpreter.py +40 -0
  19. GeneralAgent/interpreter/shell_interpreter.py +44 -0
  20. GeneralAgent/interpreter/ui_interpreter.py +51 -0
  21. GeneralAgent/memory/__init__.py +4 -0
  22. GeneralAgent/memory/link_memory.py +125 -0
  23. GeneralAgent/memory/normal_memory.py +42 -0
  24. GeneralAgent/memory/stack_memory.py +223 -0
  25. GeneralAgent/pytest.ini +2 -0
  26. GeneralAgent/requirements.txt +30 -0
  27. GeneralAgent/skills/__init__.py +121 -0
  28. GeneralAgent/skills/agent_builder_2.py +245 -0
  29. GeneralAgent/skills/agents.py +105 -0
  30. GeneralAgent/skills/ai_draw_prompt_gen.py +11 -0
  31. GeneralAgent/skills/application_builder.py +598 -0
  32. GeneralAgent/skills/applications.py +85 -0
  33. GeneralAgent/skills/build_web.py +149 -0
  34. GeneralAgent/skills/concatenate_videos/concatenate_videos.py +27 -0
  35. GeneralAgent/skills/concatenate_videos/f63bfaae7b0e.mp4 +0 -0
  36. GeneralAgent/skills/download_file.py +50 -0
  37. GeneralAgent/skills/file_operation.py +51 -0
  38. GeneralAgent/skills/llm_inference.py +458 -0
  39. GeneralAgent/skills/memory_utils.py +153 -0
  40. GeneralAgent/skills/merge_video_audio/merge_video_audio.py +67 -0
  41. GeneralAgent/skills/merge_video_audio/music.wav +3 -0
  42. GeneralAgent/skills/merge_video_audio/narration.mp3 +0 -0
  43. GeneralAgent/skills/merge_video_audio/tmp_audio.mp3 +0 -0
  44. GeneralAgent/skills/merge_video_audio/video.mp4 +0 -0
  45. GeneralAgent/skills/musicgen/generate_music.py +32 -0
  46. GeneralAgent/skills/python_envs.py +130 -0
  47. GeneralAgent/skills/replicate_api.py +75 -0
  48. GeneralAgent/skills/scrape_dynamic_web.py +70 -0
  49. GeneralAgent/skills/split_text.py +28 -0
  50. GeneralAgent/skills/stable_video_diffusion/dab774a452f3.jpg +0 -0
.env ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LLM import use litellm lib (https://docs.litellm.ai/docs/)
2
+ # OPENAI | AZURE | OR other LLM PREFIX
3
+ LLM_SOURCE='OPENAI'
4
+
5
+ LLM_TEMPERATURE='0.1'
6
+
7
+ # open ai
8
+ OPENAI_API_KEY='sk-666'
9
+ OPENAI_API_BASE='https://api.zhtec.xyz/xht/chatWith16k.php'
10
+ OPENAI_EMBEDDING_MODEL='text-embedding-ada-002'
11
+ OPENAI_LLM_MODEL_NORMAL='gpt-3.5-turbo-16k'
12
+ OPENAI_LLM_MODEL_SMART='gpt-4'
13
+ OPENAI_LLM_MODEL_SMART_LIMIT='8000'
14
+ OPENAI_LLM_MODEL_SMART_LONG='gpt-4-32k'
15
+ OPENAI_LLM_MODEL_SMART_LONG_LIMIT='32000'
16
+ OPENAI_LLM_MODEL_LONG='gpt-4-1106-preview'
17
+ OPENAI_LLM_MODEL_VISION='gpt-4-vision-preview'
18
+ OPENAI_LLM_MODEL_ALL='gpt-4-all'
19
+
20
+ # azure open ai
21
+ AZURE_API_KEY='xx'
22
+ AZURE_API_BASE='xx'
23
+ AZURE_API_VERSION='2023-06-01-preview'
24
+ AZURE_EMBEDDING_MODEL='azure/ada002'
25
+ AZURE_LLM_MODEL_NORMAL='azure/gpt35t'
26
+ AZURE_LLM_MODEL_SMART='azure/gpt4'
27
+ OPENAI_LLM_MODEL_SMART_LIMIT='8000'
28
+ AZURE_LLM_MODEL_LONG='azure/gpt4'
29
+
30
+ # replicate
31
+ REPLICATE_API_TOKEN='r8_E2UIynoq1yuY5ErDijgm4uR1C2pW8pG4IPXU7'
32
+
33
+ ## user data directory, store user's applications、functions、application_datas and server datas
34
+ DATA_DIR='/workspace/data/'
35
+
36
+ # cache llm inference and embedding, useful when develop and debug
37
+ LLM_CACHE='no'
38
+ EMBEDDING_CACHE='yes'
39
+ # CACHE_PATH='./llm_cache.json'
40
+
41
+ # google search tool at https://google.serper.dev
42
+ SERPER_API_KEY='7a7a62b5f4a991043dd7fe96553802fb7675927c'
43
+
44
+ # Whether to automatically run python, shell, applescript and other codes
45
+ # Default no: n
46
+ AUTO_RUN='y'
47
+
48
+ # Logging level
49
+ # Default: INFO, can be DEBUG, INFO, WARNING, ERROR
50
+ LOG_LEVEL='INFO'
51
+
52
+ ## ui builder directory, webui server(webui/server/server/app.py) will automatically set TSX_BUILDER_DIR to webui/server/server/ts_builder
53
+ # TSX_BUILDER_DIR
54
+
55
+ ## local applications code directory. webui server(webui/server/server/app.py) will automatically set LOCAL_APPLICATIONS_DIR to webui/server/server/applicatoins
56
+ # LOCAL_APPLICATIONS_DIR
57
+
58
+ # export $(grep -v '^#' .env | sed 's/^export //g' | xargs)
.gitattributes CHANGED
@@ -35,3 +35,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  GeneralAgent/GeneralAgent/skills/merge_video_audio/music.wav filter=lfs diff=lfs merge=lfs -text
37
  GeneralAgent/test/data/Nougat.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  GeneralAgent/GeneralAgent/skills/merge_video_audio/music.wav filter=lfs diff=lfs merge=lfs -text
37
  GeneralAgent/test/data/Nougat.pdf filter=lfs diff=lfs merge=lfs -text
38
+ GeneralAgent/skills/merge_video_audio/music.wav filter=lfs diff=lfs merge=lfs -text
39
+ test/data/Nougat.pdf filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.pyc
2
+ GeneralAgent/.env
3
+ GeneralAgent/*.json
4
+
5
+ test/data_0.json
6
+ test/data/test_workspace/*
7
+ test/tetris.py
8
+ test/tetris.py.bak
9
+ GeneralAgent/cache_json
10
+ test/data/plan_memory.json
11
+ test/data/test_interpreter.bin
12
+ test/data/b.txt
13
+ test/data/a.txt
14
+ test/data/hello.pptx
15
+ test/.env
16
+
17
+ build/*
18
+ dist/*
19
+ GeneralAgent.egg-info*
20
+ test/multi_lines_input/*
21
+ test/multi_lines_input/*
22
+ .env
23
+ examples/memory.json
24
+ test/link_memory.json
25
+ test/memory.json
26
+ test/llm_cache.json
27
+ test/summary_memory.json
28
+ */llm_cache.json
29
+ test/test_skills/data/*
30
+ test/test_skills/llm_cache.json
31
+ webui/server/server/applications/test_application_id/bot.json
32
+ webui/server/server/applications/test_application_id/main.py
33
+ data/*
34
+
35
+ .idea/*
36
+ test/test_skills/code/*
37
+ test/data/ui/*
38
+ test/code/*
39
+ test/data/read_interpreter/*
40
+ webui/server/server/ts_builder/src/lib/index.tsx
GeneralAgent/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .skills import skills
GeneralAgent/agent/__init__.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import
2
+ from .normal_agent import NormalAgent
3
+ from .stack_agent import StackAgent
4
+
5
+ class Agent(NormalAgent):
6
+ pass
7
+
8
+ import os
9
+ LLM_SOURCE = os.environ.get('LLM_SOURCE', None)
10
+ if LLM_SOURCE is None:
11
+ print('enviroment variable LLM_SOURCE not available.')
12
+
13
+ from GeneralAgent.utils import set_logging_level
14
+ set_logging_level()
GeneralAgent/agent/abs_agent.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import abc
2
+ import os
3
+ import asyncio
4
+ from GeneralAgent.utils import default_get_input, default_output_callback
5
+
6
+
7
+ class AbsAgent(metaclass=abc.ABCMeta):
8
+ """
9
+ Abstract Agent
10
+ @memory: Memory
11
+ @interpreters: list, interpreters
12
+ @model_type: str, 'normal' or 'smart' or 'long'. For OpenAI api, normal=gpt3.5, smart=gpt4, long=gpt3.5-16k
13
+ @hide_output_parse: bool, hide the llm's output that output interpreters will parse, default True
14
+ """
15
+ memory = None
16
+ interpreters = []
17
+ model_type = 'normal'
18
+ hide_output_parse = False
19
+
20
+ def add_role_prompt(self, prompt):
21
+ """
22
+ add role prompt
23
+ """
24
+ if len(self.interpreters) > 0 and self.interpreters[0].__class__.__name__ == 'RoleInterpreter':
25
+ role_interpreter = self.interpreters[0]
26
+ if role_interpreter.system_prompt is not None:
27
+ role_interpreter.system_prompt += '\n' + prompt
28
+ else:
29
+ role_interpreter.system_prompt_template += '\n' + prompt
30
+
31
+ @abc.abstractmethod
32
+ async def run(self, input=None, output_callback=default_output_callback, input_for_memory_node_id=-1):
33
+ """
34
+ input: str, user's new input, None means continue to run where it stopped
35
+ input_for_memory_node_id: int, -1 means input is not from memory, None means input new, otherwise input is for memory node
36
+ output_callback: async function, output_callback(content: str) -> None
37
+ """
38
+ pass
39
+
40
+ def stop(self):
41
+ """
42
+ stop the agent
43
+ """
44
+ self.stop_event.set()
45
+
46
+
47
+ def __init__(self, workspace='./'):
48
+ """
49
+ @workspace: str, workspace path
50
+ """
51
+ if not os.path.exists(workspace):
52
+ os.makedirs(workspace)
53
+ self.is_running = False
54
+ self.stop_event = asyncio.Event()
55
+ self.workspace = workspace
56
+
57
+ @classmethod
58
+ def empty(cls, workspace='./'):
59
+ """
60
+ empty agent, only role interpreter and memory, work like a basic LLM chatbot
61
+ @workspace: str, workspace path
62
+ """
63
+ pass
64
+
65
+ @classmethod
66
+ def default(cls, workspace='./', retrieve_type='embedding'):
67
+ """
68
+ default agent, with all interpreters
69
+ @workspace: str, workspace path
70
+ @retrieve_type: str, 'embedding' or 'link'
71
+ """
72
+ pass
73
+
74
+ @classmethod
75
+ def with_functions(cls, functions, role_prompt=None, workspace = './', model_type='smart'):
76
+ """
77
+ functions: list, [function1, function2, ...]
78
+ @role_prompt: str, role prompt
79
+ @workspace: str, workspace path
80
+ @import_code: str, import code
81
+ @libs: str, libs
82
+ """
83
+ pass
GeneralAgent/agent/normal_agent.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent
2
+ import os, re
3
+ import asyncio
4
+ import logging
5
+ from GeneralAgent.utils import default_get_input, default_output_callback
6
+ from GeneralAgent.memory import NormalMemory
7
+ from GeneralAgent.interpreter import Interpreter
8
+ from GeneralAgent.interpreter import EmbeddingRetrieveInterperter, LinkRetrieveInterperter
9
+ from GeneralAgent.interpreter import RoleInterpreter, PythonInterpreter, ShellInterpreter, AppleScriptInterpreter, FileInterpreter
10
+ from .abs_agent import AbsAgent
11
+
12
+
13
+ class NormalAgent(AbsAgent):
14
+
15
+ def __init__(self, workspace='./'):
16
+ super().__init__(workspace)
17
+ self.memory = NormalMemory(serialize_path=f'{workspace}/normal_memory.json')
18
+
19
+ @classmethod
20
+ def empty(cls, workspace='./'):
21
+ """
22
+ empty agent, only role interpreter and memory, work like a basic LLM chatbot
23
+ """
24
+ agent = cls(workspace)
25
+ agent.interpreters = [RoleInterpreter()]
26
+ return agent
27
+
28
+ @classmethod
29
+ def default(cls, workspace='./', retrieve_type='embedding'):
30
+ """
31
+ default agent, with all interpreters
32
+ @workspace: str, workspace path
33
+ @retrieve_type: str, 'embedding' or 'link'
34
+ """
35
+ agent = cls(workspace)
36
+ # memory
37
+ # interpreters
38
+ role_interpreter = RoleInterpreter()
39
+ python_interpreter = PythonInterpreter(serialize_path=f'{workspace}/code.bin')
40
+ if retrieve_type == 'embedding':
41
+ retrieve_interperter = EmbeddingRetrieveInterperter(serialize_path=f'{workspace}/read_interperter/')
42
+ else:
43
+ retrieve_interperter = LinkRetrieveInterperter(python_interpreter)
44
+ bash_interpreter = ShellInterpreter(workspace)
45
+ applescript_interpreter = AppleScriptInterpreter()
46
+ file_interpreter = FileInterpreter()
47
+ agent.interpreters = [role_interpreter, retrieve_interperter, python_interpreter, bash_interpreter, applescript_interpreter, file_interpreter]
48
+ return agent
49
+
50
+ @classmethod
51
+ def with_functions(cls, functions, role_prompt=None, workspace = './', model_type='smart'):
52
+ """
53
+ functions: list, [function1, function2, ...]
54
+ @role_prompt: str, role prompt
55
+ @workspace: str, workspace path
56
+ @import_code: str, import code
57
+ @libs: str, libs
58
+ """
59
+ agent = cls(workspace)
60
+ role_interpreter = RoleInterpreter()
61
+ python_interpreter = PythonInterpreter(serialize_path=f'{workspace}/code.bin')
62
+ python_interpreter.function_tools = functions
63
+ agent.interpreters = [role_interpreter, python_interpreter, ShellInterpreter()]
64
+ agent.model_type = model_type
65
+ if role_prompt is not None:
66
+ agent.add_role_prompt(role_prompt)
67
+ return agent
68
+
69
+
70
+ async def run(self, input=None, output_callback=default_output_callback, input_for_memory_node_id=-1):
71
+ """
72
+ agent run: parse intput -> get llm messages -> run LLM and parse output
73
+ @input: str, user's new input, None means continue to run where it stopped
74
+ @input_for_memory_node_id: int, -1 means input is not from memory, None means input new, otherwise input is for memory node
75
+ @output_callback: async function, output_callback(content: str) -> None
76
+ """
77
+ self.is_running = True
78
+
79
+ input_stop = await self._parse_input(input, output_callback)
80
+ if input_stop:
81
+ self.is_running = False
82
+ return
83
+
84
+ while True:
85
+ messages = await self._get_llm_messages()
86
+ output_stop = await self._llm_and_parse_output(messages, output_callback)
87
+ if output_stop:
88
+ self.is_running = False
89
+ return
90
+ await asyncio.sleep(0)
91
+ if self.stop_event.is_set():
92
+ self.is_running = False
93
+ return
94
+
95
+ async def _parse_input(self, input, output_callback):
96
+ self.memory.add_message('user', input)
97
+ input_content = input
98
+ input_stop = False
99
+ interpreter:Interpreter = None
100
+ for interpreter in self.interpreters:
101
+ if interpreter.input_match(input_content):
102
+ logging.info('interpreter: ' + interpreter.__class__.__name__)
103
+ parse_output, case_is_stop = await interpreter.input_parse(input_content)
104
+ if case_is_stop:
105
+ await output_callback(parse_output)
106
+ input_stop = True
107
+ return input_stop
108
+
109
+ async def _get_llm_messages(self):
110
+ from GeneralAgent import skills
111
+ messages = self.memory.get_messages()
112
+ token_limit = skills.get_llm_token_limit(self.model_type)
113
+ messages = skills.cut_messages(messages, int(token_limit*0.8))
114
+ system_prompt = '\n\n'.join([await interpreter.prompt(messages) for interpreter in self.interpreters])
115
+ messages = [{'role': 'system', 'content': system_prompt}] + messages
116
+ return messages
117
+
118
+ async def _llm_and_parse_output(self, messages, output_callback):
119
+ from GeneralAgent import skills
120
+ try:
121
+ result = ''
122
+ is_stop = True
123
+ is_break = False
124
+ in_parse_content = False
125
+ cache_tokens = []
126
+ response = skills.llm_inference(messages, model_type=self.model_type, stream=True)
127
+ for token in response:
128
+ if token is None: break
129
+ result += token
130
+ # logging.debug(result)
131
+ # print(token)
132
+ if self.hide_output_parse:
133
+ if not in_parse_content:
134
+ interpreter:Interpreter = None
135
+ for interpreter in self.interpreters:
136
+ is_start_matched, string_matched = interpreter.output_match_start(result)
137
+ if is_start_matched:
138
+ in_parse_content = True
139
+ # clear cache
140
+ cache_tokens.append(token)
141
+ left_count = len(string_matched)
142
+ while left_count > 0:
143
+ left_count -= len(cache_tokens[-1])
144
+ cache_tokens.remove(cache_tokens[-1])
145
+ while len(cache_tokens) > 0:
146
+ pop_token = cache_tokens.pop(0)
147
+ await output_callback(pop_token)
148
+ if not in_parse_content:
149
+ # cache token
150
+ cache_tokens.append(token)
151
+ if len(cache_tokens) > 5:
152
+ pop_token = cache_tokens.pop(0)
153
+ await output_callback(pop_token)
154
+ else:
155
+ await output_callback(token)
156
+ interpreter:Interpreter = None
157
+ for interpreter in self.interpreters:
158
+ if interpreter.output_match(result):
159
+ logging.info('interpreter: ' + interpreter.__class__.__name__)
160
+ output, is_stop = await interpreter.output_parse(result)
161
+ if interpreter.outptu_parse_done_recall is not None:
162
+ await interpreter.outptu_parse_done_recall()
163
+ if self.hide_output_parse:
164
+ is_matched, string_left = interpreter.output_match_end(result)
165
+ await output_callback(string_left)
166
+ while len(cache_tokens) > 0:
167
+ pop_token = cache_tokens.pop(0)
168
+ await output_callback(pop_token)
169
+ result += '\n' + output.strip() + '\n'
170
+ # logging.debug(result)
171
+ if not self.hide_output_parse or is_stop:
172
+ await output_callback('\n' + output.strip() + '\n')
173
+ is_break = True
174
+ in_parse_content = False
175
+ break
176
+ if is_break:
177
+ break
178
+ while len(cache_tokens) > 0:
179
+ pop_token = cache_tokens.pop(0)
180
+ await output_callback(pop_token)
181
+ # append messages
182
+ # logging.debug(result)
183
+ self.memory.append_message('assistant', result)
184
+ return is_stop
185
+ except Exception as e:
186
+ # if fail, recover
187
+ logging.exception(e)
188
+ await output_callback(str(e))
189
+ return True
GeneralAgent/agent/stack_agent.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent
2
+ import os, re
3
+ import asyncio
4
+ import logging
5
+ from GeneralAgent.utils import default_get_input, default_output_callback
6
+ from GeneralAgent.memory import StackMemory, StackMemoryNode
7
+ from GeneralAgent.interpreter import Interpreter
8
+ from GeneralAgent.interpreter import PlanInterpreter, EmbeddingRetrieveInterperter, LinkRetrieveInterperter
9
+ from GeneralAgent.interpreter import RoleInterpreter, PythonInterpreter, ShellInterpreter, AppleScriptInterpreter, FileInterpreter
10
+ from .abs_agent import AbsAgent
11
+
12
+
13
+ class StackAgent(AbsAgent):
14
+
15
+ def __init__(self, workspace='./'):
16
+ super().__init__(workspace)
17
+ self.memory = StackMemory(serialize_path=f'{workspace}/memory.json')
18
+
19
+ @classmethod
20
+ def empty(cls, workspace='./'):
21
+ """
22
+ empty agent, only role interpreter and memory, work like a basic LLM chatbot
23
+ """
24
+ agent = cls(workspace)
25
+ agent.interpreters = [RoleInterpreter()]
26
+ return agent
27
+
28
+ @classmethod
29
+ def default(cls, workspace='./', retrieve_type='embedding'):
30
+ """
31
+ default agent, with all interpreters
32
+ @workspace: str, workspace path
33
+ @retrieve_type: str, 'embedding' or 'link'
34
+ """
35
+ agent = cls(workspace)
36
+ # memory
37
+ # interpreters
38
+ role_interpreter = RoleInterpreter()
39
+ python_interpreter = PythonInterpreter(serialize_path=f'{workspace}/code.bin')
40
+ plan_interperter = PlanInterpreter(agent.memory)
41
+ if retrieve_type == 'embedding':
42
+ retrieve_interperter = EmbeddingRetrieveInterperter(serialize_path=f'{workspace}/read_interperter/')
43
+ else:
44
+ retrieve_interperter = LinkRetrieveInterperter(python_interpreter)
45
+ bash_interpreter = ShellInterpreter(workspace)
46
+ applescript_interpreter = AppleScriptInterpreter()
47
+ file_interpreter = FileInterpreter()
48
+ agent.interpreters = [role_interpreter, plan_interperter, retrieve_interperter, python_interpreter, bash_interpreter, applescript_interpreter, file_interpreter]
49
+ return agent
50
+
51
+ @classmethod
52
+ def with_functions(cls, functions, role_prompt=None, workspace = './', model_type='smart'):
53
+ """
54
+ functions: list, [function1, function2, ...]
55
+ @role_prompt: str, role prompt
56
+ @workspace: str, workspace path
57
+ @import_code: str, import code
58
+ @libs: str, libs
59
+ """
60
+ agent = cls(workspace)
61
+ role_interpreter = RoleInterpreter(system_prompt=role_prompt)
62
+ python_interpreter = PythonInterpreter(serialize_path=f'{workspace}/code.bin')
63
+ python_interpreter.function_tools = functions
64
+ agent.interpreters = [role_interpreter, python_interpreter, ShellInterpreter()]
65
+ agent.model_type = model_type
66
+ return agent
67
+
68
+
69
+ async def run(self, input=None, output_callback=default_output_callback, input_for_memory_node_id=-1):
70
+ """
71
+ agent run: parse intput -> get llm messages -> run LLM and parse output
72
+ input: str, user's new input, None means continue to run where it stopped
73
+ input_for_memory_node_id: int, -1 means input is not from memory, None means input new, otherwise input is for memory node
74
+ output_callback: async function, output_callback(content: str) -> None
75
+ """
76
+ self.is_running = True
77
+
78
+ if input_for_memory_node_id == -1:
79
+ memory_node_id = self.memory.current_node.node_id if self.memory.current_node is not None else None
80
+ else:
81
+ memory_node_id = input_for_memory_node_id
82
+ input_node = self._insert_node(input, memory_node_id) if input is not None else None
83
+
84
+ # input interpreter
85
+ if input_node is not None:
86
+ input_content = input
87
+ input_stop = False
88
+ self.memory.set_current_node(input_node)
89
+ interpreter:Interpreter = None
90
+ for interpreter in self.interpreters:
91
+ if interpreter.input_match(input_content):
92
+ logging.info('interpreter: ' + interpreter.__class__.__name__)
93
+ # await output_callback('input parsing\n')
94
+ input_content, case_is_stop = await interpreter.input_parse(input_content)
95
+ if case_is_stop:
96
+ await output_callback(input_content)
97
+ input_stop = True
98
+ input_node.content = input_content
99
+ self.memory.update_node(input_node)
100
+ if input_stop:
101
+ self.memory.success_node(input_node)
102
+ self.is_running = False
103
+ return input_node.node_id
104
+
105
+ # execute todo node from memory
106
+ todo_node = self.memory.get_todo_node() or input_node
107
+ logging.debug(self.memory)
108
+ while todo_node is not None:
109
+ new_node, is_stop = await self._execute_node(todo_node, output_callback)
110
+ logging.debug(self.memory)
111
+ logging.debug(new_node)
112
+ logging.debug(is_stop)
113
+ if is_stop:
114
+ return new_node.node_id
115
+ todo_node = self.memory.get_todo_node()
116
+ await asyncio.sleep(0)
117
+ if self.stop_event.is_set():
118
+ self.is_running = False
119
+ return None
120
+ self.is_running = False
121
+ return None
122
+
123
+ def _insert_node(self, input, memory_node_id=None):
124
+ node = StackMemoryNode(role='user', action='input', content=input)
125
+ if memory_node_id is None:
126
+ logging.debug(self.memory)
127
+ self.memory.add_node(node)
128
+ else:
129
+ for_node = self.memory.get_node(memory_node_id)
130
+ self.memory.add_node_after(for_node, node)
131
+ self.memory.success_node(for_node)
132
+ return node
133
+
134
+ async def _execute_node(self, node, output_callback):
135
+ # construct system prompt
136
+ from GeneralAgent import skills
137
+ messages = self.memory.get_related_messages_for_node(node)
138
+ messages = skills.cut_messages(messages, 3000)
139
+ system_prompt = '\n\n'.join([await interpreter.prompt(messages) for interpreter in self.interpreters])
140
+ messages = [{'role': 'system', 'content': system_prompt}] + messages
141
+
142
+ # add answer node and set current node
143
+ answer_node = StackMemoryNode(role='system', action='answer', content='')
144
+ self.memory.add_node_after(node, answer_node)
145
+ self.memory.set_current_node(answer_node)
146
+
147
+ if node.action == 'plan':
148
+ await output_callback(f'\n[{node.content}]\n')
149
+
150
+ try:
151
+ result = ''
152
+ is_stop = False
153
+ is_break = False
154
+ in_parse_content = False
155
+ cache_tokens = []
156
+ response = skills.llm_inference(messages, model_type=self.model_type, stream=True)
157
+ for token in response:
158
+ if token is None: break
159
+ result += token
160
+ # print(token)
161
+ if self.hide_output_parse:
162
+ if not in_parse_content:
163
+ interpreter:Interpreter = None
164
+ for interpreter in self.interpreters:
165
+ is_start_matched, string_matched = interpreter.output_match_start(result)
166
+ if is_start_matched:
167
+ in_parse_content = True
168
+ # clear cache
169
+ cache_tokens.append(token)
170
+ left_count = len(string_matched)
171
+ while left_count > 0:
172
+ left_count -= len(cache_tokens[-1])
173
+ cache_tokens.remove(cache_tokens[-1])
174
+ while len(cache_tokens) > 0:
175
+ pop_token = cache_tokens.pop(0)
176
+ await output_callback(pop_token)
177
+ if not in_parse_content:
178
+ # cache token
179
+ cache_tokens.append(token)
180
+ if len(cache_tokens) > 5:
181
+ pop_token = cache_tokens.pop(0)
182
+ await output_callback(pop_token)
183
+ else:
184
+ await output_callback(token)
185
+ interpreter:Interpreter = None
186
+ for interpreter in self.interpreters:
187
+ if interpreter.output_match(result):
188
+ logging.info('interpreter: ' + interpreter.__class__.__name__)
189
+ output, is_stop = await interpreter.output_parse(result)
190
+ if interpreter.outptu_parse_done_recall is not None:
191
+ await interpreter.outptu_parse_done_recall()
192
+ if self.hide_output_parse:
193
+ is_matched, string_left = interpreter.output_match_end(result)
194
+ await output_callback(string_left)
195
+ result += '\n' + output.strip() + '\n'
196
+ if not self.hide_output_parse or is_stop:
197
+ await output_callback('\n' + output.strip() + '\n')
198
+ is_break = True
199
+ in_parse_content = False
200
+ break
201
+ if is_break:
202
+ break
203
+ while len(cache_tokens) > 0:
204
+ pop_token = cache_tokens.pop(0)
205
+ await output_callback(pop_token)
206
+ # await output_callback('\n')
207
+ # update current node and answer node
208
+ answer_node.content = result
209
+ self.memory.update_node(answer_node)
210
+ self.memory.success_node(node)
211
+ # llm run end with any interpreter, success the node
212
+ if not is_break:
213
+ self.memory.success_node(answer_node)
214
+ return answer_node, is_stop
215
+ except Exception as e:
216
+ # if fail, recover
217
+ logging.exception(e)
218
+ await output_callback(str(e))
219
+ self.memory.delete_node(answer_node)
220
+ self.memory.set_current_node(node)
221
+ return node, True
GeneralAgent/cli.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ import datetime
4
+ import argparse
5
+ from GeneralAgent.agent import Agent
6
+ from GeneralAgent.utils import default_get_input, set_logging_level
7
+
8
+
9
+ async def _main(args):
10
+ if args.auto_run:
11
+ os.environ['AUTO_RUN'] = 'y'
12
+ else:
13
+ os.environ['AUTO_RUN'] = 'n'
14
+ set_logging_level(os.environ.get('LOG_LEVEL', 'ERROR'))
15
+ workspace = args.workspace
16
+ if args.new:
17
+ if os.path.exists(workspace):
18
+ workspace = workspace + '_' + datetime.datetime.now().strftime('%Y%m%d%H%M%S')
19
+ print('New workspace: ', workspace)
20
+ if not os.path.exists(workspace):
21
+ os.mkdir(workspace)
22
+
23
+ agent = Agent.default(workspace=workspace)
24
+ print('You can input multi lines, enter twice to end')
25
+ while True:
26
+ input_content = default_get_input()
27
+ print('[output]\n', end='', flush=True)
28
+ await agent.run(input_content)
29
+
30
+ def main():
31
+ parser = argparse.ArgumentParser(description='GeneralAgent CLI')
32
+ parser.add_argument('--workspace', default='./general_agent', help='Set workspace directory')
33
+ parser.add_argument('--new', action='store_true', help='Enable new workspace')
34
+ parser.add_argument('--auto_run', action='store_true', help='Auto run code without confirm')
35
+ args = parser.parse_args()
36
+ asyncio.run(_main(args))
37
+
38
+ if __name__ == '__main__':
39
+ main()
GeneralAgent/interpreter/__init__.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import
2
+ from .interpreter import Interpreter
3
+
4
+ # role
5
+ from .role_interpreter import RoleInterpreter
6
+
7
+ # input
8
+ from .plan_interpreter import PlanInterpreter
9
+
10
+ # retrieve
11
+ from .embedding_retrieve_interpreter import EmbeddingRetrieveInterperter
12
+ from .link_retrieve_interpreter import LinkRetrieveInterperter
13
+
14
+ # output
15
+ from .applescript_interpreter import AppleScriptInterpreter
16
+ from .file_interpreter import FileInterpreter
17
+ from .python_interpreter import SyncPythonInterpreter
18
+ from .python_interpreter import AsyncPythonInterpreter
19
+ from .shell_interpreter import ShellInterpreter
20
+ from .ui_interpreter import UIInterpreter
21
+ class PythonInterpreter(AsyncPythonInterpreter):
22
+ pass
GeneralAgent/interpreter/applescript_interpreter.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from .interpreter import Interpreter
3
+ from GeneralAgent.utils import confirm_to_run
4
+
5
+ applescript_promt = """
6
+ # Run applescript
7
+ * Here are the commands
8
+ ```applescript
9
+ <applescript_command>
10
+ ```
11
+ * the command will be executed if in macOS computer.
12
+ """
13
+
14
+ class AppleScriptInterpreter(Interpreter):
15
+
16
+ output_match_pattern = '```(\n)?applescript(.*?)\n```'
17
+ output_match_start_pattern = '```(\n)?applescript'
18
+
19
+ async def prompt(self, messages) -> str:
20
+ return applescript_promt
21
+
22
+ async def output_parse(self, string) -> (str, bool):
23
+ pattern = re.compile(self.output_match_pattern, re.DOTALL)
24
+ match = pattern.search(string)
25
+ assert match is not None
26
+ if confirm_to_run():
27
+ sys_out = self._run_applescript(match.group(2))
28
+ return sys_out.strip(), False
29
+ else:
30
+ return '', False
31
+
32
+ def _run_applescript(self, content):
33
+ content = content.replace('"', '\\"')
34
+ sys_out = ''
35
+ import subprocess
36
+ try:
37
+ p = subprocess.Popen('osascript -e "{}"'.format(content), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
38
+ except:
39
+ pass
40
+ finally:
41
+ sys_out, err = p.communicate()
42
+ sys_out = sys_out.decode('utf-8')
43
+ sys_out = sys_out.strip()
44
+ if sys_out == '':
45
+ sys_out = 'run successfully'
46
+ return sys_out
GeneralAgent/interpreter/embedding_retrieve_interpreter.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # read the document and can retrieve the information
2
+ import re
3
+ from .interpreter import Interpreter
4
+ import chromadb
5
+ import logging
6
+
7
+ class EmbeddingRetrieveInterperter(Interpreter):
8
+ """
9
+ EmbeddingRetrieveInterperter can retrieve the information from the memory by embedding.
10
+ """
11
+
12
+ input_match_pattern = '```read\n(.*?)\n```'
13
+
14
+ def __init__(self, serialize_path='./read_data/', prompt_max_length=1000, useful_msg_count=2) -> None:
15
+ self.prompt_max_length = prompt_max_length
16
+ self.useful_msg_count = useful_msg_count
17
+ self.client = chromadb.PersistentClient(path=serialize_path)
18
+ self.collection = self.client.get_or_create_collection(name="read", metadata={"hnsw:space": "cosine"})
19
+
20
+ async def prompt(self, messages) -> str:
21
+ from GeneralAgent import skills
22
+ # when collection is empty, return empty string
23
+ if self.collection.count() == 0:
24
+ return ''
25
+
26
+ querys = []
27
+ for x in messages[-self.useful_msg_count:]:
28
+ querys += skills.split_text(x['content'], 200)
29
+ query_embeddings = skills.embedding_batch(querys)
30
+ result = self.collection.query(
31
+ query_embeddings=query_embeddings,
32
+ n_results=2,
33
+ )
34
+ # extract distances and documents
35
+ distances = [x for z in result['distances'] for x in z]
36
+ documents = [x for z in result['documents'] for x in z]
37
+
38
+ # sort distances and documents by distance
39
+ sorted_docs = sorted(list(zip(distances, documents)), key=lambda x: x[0])
40
+
41
+ # filter documents with distance < 100
42
+ documents = [x for d, x in sorted_docs if d < 100]
43
+ # texts token count should less than prompt_max_length
44
+ texts = []
45
+ texts_token_count = 0
46
+ for x in documents:
47
+ if texts_token_count + skills.string_token_count(x) > self.prompt_max_length:
48
+ break
49
+ texts.append(x)
50
+ return '\n'.join(texts)
51
+
52
+ async def input_parse(self, string) -> (str, bool):
53
+ from GeneralAgent import skills
54
+ information = []
55
+ pattern = re.compile(self.input_match_pattern, re.DOTALL)
56
+ match = pattern.search(string)
57
+ assert match is not None
58
+ file_paths = match.group(1).strip().split('\n')
59
+ for file_path in file_paths:
60
+ paragraphs = skills.split_text(skills.read_file_content(file_path), max_token=300)
61
+ if len(paragraphs) > 0:
62
+ information.append(f'The content of file {file_path} is: ' + '\n'.join(paragraphs)[:100] + '\n......')
63
+ embeddings = skills.embedding_batch(paragraphs)
64
+ # logging.debug(paragraphs[:2])
65
+ # logging.debug(embeddings[:2])
66
+ self.collection.add(
67
+ documents=paragraphs,
68
+ embeddings=embeddings,
69
+ metadatas=[{'file_path': file_path} for _ in paragraphs],
70
+ ids=[file_path+str(i) for i in range(len(paragraphs))],
71
+ )
72
+ stop = False
73
+ if string.replace(match.group(0), '').strip() == '':
74
+ stop = True
75
+ info = '\n'.join(information)
76
+ string = f'\n{string}\n```{info}```\n'
77
+ return string, stop
GeneralAgent/interpreter/file_interpreter.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re, os
2
+ import logging
3
+ from .interpreter import Interpreter
4
+
5
+ file_prompt = """
6
+ # For file operations, ALWAYS enclose your commands in triple backticks (```). Here are the commands:
7
+
8
+ 1. Write:
9
+ ```file
10
+ <file_path> write <start_line> <end_line> <<EOF
11
+ <content>
12
+ EOF
13
+ ```
14
+ 2. Read:
15
+ ```file
16
+ <file_path> read <start_line> <end_line>
17
+ ```
18
+ 3. Delete:
19
+ ```file
20
+ <file_path> delete <start_line> <end_line>
21
+ ```
22
+
23
+ Line numbers start from 0, and -1 is the last line.
24
+ Read will print the content of the file with [line numbers] prefixed.
25
+ """
26
+
27
+
28
+ class FileInterpreter(Interpreter):
29
+
30
+ output_match_pattern = '```(\n)?file(\n| )?(.*?) (write|read|delete) (-?\d+) (-?\d+)(.*?)```'
31
+
32
+ async def prompt(self, messages) -> str:
33
+ return file_prompt
34
+
35
+ def _parse_commands(self, string):
36
+ match = re.search(self.output_match_pattern, string, re.DOTALL)
37
+ assert match is not None
38
+ file_path = match.group(3)
39
+ operation = match.group(4)
40
+ start_line = int(match.group(5))
41
+ end_line = int(match.group(6))
42
+ content = match.group(7).strip()
43
+ if content.startswith('<<EOF'):
44
+ content = content[5:].strip()
45
+ if content.endswith('EOF'):
46
+ content = content[:-3].strip()
47
+ return file_path, operation, start_line, end_line, content
48
+
49
+ async def output_parse(self, string) -> (str, bool):
50
+ logging.debug('FileInterpreter:parse called')
51
+ file_path, operation, start_line, end_line, content = self._parse_commands(string)
52
+ is_stop = True
53
+ if operation == 'write':
54
+ self._write_file(file_path, content, start_line, end_line)
55
+ return f'Content write to {file_path} successfully\n', is_stop
56
+ elif operation == 'delete':
57
+ self._delete_file(file_path, start_line, end_line)
58
+ return f'Delete lines of {file_path} successfully\n', is_stop
59
+ elif operation == 'read':
60
+ content = self._read_file(file_path, start_line, end_line)
61
+ return f'Read {file_path} succesfully, the content is below: \n\n```\n{content}\n```\n', is_stop
62
+
63
+ def _write_file(self, file_path, content, start_index, end_index):
64
+ # if .py file, remove ```python and ``` pair
65
+ if file_path.endswith('.py'):
66
+ content = content.replace('```python', '')
67
+ content = content.replace('```', '')
68
+ # file_path = os.path.join(self.workspace, file_path)
69
+ dir_path = os.path.dirname(file_path)
70
+ if not os.path.exists(dir_path):
71
+ os.makedirs(dir_path)
72
+ if not os.path.exists(file_path):
73
+ with open(file_path, 'w', encoding='utf-8') as f:
74
+ f.write('')
75
+ with open(file_path, 'r', encoding='utf-8') as f:
76
+ lines = f.readlines()
77
+ if start_index == -1:
78
+ start_index = len(lines)
79
+ if end_index == -1:
80
+ end_index = len(lines)
81
+ lines = lines[:start_index] + [content] + lines[end_index+1:]
82
+ with open(file_path, 'w', encoding='utf-8') as f:
83
+ f.writelines(lines)
84
+
85
+ def _delete_file(self, file_path, start_index, end_index):
86
+ # file_path = os.path.join(self.workspace, file_path)
87
+ if not os.path.exists(file_path):
88
+ with open(file_path, 'w', encoding='utf-8') as f:
89
+ f.write('')
90
+ with open(file_path, 'r', encoding='utf-8') as f:
91
+ lines = f.readlines()
92
+ if start_index == -1:
93
+ start_index = len(lines)
94
+ if end_index == -1:
95
+ end_index = len(lines)
96
+ lines = lines[:start_index] + lines[end_index+1:]
97
+ with open(file_path, 'w', encoding='utf-8') as f:
98
+ f.writelines(lines)
99
+
100
+ def _read_file(self, file_path, start_index, end_index):
101
+ # file_path = os.path.join(self.workspace, file_path)
102
+ if not os.path.exists(file_path):
103
+ with open(file_path, 'w', encoding='utf-8') as f:
104
+ f.write('')
105
+ with open(file_path, 'r', encoding='utf-8') as f:
106
+ lines = f.readlines()
107
+ if start_index == -1:
108
+ start_index = len(lines)
109
+ if end_index == -1:
110
+ end_index = len(lines)
111
+ content = ''
112
+ end_index = min(end_index + 1, len(lines))
113
+ for index in range(start_index, end_index):
114
+ new_add = f'[{index}]{lines[index]}'
115
+ if len(content + new_add) > 2000:
116
+ left_count = len(lines) - index
117
+ content += f'...\n[there are {left_count} lines left]'
118
+ break
119
+ content += new_add
120
+ return content.strip()
GeneralAgent/interpreter/interpreter.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Interpreter
2
+ import abc
3
+ import re
4
+
5
+ class Interpreter(metaclass=abc.ABCMeta):
6
+ """
7
+ Interpreter is the base class for all interpreters.
8
+ input_match_pattern is the pattern to match the LLM input string. for example ```tsx\n(.*?)\n```
9
+ output_match_pattern is the pattern to match the LLM ouput string. for example ```tsx\n(.*?)\n```
10
+ output_match_start_pattern is the pattern to match the start of the LLM output string. for example: ```tsx\n
11
+ outptu_parse_done_recall is the callback function to recall when the output_parse is done
12
+ """
13
+ input_match_pattern = None
14
+ output_match_pattern = None
15
+ output_match_start_pattern = None
16
+ outptu_parse_done_recall = None
17
+
18
+ async def prompt(self, messages) -> str:
19
+ """
20
+ :param messages: list of messages
21
+ :return: string
22
+ """
23
+ return ''
24
+
25
+ def input_match(self, string) -> bool:
26
+ if self.input_match_pattern is None:
27
+ return False
28
+ match = re.compile(self.input_match_pattern, re.DOTALL).search(string)
29
+ if match is not None:
30
+ return True
31
+ else:
32
+ return False
33
+
34
+ def output_match(self, string) -> bool:
35
+ if self.output_match_pattern is None:
36
+ return False
37
+ match = re.compile(self.output_match_pattern, re.DOTALL).search(string)
38
+ if match is not None:
39
+ return True
40
+ else:
41
+ return False
42
+
43
+ def output_match_start(self, string) -> (bool, str):
44
+ """
45
+ return is_match, string_matched
46
+ """
47
+ if self.output_match_start_pattern is None:
48
+ return False, ''
49
+ match = re.compile(self.output_match_start_pattern, re.DOTALL).search(string)
50
+ if match is not None:
51
+ string_matched = string[match.start():]
52
+ return True, string_matched
53
+ else:
54
+ return False, ''
55
+
56
+ def output_match_end(self, string) -> (bool, str):
57
+ """
58
+ return is_match, string_matched
59
+ """
60
+ if self.output_match_pattern is None:
61
+ return False, ''
62
+ match = re.compile(self.output_match_pattern, re.DOTALL).search(string)
63
+ if match is not None:
64
+ string_left = string[match.end():]
65
+ return True, string_left
66
+ else:
67
+ return False, ''
68
+
69
+ async def input_parse(self, string) -> (str, bool):
70
+ """
71
+ parse the input、output string, and return the output string and is_stop
72
+ """
73
+ return '', False
74
+
75
+ async def output_parse(self, string) -> (str, bool):
76
+ """
77
+ parse the input、output string, and return the output string and is_stop
78
+ """
79
+ return '', False
80
+
GeneralAgent/interpreter/link_retrieve_interpreter.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # read the document and can retrieve the information
2
+ import re
3
+ from .interpreter import Interpreter
4
+ from GeneralAgent.memory import LinkMemory
5
+
6
+
7
+ class LinkRetrieveInterperter(Interpreter):
8
+ """
9
+ LinkRetrieveInterperter store and retrieve the information from the memory by link embed in the document. like I live in <<My Home>>.
10
+ LinkRetrieveInterperter handle input string like this:
11
+ ```read
12
+ path/to/file1.pdf
13
+ path/to/file2.pdf
14
+ ```
15
+ """
16
+
17
+ input_match_pattern = '```read\n(.*?)\n```'
18
+
19
+ def __init__(self, python_interpreter=None, sparks_dict_name='sparks'):
20
+ self.python_intrepreter = python_interpreter
21
+ self.sparks_dict_name = sparks_dict_name
22
+ self.link_memory = LinkMemory()
23
+
24
+ async def prompt(self, messages) -> str:
25
+ if self.link_memory.is_empty():
26
+ return None
27
+ else:
28
+ access_prompt = f"""
29
+ In Python, You can access the values of <<key>> in all documents through the dictionary {self.sparks_dict_name}, such as <<Hello world>>:
30
+ ```
31
+ print({self.sparks_dict_name}['Hello world'])
32
+ ```
33
+ """
34
+ return await 'Background Information: \n' + self.link_memory.get_memory(messages) + access_prompt
35
+
36
+ async def input_parse(self, string) -> (str, bool):
37
+ from GeneralAgent import skills
38
+ pattern = re.compile(self.input_match_pattern, re.DOTALL)
39
+ match = pattern.search(string)
40
+ assert match is not None
41
+ file_paths = match.group(1).strip().split('\n')
42
+ result = ''
43
+ async def output_callback(token):
44
+ nonlocal result
45
+ if token is not None:
46
+ result += token
47
+ for file_path in file_paths:
48
+ content = skills.read_file_content(file_path)
49
+ await self.link_memory.add_memory(content, output_callback=output_callback)
50
+ self._update_python_variables()
51
+ return string + '\n' + result, True
52
+
53
+ def _update_python_variables(self):
54
+ if self.python_intrepreter is not None:
55
+ nodes = self.link_memory.concepts.values()
56
+ sparks_dict = dict(zip([node.key for node in nodes], [node.content for node in nodes]))
57
+ self.python_intrepreter.set_variable(self.sparks_dict_name, sparks_dict)
GeneralAgent/interpreter/plan_interpreter.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from collections import OrderedDict
3
+ from .interpreter import Interpreter
4
+ from GeneralAgent.memory import StackMemoryNode
5
+
6
+
7
+ class PlanInterpreter(Interpreter):
8
+ """
9
+ PlanInterperter is used to parse the plan structure.
10
+ PlanInterpreter handle the input string like this:
11
+ ```runplan
12
+ section1
13
+ section1.1
14
+ ```
15
+ Note: this is only for StackAgent and StackMemory.
16
+ """
17
+ input_match_pattern = '```runplan(.*?)?\n(.*?)\n```'
18
+
19
+ def __init__(self, memory, max_plan_depth=4) -> None:
20
+ self.memory = memory
21
+ self.max_plan_depth = max_plan_depth
22
+
23
+ async def prompt(self, messages) -> str:
24
+ return ''
25
+
26
+ async def input_parse(self, string) -> (str, bool):
27
+ pattern = re.compile(self.input_match_pattern, re.DOTALL)
28
+ match = pattern.search(string)
29
+ assert match is not None
30
+ prefix = match.group(1).strip()
31
+ structure_data = match.group(2).strip()
32
+ plan_dict = self._structure_plan(structure_data)
33
+ current_node = self.memory.current_node
34
+ self._add_plans_for_node(current_node, plan_dict, prefix)
35
+ return string, False
36
+
37
+ def _add_plans_for_node(self, node:StackMemoryNode, plan_dict, prefix):
38
+ if self.memory.get_node_level(node) >= self.max_plan_depth:
39
+ return
40
+ for k, v in plan_dict.items():
41
+ new_node = StackMemoryNode(role='system', action='plan', content=k.strip(), prefix=prefix)
42
+ self.memory.add_node_in(node, new_node)
43
+ if len(v) > 0:
44
+ self._add_plans_for_node(new_node, v, prefix)
45
+
46
+ @classmethod
47
+ def _structure_plan(cls, data):
48
+ structured_data = OrderedDict()
49
+ current_section = [structured_data]
50
+ for line in data.split('\n'):
51
+ if not line.strip():
52
+ continue
53
+ depth = line.count(' ')
54
+ section = line.strip()
55
+ while depth < len(current_section) - 1:
56
+ current_section.pop()
57
+ current_section[-1][section] = OrderedDict()
58
+ current_section.append(current_section[-1][section])
59
+ return structured_data
GeneralAgent/interpreter/python_interpreter.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re, io, os, sys
2
+ import pickle
3
+ import logging
4
+ from jinja2 import Template
5
+ from .interpreter import Interpreter
6
+ from GeneralAgent.utils import confirm_to_run
7
+ from GeneralAgent import skills
8
+ import asyncio
9
+
10
+ default_import_code = """
11
+ import os, sys, math
12
+ from GeneralAgent import skills
13
+ """
14
+
15
+ # default_libs = ' '.join(["requests", "tinydb", "openai", "jinja2", "numpy", "bs4", "playwright", "retrying", "pymupdf", "python-pptx", "python-docx", "yfinance"])
16
+ # default_libs = skills.get_current_env_python_libs()
17
+ default_libs = ''
18
+
19
+ # from GeneralAgent.tools import Tools
20
+
21
+ class SyncPythonInterpreter(Interpreter):
22
+ """
23
+ Sync Python Interpreter: run python code in the interpreter. Not same namespace with the agent & Can Only run synchronous code
24
+ """
25
+
26
+ output_match_start_pattern = '```python\n'
27
+ output_match_pattern = '```python\n(.*?)\n```'
28
+
29
+ python_prompt_template = """
30
+ # Run python
31
+ * format is : ```python\\nthe_code\\n```
32
+ * the code will be executed
33
+ * python version is {{python_version}}
34
+ * only write synchronous code
35
+ * The output display should be limited in length and should be truncated when displaying characters whose length is unknown. for example: print(a[:100])
36
+ * * Pickleable objects can be shared between different codes and variables
37
+ * Available libraries: {{python_libs}}
38
+ * The following functions can be used in code (already implemented and imported for you):
39
+ ```
40
+ {{python_funcs}}
41
+ ```
42
+ """
43
+
44
+ function_tools = []
45
+
46
+ def __init__(self,
47
+ serialize_path:str=None,
48
+ libs: str=default_libs,
49
+ import_code:str=None,
50
+ prompt_append='',
51
+ stop_wrong_count = 2
52
+ ):
53
+ """
54
+ Args:
55
+ serialize_path (str): path to save the global variables, default None, which means not save, like './serialized.bin'
56
+ libs ([str], optional): libraries can be to used. Defaults to skills.get_current_env_python_libs()
57
+ import_code (str, optional): code to import. The tools used should be imported. Defaults to default_import_code.
58
+ prompt_append: append to the prompt, custom prompt can be added here
59
+ stop_wrong_count: stop running when the code is wrong for stop_wrong_count times
60
+ """
61
+ from GeneralAgent import skills
62
+ self.globals = {} # global variables shared by all code
63
+ self.python_libs = libs
64
+ self.import_code = import_code or default_import_code
65
+ self.serialize_path = serialize_path
66
+ self.prompt_append = prompt_append
67
+ # self.tools = tools or Tools([])
68
+ self.globals = self.load()
69
+ # count the number of times the code is wrong, and stop running when it reaches the threshold
70
+ self.run_wrong_count = 0
71
+ self.stop_wrong_count = stop_wrong_count
72
+
73
+ def load(self):
74
+ if self.serialize_path is None:
75
+ return {}
76
+ if os.path.exists(self.serialize_path):
77
+ with open(self.serialize_path, 'rb') as f:
78
+ data = pickle.loads(f.read())
79
+ return data['globals']
80
+ return {}
81
+
82
+ async def prompt(self, messages) -> str:
83
+ from GeneralAgent import skills
84
+ funtions = '\n\n'.join([skills.get_function_signature(x) for x in self.function_tools])
85
+ variables = {
86
+ 'python_libs': self.python_libs,
87
+ 'python_funcs': funtions,
88
+ 'python_version': skills.get_python_version()
89
+ }
90
+ return Template(self.python_prompt_template).render(**variables) + self.prompt_append
91
+
92
+ def save(self):
93
+ if self.serialize_path is None:
94
+ return
95
+ self._remove_unpickleable()
96
+ # save
97
+ with open(self.serialize_path, 'wb') as f:
98
+ data = {'globals': self.globals}
99
+ f.write(pickle.dumps(data))
100
+
101
+ def _remove_unpickleable(self):
102
+ if '__builtins__' in self.globals:
103
+ self.globals.__delitem__('__builtins__')
104
+ keys = list(self.globals.keys())
105
+ for key in keys:
106
+ try:
107
+ pickle.dumps(self.globals[key])
108
+ except Exception as e:
109
+ self.globals.__delitem__(key)
110
+
111
+ async def output_parse(self, string) -> (str, bool):
112
+ sys_out = ''
113
+ pattern = re.compile(self.output_match_pattern, re.DOTALL)
114
+ match = pattern.search(string)
115
+ assert match is not None
116
+ if confirm_to_run():
117
+ sys_out, stop = await self.run_code(match.group(1))
118
+ result = 'python runs result:\n' + sys_out.strip()
119
+ return result, stop
120
+ else:
121
+ return '', False
122
+
123
+ async def run_code(self, code):
124
+ stop = False
125
+ code = self.add_print(code)
126
+ code = self.import_code + '\n' + code
127
+ globals_backup = self.load()
128
+ logging.debug(code)
129
+ sys_stdout = ''
130
+ output = io.StringIO()
131
+ sys.stdout = output
132
+ success = False
133
+ try:
134
+ exec(code, self.globals)
135
+ success = True
136
+ self.run_wrong_count = 0
137
+ except Exception as e:
138
+ import traceback
139
+ sys_stdout += traceback.format_exc()
140
+ self.globals = globals_backup
141
+ self.run_wrong_count += 1
142
+ if self.run_wrong_count >= self.stop_wrong_count:
143
+ stop = True
144
+ finally:
145
+ sys_stdout += output.getvalue()
146
+ sys.stdout = sys.__stdout__
147
+ if success:
148
+ self.save()
149
+ sys_stdout = sys_stdout.strip()
150
+ if sys_stdout == '':
151
+ sys_stdout = 'run successfully'
152
+ return sys_stdout, stop
153
+
154
+ def get_variable(self, name):
155
+ if name in self.globals:
156
+ return self.globals[name]
157
+ else:
158
+ logging.warning(f"Variable {name} not found")
159
+ return None
160
+
161
+ def set_variable(self, name, value):
162
+ self.globals[name] = value
163
+ self.save()
164
+
165
+ @classmethod
166
+ def add_print_old(cls, code_string):
167
+ pattern = r'^(\s*)(\w+)(\s*)$'
168
+ lines = code_string.split('\n')
169
+ for i, line in enumerate(lines):
170
+ keywords = ['False' 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
171
+ if line.strip() in keywords:
172
+ continue
173
+ match = re.match(pattern, line)
174
+ if match:
175
+ lines[i] = f'{match.group(1)}print({match.group(2)}){match.group(3)}'
176
+ return '\n'.join(lines)
177
+
178
+ @classmethod
179
+ def add_print(cls, code_string):
180
+ from GeneralAgent import skills
181
+ code = code_string.strip()
182
+ lines = code.split('\n')
183
+ if len(lines) > 0:
184
+ last_line = lines[-1]
185
+ if skills.python_line_is_variable_expression(last_line):
186
+ last_line = f'print({last_line})'
187
+ lines[-1] = last_line
188
+ return '\n'.join(lines)
189
+
190
+ def _remove_unpickleable(namespace):
191
+ import pickle
192
+ if '__builtins__' in namespace:
193
+ namespace.__delitem__('__builtins__')
194
+ keys = list(namespace.keys())
195
+ for key in keys:
196
+ try:
197
+ pickle.dumps(namespace[key])
198
+ except Exception as e:
199
+ namespace.__delitem__(key)
200
+ return namespace
201
+
202
+
203
+ def code_wrap(code, namespace):
204
+ lines = code.split('\n')
205
+ code = '\n '.join(lines)
206
+ variables = '\n '.join([f'{name} = globals()[\'{name}\']' for name, value in namespace.items()])
207
+ content = f"""
208
+ import asyncio
209
+
210
+ def _remove_unpickleable(namespace):
211
+ import pickle
212
+ if '__builtins__' in namespace:
213
+ namespace.__delitem__('__builtins__')
214
+ keys = list(namespace.keys())
215
+ for key in keys:
216
+ try:
217
+ pickle.dumps(namespace[key])
218
+ except Exception as e:
219
+ namespace.__delitem__(key)
220
+ for name in ['__name', '__value', '__namespace']:
221
+ if name in namespace:
222
+ namespace.__delitem__(name)
223
+ return namespace
224
+
225
+ __namespace = None
226
+
227
+ async def __main():
228
+ {variables}
229
+ {code}
230
+ global __namespace
231
+ __namespace = _remove_unpickleable(locals().copy())
232
+ """
233
+ # print('----------<code>--------')
234
+ # print(content)
235
+ # print('----------</code>--------')
236
+ return content
237
+
238
+
239
+ class AsyncPythonInterpreter(SyncPythonInterpreter):
240
+ """
241
+ Sync Python Interpreter: run python code in the interpreter. Same namespace with the agent & Can run async code
242
+ """
243
+
244
+ python_prompt_template = """
245
+ # Run python
246
+ - format is : ```python\\nthe_code\\n```
247
+ - the code will be executed
248
+ - python version is {{python_version}}
249
+ - Pickleable objects can be shared between different codes and variables
250
+ - The output display should be limited in length and should be truncated when displaying characters whose length is unknown. for example: print(a[:100])
251
+ - Available libraries: {{python_libs}}
252
+ Complete the entire process in one code instead of writing multiple codes to run step by step. For example, the following code is allowed:
253
+ ```python
254
+ # step 1
255
+ a = fun1(xx)
256
+ # step 2
257
+ c = fun2(a)
258
+ # step 3
259
+ d = fun3(c)
260
+ ...
261
+ ```
262
+ - The following functions can be used in code (already implemented and imported for you):
263
+ ```
264
+ {{python_funcs}}
265
+ ```
266
+ """
267
+
268
+ async def run_code(self, code):
269
+ stop = False
270
+ code = self.add_print(code)
271
+ code = code_wrap(code, self.globals)
272
+ code = self.import_code + '\n' + code
273
+ # print('hello')
274
+ # print(code)
275
+ # print(self.function_tools)
276
+ globals_backup = self.load()
277
+ logging.debug(code)
278
+ sys_stdout = ''
279
+ output = io.StringIO()
280
+ sys.stdout = output
281
+ success = False
282
+ try:
283
+ # exec(code, self.globals)
284
+ # run async python code
285
+ local_vars = self.globals
286
+ # register functions
287
+ for fun in self.function_tools:
288
+ local_vars[fun.__name__] = fun
289
+ # print(local_vars)
290
+ exec(code, local_vars, local_vars)
291
+ main_function = local_vars['__main']
292
+ await asyncio.create_task(main_function())
293
+ local_vars = _remove_unpickleable(local_vars)
294
+ local_vars = local_vars['__namespace']
295
+ # remove functions
296
+ for fun in self.function_tools:
297
+ if fun.__name__ in local_vars:
298
+ local_vars.__delitem__(fun.__name__)
299
+ self.globals = local_vars
300
+
301
+ success = True
302
+ self.run_wrong_count = 0
303
+ except Exception as e:
304
+ import traceback
305
+ sys_stdout += traceback.format_exc()
306
+ self.globals = globals_backup
307
+ logging.exception((e))
308
+ self.run_wrong_count += 1
309
+ if self.run_wrong_count >= self.stop_wrong_count:
310
+ stop = True
311
+ finally:
312
+ sys_stdout += output.getvalue()
313
+ sys.stdout = sys.__stdout__
314
+ if success:
315
+ self.save()
316
+ sys_stdout = sys_stdout.strip()
317
+ if sys_stdout == '':
318
+ sys_stdout = 'run successfully'
319
+ return sys_stdout, stop
320
+
321
+
322
+ # class PythonInterpreter(AsyncPythonInterpreter):
323
+ # """
324
+ # Sync Python Interpreter: run python code in the interpreter. Same namespace with the agent & Can run async code
325
+ # """
326
+ # pass
GeneralAgent/interpreter/role_interpreter.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import datetime
3
+ import platform
4
+ from jinja2 import Template
5
+ from .interpreter import Interpreter
6
+
7
+
8
+ class RoleInterpreter(Interpreter):
9
+ """
10
+ RoleInterpreter, a interpreter that can change the role of the agent.
11
+ Note: This should be the first interpreter in the agent.
12
+ """
13
+
14
+ system_prompt_template = \
15
+ """
16
+ Now: {{now}}
17
+ You are GeneralAgent, a agent on the {{os_version}} computer to help the user solve the problem.
18
+ Remember, you can control the computer and access the internet.
19
+ Reponse message in markdown format to user. for example file a.txt, you should reponse [title](a.txt)
20
+ You can use the following skills (start with # ) to help you solve the problem directly without explain, without ask for permission.
21
+ """
22
+
23
+ def __init__(self, system_prompt=None) -> None:
24
+ from GeneralAgent import skills
25
+ self.os_version = skills.get_os_version()
26
+ self.system_prompt = system_prompt
27
+
28
+ async def prompt(self, messages) -> str:
29
+ if self.system_prompt is not None:
30
+ return self.system_prompt
31
+ if os.environ.get('LLM_CACHE', 'no') in ['yes', 'y', 'YES']:
32
+ now = '2023-09-27 00:00:00'
33
+ else:
34
+ now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
35
+ data = {
36
+ 'now': now,
37
+ 'os_version': self.os_version
38
+ }
39
+ the_prompt = Template(self.system_prompt_template).render(**data)
40
+ return the_prompt
GeneralAgent/interpreter/shell_interpreter.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from .interpreter import Interpreter
3
+ from GeneralAgent.utils import confirm_to_run
4
+
5
+ shell_prompt = """
6
+ # Run shell
7
+ * format is : ```shell\\nthe_command\\n```
8
+ * the command will be executed
9
+ """
10
+
11
+ class ShellInterpreter(Interpreter):
12
+
13
+ output_match_start_pattern = '```shell\n'
14
+ output_match_pattern = '```shell\n(.*?)\n```'
15
+
16
+ def __init__(self, workspace='./') -> None:
17
+ self.workspace = workspace
18
+
19
+ async def prompt(self, messages) -> str:
20
+ return shell_prompt
21
+
22
+ async def output_parse(self, string) -> (str, bool):
23
+ pattern = re.compile(self.output_match_pattern, re.DOTALL)
24
+ match = pattern.search(string)
25
+ assert match is not None
26
+ if confirm_to_run():
27
+ output = self._run_bash(match.group(1))
28
+ return output.strip(), False
29
+ else:
30
+ return '', False
31
+
32
+ def _run_bash(self, content):
33
+ sys_out = ''
34
+ import subprocess
35
+ if 'python ' in content:
36
+ content = content.replace('python ', 'python3 ')
37
+ try:
38
+ p = subprocess.Popen(content, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
39
+ except:
40
+ pass
41
+ finally:
42
+ sys_out, err = p.communicate()
43
+ sys_out = sys_out.decode('utf-8')
44
+ return sys_out
GeneralAgent/interpreter/ui_interpreter.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from .interpreter import Interpreter
3
+ import asyncio
4
+
5
+ class UIInterpreter(Interpreter):
6
+
7
+ output_match_start_pattern = '```tsx\n'
8
+ output_match_pattern = '```tsx\n(.*?)\n```'
9
+
10
+ ui_prompt = """
11
+ # Send UI to user
12
+ Use the following tsx architecture to write a React component. The component will be compiled into a UI and sent to the user. The user's input can be sent to you through the save_data function.
13
+ ```tsx
14
+ const React = (window as any).React;
15
+ const antd = (window as any).antd;
16
+
17
+ const [Form, Input, Button] = [antd.Form, antd.Input, antd.Button];
18
+
19
+ const LibTemplate = ({save_data}: {save_data: (data:any)=>void}) => {
20
+ // use save_data to save the data
21
+ }
22
+
23
+ export default LibTemplate;
24
+ ```
25
+ """
26
+
27
+ def __init__(self, send_ui, output_callback, workspace=None) -> None:
28
+ """
29
+ :param send_ui: the async function to send ui to user
30
+ :param workspace: workspace for the interpreter
31
+ """
32
+ self.send_ui = send_ui
33
+ self.output_callback = output_callback
34
+ self.workspace = workspace
35
+
36
+ async def prompt(self, messages) -> str:
37
+ return self.ui_prompt
38
+
39
+ async def output_parse(self, string) -> (str, bool):
40
+ from GeneralAgent import skills
41
+ pattern = re.compile(self.output_match_pattern, re.DOTALL)
42
+ match = pattern.search(string)
43
+ assert match is not None
44
+ code = match.group(1)
45
+ lib_name, js_path = skills.parse_tsx_to_ui(code, save_dir=self.workspace)
46
+ # Terminate the output callback
47
+ await self.output_callback(None)
48
+ # Send UI to user
49
+ await self.send_ui(lib_name, js_path)
50
+ print('Send UI to user successfuly.')
51
+ return '', True
GeneralAgent/memory/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # import
2
+ from .normal_memory import NormalMemory
3
+ from .stack_memory import StackMemory, StackMemoryNode
4
+ from .link_memory import LinkMemory, LinkMemoryNode
GeneralAgent/memory/link_memory.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import List
3
+ from tinydb import TinyDB, Query
4
+ import asyncio
5
+
6
+ @dataclass
7
+ class LinkMemoryNode:
8
+ key: str
9
+ content: str
10
+ childrens: List[str] = None
11
+ parents: List[str] = None
12
+
13
+ def __post_init__(self):
14
+ self.childrens = self.childrens if self.childrens else []
15
+ self.parents = self.parents if self.parents else []
16
+
17
+ def __str__(self):
18
+ return f'<<{self.key}>>\n{self.content}'
19
+
20
+ def __repr__(self):
21
+ return str(self)
22
+
23
+
24
+ async def summarize_and_segment(text, output_callback=None):
25
+ from GeneralAgent import skills
26
+ summary = await skills.summarize_text(text)
27
+ if output_callback is not None:
28
+ await output_callback(f'Summary: {summary}\n')
29
+ segments = await skills.segment_text(text)
30
+ if output_callback is not None:
31
+ for key in segments:
32
+ await output_callback(f'<<{key}>>\n')
33
+ return summary, segments
34
+
35
+
36
+ class LinkMemory():
37
+ def __init__(self, serialize_path='./link_memory.json', short_memory_limit=2000) -> None:
38
+ self.serialize_path = serialize_path
39
+ self.short_memory_limit = short_memory_limit
40
+ self.db = TinyDB(serialize_path)
41
+ nodes = [LinkMemoryNode(**x) for x in self.db.all()]
42
+ self.concepts = dict(zip([node.key for node in nodes], nodes))
43
+ self.short_memory = ''
44
+ self._load_short_memory()
45
+
46
+ def is_empty(self):
47
+ return len(self.concepts) == 0
48
+
49
+ async def add_memory(self, content, output_callback=None):
50
+ from GeneralAgent import skills
51
+ # await self._oncurrent_summarize_content(content, output_callback)
52
+ await self._summarize_content(content, output_callback)
53
+ while skills.string_token_count(self.short_memory) > self.short_memory_limit:
54
+ content = self.short_memory
55
+ self.short_memory = ''
56
+ # await self._oncurrent_summarize_content(content, output_callback)
57
+ await self._summarize_content(content, output_callback)
58
+
59
+ async def get_memory(self, messages=None):
60
+ from GeneralAgent import skills
61
+ if len(self.concepts) == 0:
62
+ return ''
63
+ if messages is None:
64
+ return self.short_memory
65
+ else:
66
+ messages = skills.cut_messages(messages, 1000)
67
+ xx = self.short_memory.split('\n')
68
+ background = '\n'.join([f'#{line} {xx[line]}' for line in range(len(xx))])
69
+ task = '\n'.join([f'{x["role"]}: {x["content"]}' for x in messages])
70
+ info = await skills.extract_info(background, task)
71
+ line_numbers, keys = skills.parse_extract_info(info)
72
+ result = []
73
+ for line_number in line_numbers:
74
+ if line_number < len(xx) and line_number >= 0:
75
+ result.append(xx[line_number])
76
+ for key in keys:
77
+ if key in self.concepts:
78
+ result.append(f'{key}\n{self.concepts[key]}\n')
79
+ return '\n'.join(result)
80
+
81
+ def _load_short_memory(self):
82
+ short_memorys = self.db.table('short_memory').all()
83
+ self.short_memory = '' if len(short_memorys) == 0 else short_memorys[0]['content']
84
+
85
+ def _save_short_memory(self):
86
+ self.db.table('short_memory').truncate()
87
+ self.db.table('short_memory').insert({'content': self.short_memory})
88
+
89
+ async def _oncurrent_summarize_content(self, input):
90
+ from GeneralAgent import skills
91
+ inputs = skills.split_text(input, max_token=3000)
92
+ print('splited count: ', len(inputs))
93
+ coroutines = [summarize_and_segment(x) for x in inputs]
94
+ results = await asyncio.gather(*coroutines)
95
+ for summary, nodes in results:
96
+ new_nodes = {}
97
+ for key in nodes:
98
+ new_key = self._add_node(key, nodes[key])
99
+ new_nodes[new_key] = nodes[key]
100
+ self.short_memory += '\n' + summary + ' Detail in ' + ', '.join([f'<<{key}>>' for key in new_nodes])
101
+ self.short_memory = self.short_memory.strip()
102
+ self._save_short_memory()
103
+
104
+ async def _summarize_content(self, input, output_callback=None):
105
+ from GeneralAgent import skills
106
+ inputs = skills.split_text(input, max_token=3000)
107
+ for text in inputs:
108
+ summary, nodes = await summarize_and_segment(text, output_callback)
109
+ new_nodes = {}
110
+ for key in nodes:
111
+ new_key = self._add_node(key, nodes[key])
112
+ new_nodes[new_key] = nodes[key]
113
+ self.short_memory += '\n' + summary + ' Detail in ' + ', '.join([f'<<{key}>>' for key in new_nodes])
114
+ self.short_memory = self.short_memory.strip()
115
+ self._save_short_memory()
116
+
117
+ def _add_node(self, key, value):
118
+ index = 0
119
+ new_key = key
120
+ while new_key in self.concepts:
121
+ index += 1
122
+ new_key = key + str(index)
123
+ self.concepts[new_key] = LinkMemoryNode(key=new_key, content=value)
124
+ self.db.upsert(self.concepts[key].__dict__, Query().key == new_key)
125
+ return new_key
GeneralAgent/memory/normal_memory.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Memeory
2
+ import json
3
+ import os
4
+
5
+ class NormalMemory:
6
+ def __init__(self, serialize_path='./memory.json'):
7
+ self.messages = []
8
+ self.serialize_path = serialize_path
9
+ if os.path.exists(serialize_path):
10
+ with open(serialize_path, 'r', encoding='utf-8') as f:
11
+ self.messages = json.load(f)
12
+
13
+ def save(self):
14
+ with open(self.serialize_path, 'w', encoding='utf-8') as f:
15
+ json.dump(self.messages, f)
16
+
17
+ def add_message(self, role, content):
18
+ assert role in ['user', 'assistant']
19
+ self.messages.append({'role': role, 'content': content})
20
+ self.save()
21
+
22
+ def append_message(self, role, content):
23
+ assert role in ['user', 'assistant']
24
+ if len(self.messages) > 0 and self.messages[-1]['role'] == role:
25
+ self.messages[-1]['content'] += '\n' + content
26
+ else:
27
+ self.messages.append({'role': role, 'content': content})
28
+ self.save()
29
+
30
+ def get_messages(self):
31
+ return self.messages
32
+
33
+
34
+ def test_NormalMemory():
35
+ serialize_path = './memory.json'
36
+ mem = NormalMemory(serialize_path=serialize_path)
37
+ mem.add_message('user', 'hello')
38
+ mem.add_message('assistant', 'hi')
39
+ mem = NormalMemory(serialize_path=serialize_path)
40
+ assert len(mem.get_messages()) == 2
41
+ mem.append_message('assistant', 'hi')
42
+ assert len(mem.get_messages()) == 2
GeneralAgent/memory/stack_memory.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Memeory
2
+ from dataclasses import dataclass
3
+ from typing import List
4
+ from tinydb import TinyDB, Query
5
+
6
+
7
+ @dataclass
8
+ class StackMemoryNode:
9
+ role: str
10
+ action: str
11
+ state: str = 'ready'
12
+ content: str = None
13
+ prefix: str = None
14
+ node_id: int = None
15
+ parent: int = None
16
+ childrens: List[int] = None
17
+
18
+ def __post_init__(self):
19
+ assert self.role in ['user', 'system', 'root'], self.role
20
+ assert self.action in ['input', 'answer', 'plan'], self.action
21
+ assert self.state in ['ready', 'success', 'fail'], self.state
22
+ self.childrens = self.childrens if self.childrens else []
23
+
24
+ def __str__(self):
25
+ return f'<{self.role}><{self.action}><{self.state}>: {self.content}'
26
+
27
+ def __repr__(self):
28
+ return str(self)
29
+
30
+ def success_work(self):
31
+ self.state = 'success'
32
+
33
+ def fail_work(self):
34
+ self.state = 'fail'
35
+
36
+ def is_root(self):
37
+ return self.role == 'root'
38
+
39
+ def get_level(self):
40
+ if self.is_root():
41
+ return 0
42
+ else:
43
+ return self.get_parent().get_level() + 1
44
+
45
+ @classmethod
46
+ def new_root(cls):
47
+ return cls(node_id=0, role='root', action='input', state='success', content='root', parent=None, childrens=[])
48
+
49
+
50
+ class StackMemory:
51
+ def __init__(self, serialize_path='./memory.json'):
52
+ self.db = TinyDB(serialize_path)
53
+ nodes = [StackMemoryNode(**node) for node in self.db.all()]
54
+ self.spark_nodes = dict(zip([node.node_id for node in nodes], nodes))
55
+ # add root node
56
+ if len(self.spark_nodes) == 0:
57
+ root_node = StackMemoryNode.new_root()
58
+ self.spark_nodes[root_node.node_id] = root_node
59
+ self.db.insert(root_node.__dict__)
60
+ # load current_node
61
+ current_nodes = self.db.table('current_node').all()
62
+ if len(current_nodes) > 0:
63
+ node_id = current_nodes[0]['id']
64
+ # print(node_id)
65
+ # print(self)
66
+ self.current_node = self.get_node(node_id)
67
+ else:
68
+ self.current_node = None
69
+
70
+ def set_current_node(self, current_node):
71
+ self.current_node = current_node
72
+ # save current node
73
+ self.db.table('current_node').truncate()
74
+ self.db.table('current_node').insert({'id': current_node.node_id})
75
+
76
+ def new_node_id(self):
77
+ return max(self.spark_nodes.keys()) + 1
78
+
79
+ def node_count(self):
80
+ # ignore root node
81
+ return len(self.spark_nodes.keys()) - 1
82
+
83
+ def is_all_children_success(self, node):
84
+ # check if all childrens of node are success
85
+ childrens = [self.get_node(node_id) for node_id in node.childrens]
86
+ return all([children.state == 'success' for children in childrens])
87
+
88
+ def add_node(self, node):
89
+ # put in root node
90
+ root_node = self.get_node(0)
91
+ node.node_id = self.new_node_id()
92
+ node.parent = root_node.node_id
93
+ root_node.childrens.append(node.node_id)
94
+ # save node
95
+ self.update_node(root_node)
96
+ self.db.insert(node.__dict__)
97
+ self.spark_nodes[node.node_id] = node
98
+
99
+ def delete_node(self, node):
100
+ # delete node and all its childrens
101
+ for children_id in node.childrens:
102
+ children = self.get_node(children_id)
103
+ self.delete_node(children)
104
+ parent = self.get_node_parent(node)
105
+ if parent:
106
+ parent.childrens.remove(node.node_id)
107
+ self.update_node(parent)
108
+ self.db.remove(Query().node_id == node.node_id)
109
+ del self.spark_nodes[node.node_id]
110
+
111
+
112
+ def add_node_after(self, last_node, node):
113
+ # add node after last_node
114
+ node.node_id = self.new_node_id()
115
+ node.parent = last_node.parent
116
+ parent = self.get_node_parent(node)
117
+ if parent:
118
+ parent.childrens.insert(parent.childrens.index(last_node.node_id)+1, node.node_id)
119
+ self.update_node(parent)
120
+ # move childrens of last_node to node
121
+ node.childrens = last_node.childrens
122
+ last_node.childrens = []
123
+ self.update_node(last_node)
124
+ for children_id in node.childrens:
125
+ children = self.get_node(children_id)
126
+ children.parent = node.node_id
127
+ self.update_node(children)
128
+ # save node
129
+ self.db.insert(node.__dict__)
130
+ self.spark_nodes[node.node_id] = node
131
+ return node
132
+
133
+ def add_node_in(self, parent_node, node, put_first=False):
134
+ # add node in parent_node
135
+ node.node_id = self.new_node_id()
136
+ node.parent = parent_node.node_id
137
+ if put_first:
138
+ parent_node.childrens.insert(0, node.node_id)
139
+ else:
140
+ parent_node.childrens.append(node.node_id)
141
+ self.update_node(parent_node)
142
+ # save node
143
+ self.db.insert(node.__dict__)
144
+ self.spark_nodes[node.node_id] = node
145
+ return node
146
+
147
+ def get_node(self, node_id):
148
+ return self.spark_nodes[node_id]
149
+
150
+ def get_node_level(self, node:StackMemoryNode):
151
+ if node.is_root():
152
+ return 0
153
+ else:
154
+ return self.get_node_level(self.get_node_parent(node)) + 1
155
+
156
+ def get_node_parent(self, node):
157
+ if node.parent is None:
158
+ return None
159
+ else:
160
+ return self.get_node(node.parent)
161
+
162
+ def update_node(self, node):
163
+ self.db.update(node.__dict__, Query().node_id == node.node_id)
164
+
165
+ def get_related_nodes_for_node(self, node):
166
+ # ancestors + left_brothers + self
167
+ parent = self.get_node_parent(node)
168
+ brothers = [self.get_node(node_id) for node_id in parent.childrens]
169
+ left_brothers = [('brother', x) for x in brothers[:brothers.index(node)]]
170
+ ancestors = self.get_related_nodes_for_node(parent) if not parent.is_root() else []
171
+ return ancestors + left_brothers + [('direct', node)]
172
+
173
+ def get_related_messages_for_node(self, node: StackMemoryNode):
174
+ def _get_message(node, position='direct'):
175
+ content = node.content if node.prefix is None else node.prefix + ' ' + node.content
176
+ if position == 'brother' and node.action == 'plan' and len(node.childrens) > 0:
177
+ content = node.content + ' [detail ...]'
178
+ return {'role': node.role, 'content': content}
179
+ nodes_with_position = self.get_related_nodes_for_node(node)
180
+ messages = [_get_message(node, position) for position, node in nodes_with_position]
181
+ # if node.action == 'plan':
182
+ # messages[-1]['content'] = 'Improve the details of this topic:: ' + messages[-1]['content']
183
+ return messages
184
+
185
+ def get_all_description_of_node(self, node, intend_char=' ', depth=0):
186
+ lines = []
187
+ description = intend_char * depth + str(node)
188
+ if not node.is_root():
189
+ lines += [description]
190
+ for children_id in node.childrens:
191
+ children = self.get_node(children_id)
192
+ lines += self.get_all_description_of_node(children, intend_char, depth+1)
193
+ return lines
194
+
195
+ def __str__(self) -> str:
196
+ lines = self.get_all_description_of_node(self.get_node(0), depth=-1)
197
+ return '\n'.join(lines)
198
+
199
+ def success_node(self, node):
200
+ node.success_work()
201
+ self.update_node(node)
202
+
203
+ def _get_todo_node(self, node=None):
204
+ # get the first ready node in the tree of node
205
+ if node is None:
206
+ node = self.get_node(0)
207
+ for node_id in node.childrens:
208
+ child = self._get_todo_node(self.get_node(node_id))
209
+ if child is not None:
210
+ return child
211
+ if node.is_root():
212
+ return None
213
+ if node.state in ['ready']:
214
+ return node
215
+ return None
216
+
217
+ def get_todo_node(self):
218
+ todo_node = self._get_todo_node()
219
+ # if all childrens of todo_node are success, success todo_node
220
+ if todo_node is not None and len(todo_node.childrens) > 0 and self.is_all_children_success(todo_node):
221
+ self.success_node(todo_node)
222
+ return self.get_todo_node()
223
+ return todo_node
GeneralAgent/pytest.ini ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [pytest]
2
+ python_files = *.py
GeneralAgent/requirements.txt ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ requests==2.31.0
3
+ tinydb==4.8.0
4
+ openai==0.27.6
5
+ jinja2==3.1.2
6
+ numpy==1.24.4
7
+ bs4==0.0.1
8
+ playwright==1.39.0
9
+ retrying==1.3.4
10
+ pymupdf==1.23.6
11
+ python-pptx==0.6.23
12
+ python-docx==1.1.0
13
+ yfinance==0.2.31
14
+ beautifulsoup4==4.12.2
15
+ python-dotenv==1.0.0
16
+ uvicorn==0.24.0.post1
17
+ tiktoken==0.5.1
18
+ httpx==0.25.1
19
+ pulsar-client==3.3.0
20
+ pymongo==4.6.0
21
+ websocket-client-py3==0.15.0
22
+ websockets==12.0
23
+ pypdf==3.17.1
24
+ replicate==0.18.1
25
+ edge-tts==6.1.9
26
+ pydub==0.25.1
27
+ chromadb==0.4.17
28
+ python-multipart==0.0.6
29
+ pytest==7.4.3
30
+ pytest-asyncio==0.21.1
GeneralAgent/skills/__init__.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 单列
2
+ import os
3
+
4
+ def load_functions_with_path(python_code_path):
5
+ try:
6
+ import importlib.util
7
+ import inspect
8
+
9
+ # 指定要加载的文件路径和文件名
10
+ module_name = 'skills'
11
+ module_file = python_code_path
12
+
13
+ # 使用importlib加载文件
14
+ spec = importlib.util.spec_from_file_location(module_name, module_file)
15
+ module = importlib.util.module_from_spec(spec)
16
+ spec.loader.exec_module(module)
17
+
18
+ # 获取文件中的所有函数
19
+ functions = inspect.getmembers(module, inspect.isfunction)
20
+
21
+ # 过滤functions中以下划线开头的函数、test_开头的函数
22
+ functions = filter(lambda f: not f[0].startswith('_'), functions)
23
+ # functions = filter(lambda f: not f[0].startswith('test_'), functions)
24
+
25
+ return [f[1] for f in functions]
26
+ except Exception as e:
27
+ # 代码可能有错误,加载不起来
28
+ import logging
29
+ logging.exception(e)
30
+ return []
31
+
32
+ class Skills:
33
+ __instance = None
34
+
35
+ @classmethod
36
+ def __getInstance(cls):
37
+ return cls.__instance
38
+
39
+ @classmethod
40
+ def _instance(cls, *args, **kwargs):
41
+ if not Skills.__instance:
42
+ Skills.__instance = Skills(*args, **kwargs)
43
+ return Skills.__instance
44
+
45
+ def __setattr__(self, name, value):
46
+ if name.startswith('_'):
47
+ object.__setattr__(self, name, value)
48
+ else:
49
+ self._local_funs[name] = value
50
+
51
+ def __getattr__(self, name):
52
+ if name.startswith('_'):
53
+ return object.__getattr__(self, name)
54
+ else:
55
+ return self._get_func(name)
56
+
57
+ def _get_func(self, name):
58
+ fun = self._local_funs.get(name, None)
59
+ if fun is not None:
60
+ return fun
61
+ fun = self._remote_funs.get(name, None)
62
+ if fun is not None:
63
+ return fun
64
+ self._load_remote_funs()
65
+ fun = self._remote_funs.get(name, None)
66
+ if fun is not None:
67
+ return fun
68
+ print('Function {} not found'.format(name))
69
+ return None
70
+
71
+ def __init__(self):
72
+ self._local_funs = {}
73
+ self._remote_funs = {}
74
+ self._load_local_funs()
75
+ self._load_remote_funs()
76
+
77
+ def _load_funcs(self, the_dir):
78
+ total_funs = []
79
+ for file in os.listdir(the_dir):
80
+ # 如果file是文件夹
81
+ if os.path.isdir(os.path.join(the_dir, file)):
82
+ total_funs += self._load_funcs(os.path.join(the_dir, file))
83
+ else:
84
+ # 如果file是文件
85
+ if file.endswith('.py') and (not file.startswith('__init__') and not file.startswith('_') and not file == 'main.py'):
86
+ funcs = load_functions_with_path(os.path.join(the_dir, file))
87
+ total_funs += funcs
88
+ return total_funs
89
+
90
+ def _load_local_funs(self):
91
+ self._local_funs = {}
92
+ funcs = self._load_funcs(os.path.dirname(__file__))
93
+ for fun in funcs:
94
+ self._local_funs[fun.__name__] = fun
95
+
96
+ def _load_remote_funs(self):
97
+ from GeneralAgent.utils import get_functions_dir
98
+ self._remote_funs = {}
99
+ funcs = self._load_funcs(get_functions_dir())
100
+ for fun in funcs:
101
+ self._remote_funs[fun.__name__] = fun
102
+
103
+ def _search_functions(self, task_description, return_list=False):
104
+ """
105
+ Search functions that may help to solve the task.
106
+ """
107
+ from .llm_inference import search_similar_texts
108
+ signatures = self._all_function_signatures()
109
+ results = search_similar_texts(task_description, signatures, top_k=5)
110
+ if return_list:
111
+ return results
112
+ else:
113
+ return '\n'.join(results)
114
+
115
+ def _all_function_signatures(self):
116
+ from .python_envs import get_function_signature
117
+ locals = [get_function_signature(fun, 'skills') for fun in self._local_funs.values() if not fun.__name__.startswith('test_')]
118
+ remotes = [get_function_signature(fun, 'skills') for fun in self._remote_funs.values() if not fun.__name__.startswith('test_')]
119
+ return locals + remotes
120
+
121
+ skills = Skills._instance()
GeneralAgent/skills/agent_builder_2.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def _llm_write_ui_lib(lib_name:str, task:str) -> str:
2
+ """
3
+ Write a UI library for a given task description.
4
+ @param lib_name: The name of the UI library.
5
+ @param task: The task description.
6
+ @return: The UI library code.
7
+ """
8
+ from GeneralAgent import skills
9
+ prompt_template = """
10
+ You are a React and Typescript expert.
11
+
12
+ # Task
13
+ Create a React function component named LibTemplate in tsx language.
14
+ The component should have the following functionality:
15
+ {{task}}
16
+ Note:
17
+ 1. The component only save data to backend, no need to display data, or result of task.
18
+ 2. When uploaded file, should show the file path
19
+ 3. No need to ask user set result file name, backend server will use a unique name to save the file.
20
+
21
+ # Import
22
+ Use the following import syntax:
23
+ ```
24
+ const React = (window as any).React;
25
+ const antd = (window as any).antd;
26
+ ```
27
+ No other import methods are allowed.
28
+
29
+ # DEMO
30
+
31
+ ```tsx
32
+ const React = (window as any).React;
33
+ const antd = (window as any).antd;
34
+
35
+ interface Props {
36
+ save_data: (user_data:any)=>void,
37
+ FileUploadConponent: (props: {onUploadSuccess: (file_path: string) => void, title?: string}) => React.ReactElement
38
+ }
39
+
40
+ // user save_data to save the user_data
41
+ // user_data will be processed into a string through save_data using JSON.stringify({'data': user_data}) and sent to the backend
42
+ // use FileUploadConponent to upload file (<props.FileUploadConponent onUploadSuccess={handleUploadSuccess} title=''/>) and add file_path to data before save
43
+
44
+ const LibTemplate = (props: Props) => {
45
+
46
+ const handleCommit = () => {
47
+ # props.save_data(all_data_should_save)
48
+ };
49
+
50
+ return (<>{xxx}</>);
51
+ };
52
+
53
+ export default LibTemplate;
54
+ ```
55
+
56
+ Please reponse the component code which finish the task without any explaination.
57
+ """
58
+
59
+ from jinja2 import Template
60
+ prompt = Template(prompt_template).render(task=task)
61
+ messages = [{'role': 'system', 'content': prompt}]
62
+ response = skills.llm_inference(messages, model_type="normal", stream=True)
63
+ result = ''
64
+ for token in response:
65
+ result += token
66
+ result = result.replace('LibTemplate', lib_name)
67
+ return result
68
+
69
+
70
+ def create_application_ui(task: str, component_name: str = None) -> (str, str):
71
+ """
72
+ Convert a given task description into UI components.
73
+ In the code, user_data will be processed into a string through save_data using JSON.stringify({'data': user_data}) and sent to the backend
74
+ @param task: Task description, should include all details related to creating the UI
75
+ @param component_name: The name of the UI library.
76
+ @return: The name of the UI library, the path of the UI library, the code of the UI library. None if failed.
77
+ Example:
78
+ create_application_ui('A task description with all the necessary details')
79
+ """
80
+ import os
81
+ import uuid
82
+ from GeneralAgent import skills
83
+ lib_name = component_name
84
+ if lib_name is None:
85
+ lib_name = 'Lib' + str(uuid.uuid1())[:4]
86
+ target_dir = os.path.join(skills.get_code_dir(), lib_name)
87
+ content = _llm_write_ui_lib(lib_name, task)
88
+ code = skills.extract_tsx_code(content)
89
+ success = skills.compile_tsx(lib_name, code, target_dir)
90
+ if success:
91
+ js_path = os.path.join(lib_name, 'index.js')
92
+ print(f'UI library created successfully.\n js_component_name: {lib_name}\n js_path: {js_path}\n code: \n```tsx\n{code}\n```')
93
+ return lib_name, js_path, code
94
+ else:
95
+ return None
96
+
97
+
98
+ def update_application_meta_2(
99
+ application_id:str=None,
100
+ type:str=None,
101
+ application_name:str=None,
102
+ description:str=None,
103
+ js_component_name:str=None,
104
+ js_path:str=None,
105
+ agent_can_upload_file=None,
106
+ ) -> None:
107
+ """
108
+ Update application meta data. When type is application, you should provide js_component_name and js_path. When type is agent, you should ignore js_component_name and js_path.
109
+ @param application_id: application id, You should name it, example: translat_text, ai_draw
110
+ @param type: application type, one of ['application', 'agent']. application is a normal application with ui, agent is a agent application with chat interface.
111
+ @param application_name: application name
112
+ @param description: application description
113
+ @param js_component_name: js component name
114
+ @param js_path: js file path
115
+ @param agent_can_upload_file: agent can upload file or not, default is False, if True, agent can upload file to the application
116
+ @return: None
117
+ """
118
+ import os, json
119
+ from GeneralAgent import skills
120
+ bot_json_path = os.path.join(skills.get_code_dir(), 'bot.json')
121
+ if os.path.exists(bot_json_path):
122
+ with open(bot_json_path, 'r', encoding='utf-8') as f:
123
+ app_json = json.loads(f.read())
124
+ else:
125
+ app_json = {}
126
+ if application_id is not None:
127
+ from GeneralAgent import skills
128
+ bots = skills.load_applications()
129
+ if application_id in [x['id'] for x in bots]:
130
+ print(f'application_id ({application_id}) exists. ignore If you are just edit the exist application, or you should change the application_id')
131
+ app_json['id'] = application_id
132
+ if type is not None:
133
+ app_json['type'] = type
134
+ if application_name is not None:
135
+ app_json['name'] = application_name
136
+ if description is not None:
137
+ app_json['description'] = description
138
+ app_json['upload_file'] = 'yes'
139
+ if js_component_name is not None:
140
+ app_json['js_name'] = js_component_name
141
+ if js_path is not None:
142
+ app_json['js_path'] = js_path
143
+ if agent_can_upload_file is not None:
144
+ app_json['upload_file'] = 'yes'
145
+ if os.path.exists(os.path.join(skills.get_code_dir(), 'icon.jpg')):
146
+ app_json['icon'] = 'icon.jpg'
147
+ else:
148
+ del app_json['icon']
149
+ with open(bot_json_path, 'w', encoding='utf-8') as f:
150
+ f.write(json.dumps(app_json, indent=4))
151
+
152
+
153
+ def edit_application_code_2(task_description:str) -> str:
154
+ """
155
+ edit_application_code_2 is an Agent. You just tell it what will be done and vailable functions, it will generate a python function to complete the task. the code will be saved in main.py, which will be used to create a normal application or agent application.
156
+ @param task_description: task description, should be a string and include the detail of task, and what functions can be used. when building a normal application, task_desciption should have the detail of data format that ui save to backend, example: "Create a image creation application. Available functions:\n\nskills.image_generation(prompt) generate a image with prompt (in english), return a image url\n\nskills.translate_text(content, target_language), data format is {'data': {'prompt': 'xxxxx'}}". when building a agent application, ignore the detail of data format, example: "Create a agent. role prompt is : You are a image creator, transfer users's need to create a image. Available functions:\n\n xxxx"
157
+ @return: python code for the task
158
+ """
159
+ import os
160
+ from GeneralAgent import skills
161
+ code_path = os.path.join(skills.get_code_dir(), 'main.py')
162
+ old_code = None
163
+ if os.path.exists(code_path):
164
+ with open(code_path, 'r', encoding='utf-8') as f:
165
+ old_code = f.read()
166
+ code = _generate_agent_code(task_description, default_code=old_code)
167
+ with open(code_path, 'w', encoding='utf-8') as f:
168
+ f.write(code)
169
+ return code
170
+
171
+
172
+ def _generate_agent_code(task_description, default_code=None):
173
+ """Return the python code text that completes the task to build a chat bot, when default_code is not None, update default_code by task"""
174
+ from GeneralAgent import skills
175
+ python_version = skills.get_python_version()
176
+ requirements = skills.get_current_env_python_libs()
177
+ prompt = f"""
178
+ You are a python expert, write a python function to complete user's task.
179
+ The function in code will be used to create a chat bot, like slack, discord.
180
+
181
+ # Function signature
182
+ ```
183
+ async def main(messages, input, output_callback):
184
+ # messages is a list of dict, like [{{"role": "user", "content": "hello"}}, {{"role": "system", "content": "hi"}}]
185
+ # input is a string, user's input in agent application, or json string by save_data in UI in normal application.
186
+ # output_callback is a async function, output_callback(content: str) -> None. output_callback will send content to user. the content should be markdown format. file should be like [title](sandbox:file_path)
187
+ ```
188
+
189
+ # Python Version: {python_version}
190
+
191
+ # Python Libs installed
192
+ {requirements}
193
+
194
+ # CONSTRAINTS:
195
+ - Do not import the lib that the function not use.
196
+ - Import the lib in the function
197
+ - In the code, Intermediate files are written directly to the current directory (./)
198
+ - Give the function a name that describe the task
199
+ - The docstring of the function should be as concise as possible without losing key information, only one line, and output in English
200
+
201
+ # DEMO 1 : normal application, write user's input to a file and return
202
+ ```python
203
+ async def main(messages, input, output_callback):
204
+ from GeneralAgent import skills
205
+ import json
206
+ data = json.loads(input)['data']
207
+ # file_path should be a unique name, because the file will not be deleted, and the application will run many times.
208
+ file_path = skills.unique_name() + '.json
209
+ with open(file_path, 'w', encoding='utf-8') as f:
210
+ f.write(json.dumps(data))
211
+ await output_callback(f'file saved: [user_data.json](sandbox:{{file_path}})')
212
+ ```
213
+
214
+ # DEMO 2 : agent application, Agent with functions
215
+ ```python
216
+ async def main(messages, input, output_callback):
217
+ from GeneralAgent.agent import Agent
218
+ role_prompt = \"\"\"
219
+ You are a translation agent.
220
+ You complete user requirements by writing python code to call the predefined functions.
221
+ \"\"\"
222
+ functions = [
223
+ skills.translate_text
224
+ ]
225
+ agent = Agent.with_functions(functions)
226
+ agent.add_role_prompt(role_prompt)
227
+ await agent.run(input, output_callback=output_callback)
228
+ ```python
229
+
230
+ # There are two function types:
231
+ 1. Application: like DEMO1, The application process is fixed and less flexible, but the function will be more stable
232
+ 2. Agent: like DEMO2, Agent is a chat bot that can use functions to complete user's task. The agent will automatic handle user's input and output
233
+ You can choose one of them to complete the task.
234
+
235
+ Please think step by step carefully, consider any possible situation, and write a complete code like DEMO
236
+ Just reponse the python code, no any explain, no start with ```python, no end with ```, no any other text.
237
+ """
238
+
239
+ messages = [{"role": "system", "content": prompt}]
240
+ if default_code is not None:
241
+ messages += [{"role": "system", "content": "user's code: " + default_code}]
242
+ messages += [{"role": "system", "content": f"user's task: {task_description}"}]
243
+ code = skills.llm_inference(messages, model_type='smart')
244
+ code = skills.get_python_code(code)
245
+ return code
GeneralAgent/skills/agents.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ async def get_function_builder_agent():
3
+ """
4
+ Get a function builder agent
5
+ """
6
+ from GeneralAgent.agent import Agent
7
+ from GeneralAgent import skills
8
+ from GeneralAgent.interpreter import RoleInterpreter, PythonInterpreter, FileInterpreter, ShellInterpreter
9
+ from GeneralAgent.utils import get_functions_dir
10
+ function_dir = get_functions_dir()
11
+ role_prompt = f"""
12
+ You are an agent who writes python function and the test function of it, into files according to user needs.
13
+ You can control your computer and access the Internet.
14
+
15
+ # make a directory to store function file and test dataset
16
+ ```shell
17
+ mkdir -p {function_dir}/function_folder_name
18
+ ```
19
+
20
+ # copy files uploaded by users to the function file directory
21
+ ```shell
22
+ cp yy.zz {function_dir}/function_folder_name/yy.zz
23
+ ```
24
+
25
+ # When writing a function, you can first search for available functions. For Example
26
+ ```python
27
+ result = search_functions('scrape web page')
28
+ print(result)
29
+ ```
30
+
31
+ # The function should be written in the folder {function_dir}/functoin_folder_name, and the file name should be the function name
32
+ # The content of the file is the function and the test function of the function (starting with test_)
33
+ # every created file should have a unique name, which can be generated by skills.unique_name()
34
+ # Import code should be placed inside the function
35
+
36
+ # DEMO
37
+ ```file
38
+ {function_dir}/image_generation/image_generation.py write 0 -1 <<EOF
39
+ def image_generation(prompt) -> str:
40
+ \"\"\"
41
+ Generate an image according to the prompt and return the image path. For example, when the prompt is "apple" you will get an image of an apple. Note: The prompt should describe objective things in detail, not abstract concepts. For example, if you want to draw a picture of Chengdu, the prompt should be "Picture of Chengdu, with giant pandas playing in the bamboo forest, people eating hot pot around, and a Jinsha Sunbird totem next to it" instead of "Draw a picture of Chengdu" "painting"
42
+ @param prompt: The prompt should be detailed enough to describe the image. Tips can be in any type of language, but English is recommended.
43
+ \"\"\"
44
+ import replicate
45
+ from GeneralAgent import skills
46
+ output = replicate.run(
47
+ "stability-ai/sdxl:2f779eb9b23b34fe171f8eaa021b8261566f0d2c10cd2674063e7dbcd351509e",
48
+ input={{"prompt": prompt}}
49
+ )
50
+ image_url = output
51
+ if not skills.text_is_english(prompt):
52
+ prompt = skills.translate_text(prompt, 'english')
53
+ image_url = _replicate_image_generation(prompt)
54
+ image_path = skills.try_download_file(image_url)
55
+ print(f'image created at {{image_path}}')
56
+ return image_path
57
+
58
+ def test_image_generation():
59
+ import os
60
+ # load test file
61
+ file_path = os.path.join(os.path.dirname(__file__), 'yy.zz')
62
+ prompt = 'xxx'
63
+ image_path = image_generation(prompt)
64
+ assert os.path.exists(image_path)
65
+ EOF
66
+ ```
67
+
68
+ # The written functions can be accessed through GeneralAgent's skills library, such as:
69
+
70
+ ```python
71
+ from GeneralAgent import skills
72
+ result = skills.function_name()
73
+ skills.test_function_name()
74
+ ```
75
+
76
+ # Note:
77
+ - Don't make up functions that don't exist
78
+
79
+ # General process for write function
80
+ * Fully communicate needs with users
81
+ * search available functions (by search_functions in python, optional)
82
+ * edit functions (by file operation)
83
+ * test functions (by python)
84
+ * ask for test files if needed, for example test data, test code, etc.
85
+ """
86
+ functoins = [
87
+ skills.search_functions,
88
+ # skills.scrape_dynamic_web
89
+ ]
90
+ workspace = './'
91
+ agent = Agent(workspace)
92
+ role_interpreter = RoleInterpreter(system_prompt=role_prompt)
93
+ python_interpreter = PythonInterpreter(serialize_path=f'{workspace}/code.bin')
94
+ python_interpreter.function_tools = functoins
95
+
96
+ # when file operation(python file), reload functions
97
+ file_interpreter = FileInterpreter()
98
+ async def reload_funs():
99
+ skills._load_remote_funs()
100
+
101
+ file_interpreter.outptu_parse_done_recall = reload_funs
102
+ agent.interpreters = [role_interpreter, python_interpreter, FileInterpreter(), ShellInterpreter()]
103
+ agent.model_type = 'smart'
104
+ agent.hide_output_parse = False
105
+ return agent
GeneralAgent/skills/ai_draw_prompt_gen.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def ai_draw_prompt_gen(command):
3
+ """The prompt word generator for AI painting inputs the user's needs and outputs a description word for a picture."""
4
+ from GeneralAgent import skills
5
+ system_prompt = "You are a prompt word engineer for AI painting. Your task is to describe the user's needs into descriptors for a picture, and require the content of the picture to meet the user's needs as much as possible. You can add some of your own creativity and understanding. Directly returns the description of the image without explanation or suffixes."
6
+ messages = [
7
+ {'role': 'system', 'content': system_prompt},
8
+ {"role": "user", "content": command}
9
+ ]
10
+ image_prompt = skills.llm_inference(messages)
11
+ return image_prompt
GeneralAgent/skills/application_builder.py ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ CODE_DIR = './code'
3
+
4
+ def get_code_dir():
5
+ global CODE_DIR
6
+ import os
7
+ if not os.path.exists(CODE_DIR):
8
+ os.makedirs(CODE_DIR)
9
+ return CODE_DIR
10
+
11
+ def _set_code_dir(code_dir):
12
+ global CODE_DIR
13
+ CODE_DIR = code_dir
14
+
15
+
16
+ def search_functions(task_description:str) -> str:
17
+ """
18
+ print function signatures that may help to solve the task, and return the function signatures
19
+ """
20
+ from GeneralAgent import skills
21
+ functions = skills._search_functions(task_description)
22
+ print(functions)
23
+ return functions
24
+ from jinja2 import Template
25
+ # # print(functions)
26
+ # prompt_template = """
27
+ # # Functions
28
+ # {{functions}}
29
+
30
+ # # Task
31
+ # {{task}}
32
+
33
+ # Please return the function signatures that can solve the task.
34
+
35
+ # """
36
+ # prompt = Template(prompt_template).render(task=task_description, functions=functions)
37
+ # functions = skills.llm_inference([{'role': 'system', 'content': 'You are a helpful assistant'}, {'role': 'user', 'content': prompt}])
38
+ # print(functions)
39
+ # return functions
40
+
41
+
42
+ def edit_normal_function(function_name:str, task_description:str) -> None:
43
+ """
44
+ Edit normal function code by task_description
45
+ @param function_name: The name of the function to be generated.
46
+ @param task_description (str): A description of the task that the generated function should perform and what functions can be used.
47
+ @return: The generated Python function signature as a string.
48
+ """
49
+ return _edit_function(function_name, task_description, _generate_function_code)
50
+
51
+
52
+ def edit_llm_function(function_name: str, task_description:str) -> str:
53
+ """
54
+ This function generates a Python function to perform a specific task using a large language model (LLM), such as translation, planning, answering general knowledge questions and so on.
55
+ @param function_name: The name of the function to be generated.
56
+ @param task_description (str): A description of the task that the generated function should perform.
57
+ @return: The generated Python function signature as a string.
58
+ """
59
+ return _edit_function(function_name, task_description, _generate_llm_task_function)
60
+
61
+
62
+ def _edit_function(function_name: str, task_description:str, code_fun) -> str:
63
+ import os
64
+ from GeneralAgent import skills
65
+ from GeneralAgent.utils import get_functions_dir
66
+ file_path = os.path.join(get_functions_dir(), function_name + '.py')
67
+ code = None
68
+ if os.path.exists(file_path):
69
+ with open(file_path, 'r', encoding='utf-8') as f:
70
+ code = f.read()
71
+ task_description += f'\n# Function name: {function_name}'
72
+ code = code_fun(task_description, default_code=code)
73
+ with open(file_path, 'w', encoding='utf-8') as f:
74
+ f.write(code)
75
+ funcs, error = skills.load_functions_with_path(file_path)
76
+ funcs = [x for x in funcs if x.__name__ == function_name]
77
+ if len(funcs) <= 0:
78
+ print(error)
79
+ return error
80
+ else:
81
+ signature = skills.get_function_signature(funcs[0], 'skills')
82
+ skills._load_remote_funs()
83
+ print(signature)
84
+ return signature
85
+
86
+ def delete_function(func_name:str) -> None:
87
+ """
88
+ Delete a function by name
89
+ """
90
+ import os
91
+ from GeneralAgent.utils import get_functions_dir
92
+ file_path = os.path.join(get_functions_dir(), func_name + '.py')
93
+ if os.path.exists(file_path):
94
+ os.remove(file_path)
95
+
96
+ def create_application_icon(application_description:str) -> None:
97
+ """
98
+ Create a application icon by application description. The application description is application_description
99
+ """
100
+ import os
101
+ from GeneralAgent import skills
102
+ prompt = skills.ai_draw_prompt_gen("Create an application icon. The application's description is below: \n" + application_description)
103
+ image_url = skills.image_generation(prompt)
104
+ file_path = skills.try_download_file(image_url)
105
+ target_path = os.path.join(get_code_dir(), 'icon.jpg')
106
+ os.system(f"mv {file_path} {target_path}")
107
+
108
+
109
+ def edit_application_code(task_description:str) -> None:
110
+ """
111
+ edit_application_code is an Agent. You just tell it what will be done and vailable functions, it will generate a python function to complete the task.
112
+ Edit agent code by task_description. task description should be a string and include the detail of task, and what functions can be used.
113
+ task_description example: "Create a image creation agent. Available functions:\n\nskills.image_generation(prompt) generate a image with prompt (in english), return a image url\n\nskills.translate_text(content, target_language)"
114
+ """
115
+ import os
116
+ code_path = os.path.join(get_code_dir(), 'main.py')
117
+ old_code = None
118
+ if os.path.exists(code_path):
119
+ with open(code_path, 'r', encoding='utf-8') as f:
120
+ old_code = f.read()
121
+ from GeneralAgent import skills
122
+ code = _generate_agent_code(task_description, default_code=old_code)
123
+ with open(code_path, 'w', encoding='utf-8') as f:
124
+ f.write(code)
125
+
126
+
127
+ def delete_application():
128
+ """
129
+ Delete application code
130
+ """
131
+ import os
132
+ code_path = os.path.join(get_code_dir(), 'main.py')
133
+ if os.path.exists(code_path):
134
+ os.remove(code_path)
135
+
136
+
137
+ def update_application_meta(application_id:str=None, application_name:str=None, description:str=None, upload_file:str=None) -> None:
138
+ """
139
+ Update application meta data
140
+ application_id: application id, You should name it, example: translat_text, ai_draw
141
+ application_name: application name
142
+ description: application description
143
+ upload_file: 'yes' or 'no', when upload_file is 'yes', the application can upload file, when upload_file is 'no', the application can not upload file
144
+ """
145
+ import os, json
146
+ bot_json_path = os.path.join(get_code_dir(), 'bot.json')
147
+ if os.path.exists(bot_json_path):
148
+ with open(bot_json_path, 'r', encoding='utf-8') as f:
149
+ app_json = json.loads(f.read())
150
+ else:
151
+ app_json = {}
152
+ if application_id is not None:
153
+ from GeneralAgent import skills
154
+ bots = skills.load_applications()
155
+ if application_id in [x['id'] for x in bots]:
156
+ print(f'application_id ({application_id}) exists. ignore If you are just edit the exist application, or you should change the application_id')
157
+ app_json['id'] = application_id
158
+ if application_name is not None:
159
+ app_json['name'] = application_name
160
+ if description is not None:
161
+ app_json['description'] = description
162
+ if upload_file is not None:
163
+ app_json['upload_file'] = upload_file
164
+ if os.path.exists(os.path.join(get_code_dir(), 'icon.jpg')):
165
+ app_json['icon'] = 'icon.jpg'
166
+ else:
167
+ del app_json['icon']
168
+ with open(bot_json_path, 'w', encoding='utf-8') as f:
169
+ f.write(json.dumps(app_json, indent=4))
170
+
171
+
172
+ def install_application() -> None:
173
+ """
174
+ Install application to chat bot
175
+ """
176
+ # TODO: check function_id and application_name
177
+ import os, json
178
+ bot_json_path = os.path.join(get_code_dir(), 'bot.json')
179
+ if os.path.exists(bot_json_path):
180
+ with open(bot_json_path, 'r', encoding='utf-8') as f:
181
+ app_json = json.loads(f.read())
182
+ else:
183
+ print('applicatoin meta not exists')
184
+ return
185
+ application_id = app_json['id']
186
+ # move code to bot
187
+ from GeneralAgent.utils import get_applications_dir
188
+ target_dir = os.path.join(get_applications_dir(), application_id)
189
+ if os.path.exists(target_dir):
190
+ import shutil
191
+ shutil.rmtree(target_dir)
192
+ os.makedirs(target_dir)
193
+ # print(target_dir)
194
+ os.system(f"cp -r {get_code_dir()}/* {target_dir}")
195
+
196
+
197
+ def uninstall_application() -> None:
198
+ """
199
+ Uninstall application from chat bot
200
+ """
201
+ import os, json
202
+ bot_json_path = os.path.join(get_code_dir(), 'bot.json')
203
+ if os.path.exists(bot_json_path):
204
+ with open(bot_json_path, 'r', encoding='utf-8') as f:
205
+ app_json = json.loads(f.read())
206
+ else:
207
+ print('applicatoin meta not exists')
208
+ return
209
+ application_id = app_json['id']
210
+ # move code to bot
211
+ from GeneralAgent.utils import get_applications_dir
212
+ target_dir = os.path.join(get_applications_dir(), application_id)
213
+ if os.path.exists(target_dir):
214
+ import shutil
215
+ shutil.rmtree(target_dir)
216
+
217
+
218
+
219
+ def _generate_function_code(task:str, default_code=None, search_functions=False):
220
+ """Return the python function code text that completes the task to be used by other function or application, when default_code is not None, update default_code by task"""
221
+
222
+ """
223
+ Return the python function code text that completes the task(a string)
224
+ task: 文字描述的任务
225
+ default_code: 如果不为None,按task修改默认代码,否则按task生成代码
226
+ return: 一个python代码字符串,主要包含了一个函数
227
+ """
228
+ # global skills
229
+ import os
230
+ from GeneralAgent import skills
231
+ python_version = skills.get_python_version()
232
+ requirements = skills.get_current_env_python_libs()
233
+ the_skills_can_use = skills._search_functions(task) if search_functions else ''
234
+ prompt = f"""
235
+ You are a python expert, write a function to complete user's task
236
+
237
+ # Python Version
238
+ {python_version}
239
+
240
+ # Python Libs installed
241
+ {requirements}
242
+
243
+ # You can use skills lib(from GeneralAgent import skills), the function in the lib are:
244
+ {the_skills_can_use}
245
+
246
+
247
+ # CONSTRAINTS:
248
+ - Do not import the lib that the function not use.
249
+ - Import the lib in the function, any import statement must be placed in the function
250
+ - docstring the function simplely
251
+ - Do not use other libraries
252
+ - In the code, Intermediate files are written directly to the current directory (./)
253
+ - Give the function a name that describle the task
254
+ - The docstring of the function should be as concise as possible without losing key information, only one line, and output in English
255
+ - The code should be as simple as possible and the operation complexity should be low
256
+
257
+ # Demo:
258
+ ```python
259
+ def translate(text:str, language:str) -> str:
260
+ \"\"\"
261
+ translate, return the translated text
262
+ Parameters: text -- user text, string
263
+ Returns: the translated text, string
264
+ \"\"\"
265
+ from GeneralAgent import skills
266
+ contents = text.split('.')
267
+ translated = []
268
+ for x in contents:
269
+ prompt = "Translate the following text to " + language + "\n" + x
270
+ translated += [skills.llm_inference([{{'role': 'system', 'content': prompt}}])
271
+ return '. '.join(translated)
272
+ ```
273
+
274
+ Please think step by step carefully, consider any possible situation, and write a complete function.
275
+ Just reponse the python code, no any explain, no start with ```python, no end with ```, no any other text.
276
+ """
277
+ messages = [{"role": "system", "content": prompt}]
278
+ if default_code is not None:
279
+ messages += [{"role": "system", "content": "user's code: " + default_code}]
280
+ messages += [{"role": "system", "content": f"user's task: {task}"}]
281
+ code = skills.llm_inference(messages, model_type='smart')
282
+ code = skills.get_python_code(code)
283
+ return code
284
+
285
+
286
+ def application_code_generation(task, default_code=None):
287
+ """Return the python code text that completes the task to build a chat bot, when default_code is not None, update default_code by task"""
288
+ from GeneralAgent import skills
289
+ python_version = skills.get_python_version()
290
+ requirements = skills.get_current_env_python_libs()
291
+ the_skills_can_use = skills._search_functions(task)
292
+
293
+ prompt = f"""
294
+ You are a python expert, write a python function to complete user's task.
295
+ The function in code will be used to create a chat bot, like slack, discord.
296
+
297
+ # Function signature
298
+ ```
299
+ async def main(chat_history, input, file_path, output_callback, file_callback, ui_callback):
300
+ # chat_history is a list of dict, like [{{"role": "user", "content": "hello"}}, {{"role": "system", "content": "hi"}}]
301
+ # input is a string, user's input
302
+ # file_path is a string, user's file path
303
+ # output_callback is a async function, output_callback(content: str) -> None
304
+ # file_callback is a async function, file_callback(file_path: str) -> None
305
+ # ui_callback is a async function, ui_callback(name:str, js_path:str, data={{}}) -> None
306
+ ```
307
+
308
+ # Python Version: {python_version}
309
+
310
+ # Python Libs installed
311
+ {requirements}
312
+
313
+ # You can use skills lib(from GeneralAgent import skills), the function in the lib are:
314
+ {the_skills_can_use}
315
+
316
+ # CONSTRAINTS:
317
+ - Do not import the lib that the function not use.
318
+ - Import the lib in the function
319
+ - In the code, Intermediate files are written directly to the current directory (./)
320
+ - Give the function a name that describe the task
321
+ - The docstring of the function should be as concise as possible without losing key information, only one line, and output in English
322
+
323
+ # DEMO 1 : Chat with A large language model
324
+ ```python
325
+ async def main(chat_history, input, file_path, output_callback, file_callback, ui_callback):
326
+ from GeneralAgent import skills
327
+ chat_history = skills.cut_messages(chat_history, 4000)
328
+ messages = [{{"role": "system", "content": "You are a helpful assistant."}}] + chat_history
329
+ response = skills.llm_inference(messages, stream=True)
330
+ for token in response:
331
+ await output_callback(token)
332
+ await output_callback(None)
333
+ ```
334
+
335
+ # DEMO 2 : Create a image by user's prompt
336
+ ```python
337
+ async def main(chat_history, input, file_path, output_callback, file_callback, ui_callback):
338
+ from GeneralAgent import skills
339
+ prompt = input
340
+ if not skills.text_is_english(prompt):
341
+ prompt = skills.translate_text(prompt, 'english')
342
+ image_url = skills.image_generation(prompt)
343
+ await file_callback(image_url)
344
+ ```
345
+
346
+ Please think step by step carefully, consider any possible situation, and write a complete code like DEMO
347
+ Just reponse the python code, no any explain, no start with ```python, no end with ```, no any other text.
348
+ """
349
+
350
+ messages = [{"role": "system", "content": prompt}]
351
+ if default_code is not None:
352
+ messages += [{"role": "system", "content": "user's code: " + default_code}]
353
+ messages += [{"role": "system", "content": f"user's task: {task}"}]
354
+ code = skills.llm_inference(messages, model_type='smart')
355
+ code = skills.get_python_code(code)
356
+ return code
357
+
358
+
359
+ def _generate_agent_code(task_description, default_code=None):
360
+ """Return the python code text that completes the task to build a chat bot, when default_code is not None, update default_code by task"""
361
+ from GeneralAgent import skills
362
+ python_version = skills.get_python_version()
363
+ requirements = skills.get_current_env_python_libs()
364
+ prompt = f"""
365
+ You are a python expert, write a python function to complete user's task.
366
+ The function in code will be used to create a chat bot, like slack, discord.
367
+
368
+ # Function signature
369
+ ```
370
+ async def main(chat_history, input, file_path, output_callback, file_callback, ui_callback):
371
+ # chat_history is a list of dict, like [{{"role": "user", "content": "hello"}}, {{"role": "system", "content": "hi"}}]
372
+ # input is a string, user's input
373
+ # file_path is a string, user's file path
374
+ # output_callback is a async function, output_callback(content: str) -> None
375
+ # file_callback is a async function, file_callback(file_path: str) -> None
376
+ # ui_callback is a async function, ui_callback(name:str, js_path:str, data={{}}) -> None
377
+ ```
378
+
379
+ # Python Version: {python_version}
380
+
381
+ # Python Libs installed
382
+ {requirements}
383
+
384
+ # CONSTRAINTS:
385
+ - Do not import the lib that the function not use.
386
+ - Import the lib in the function
387
+ - In the code, Intermediate files are written directly to the current directory (./)
388
+ - Give the function a name that describe the task
389
+ - The docstring of the function should be as concise as possible without losing key information, only one line, and output in English
390
+ - Every created file should have a unique name, which can be generated by skills.unique_name()
391
+
392
+ # DEMO 1 : write user's input to a file and return
393
+ ```python
394
+ async def main(chat_history, input, file_path, output_callback, file_callback, ui_callback):
395
+ from GeneralAgent import skills
396
+
397
+ ```
398
+
399
+ # DEMO 2 : Agent with functions
400
+ ```python
401
+ async def main(chat_history, input, file_path, output_callback, file_callback, ui_callback):
402
+ from GeneralAgent.agent import Agent
403
+ role_prompt = \"\"\"
404
+ You are a translation agent.
405
+ You complete user requirements by writing python code to call the predefined functions.
406
+ \"\"\"
407
+ functions = [
408
+ skills.translate_text
409
+ ]
410
+ agent = Agent.with_functions(functions, role_prompt)
411
+ await agent.run(input, output_callback=output_callback)
412
+ ```python
413
+
414
+ # There are two function types:
415
+ 1. Application: like DEMO1, The application process is fixed and less flexible, but the function will be more stable
416
+ 2. Agent: like DEMO2, Agent is a chat bot that can use functions to complete user's task. The agent will automatic handle user's input and output
417
+ You can choose one of them to complete the task.
418
+
419
+ Please think step by step carefully, consider any possible situation, and write a complete code like DEMO
420
+ Just reponse the python code, no any explain, no start with ```python, no end with ```, no any other text.
421
+ """
422
+
423
+ messages = [{"role": "system", "content": prompt}]
424
+ if default_code is not None:
425
+ messages += [{"role": "system", "content": "user's code: " + default_code}]
426
+ messages += [{"role": "system", "content": f"user's task: {task_description}"}]
427
+ code = skills.llm_inference(messages, model_type='smart')
428
+ code = skills.get_python_code(code)
429
+ return code
430
+
431
+
432
+ def _generate_llm_task_function(task_description, default_code=None):
433
+ """
434
+ This function generates a Python function to perform a specific task using a large language model (LLM), such as translation, planning, answering general knowledge questions.
435
+
436
+ Parameters:
437
+ task_description (str): A description of the task that the generated function should perform.
438
+
439
+ Returns:
440
+ str: The generated Python function code as a string.
441
+ """
442
+ import os
443
+ from GeneralAgent import skills
444
+ python_version = skills.get_python_version()
445
+ requirements = skills.get_current_env_python_libs()
446
+ the_skills_can_use = skills._search_functions(task_description) if search_functions else ''
447
+ prompt = f"""
448
+ You are a python expert, write a function to complete user's task
449
+
450
+ # Python Version
451
+ {python_version}
452
+
453
+ # Python Libs installed
454
+ {requirements}
455
+
456
+ # You can use skills lib(from GeneralAgent import skills), the function in the lib are:
457
+
458
+ def skills.simple_llm_inference(messages, json_schema):
459
+ Run LLM (large language model) inference on the provided messages, The total number of tokens in the messages and the returned string must be less than 8000.
460
+ @param messages: Input messages for the model, like [{{'role': 'system', 'content': 'You are a helpful assistant'}}, {{'role': 'user', 'content': 'translate blow to english:\nxxxx'}}]
461
+ @param json_schema: the json schema of return dictionary, like {{"type": "object", "properties": {{"name": {{"type": "string"}}, "age": {{"type": "integer" }} }} }}
462
+ @return returned as a dictionary According to the provided JSON schema.
463
+
464
+ {the_skills_can_use}
465
+
466
+ # CONSTRAINTS:
467
+ - Do not import the lib that the function not use.
468
+ - Import the lib in the function, any import statement must be placed in the function
469
+ - docstring the function simplely
470
+ - Do not use other libraries
471
+ - In the code, Intermediate files are written directly to the current directory (./)
472
+ - Give the function a name that describle the task
473
+ - The docstring of the function should be as concise as possible without losing key information, only one line, and output in English
474
+ - The code should be as simple as possible and the operation complexity should be low
475
+ - Every created file should have a unique name, which can be generated by skills.unique_name()
476
+
477
+ # Demo:
478
+ ```python
479
+ def translate(text:str, language:str) -> str:
480
+ \"\"\"
481
+ translate, return the translated text
482
+ Parameters: text -- user text, string
483
+ Returns: the translated text, string
484
+ \"\"\"
485
+ from GeneralAgent import skills
486
+ contents = text.split('.')
487
+ translated = []
488
+ for x in contents:
489
+ prompt = "Translate the following text to " + language + "\n" + x
490
+ translated += [skills.llm_inference([{{'role': 'system', 'content': prompt}}])
491
+ return '. '.join(translated)
492
+ ```
493
+
494
+ Please think step by step carefully, consider any possible situation, and write a complete function.
495
+ Just reponse the python code, no any explain, no start with ```python, no end with ```, no any other text.
496
+ """
497
+ messages = [{"role": "system", "content": prompt}]
498
+ if default_code is not None:
499
+ messages += [{"role": "system", "content": "user's code: " + default_code}]
500
+ messages += [{"role": "system", "content": f"user's task: {task_description}"}]
501
+ code = skills.llm_inference(messages, model_type='smart')
502
+ code = skills.get_python_code(code)
503
+ return code
504
+
505
+ # from GeneralAgent import skills
506
+ # from jinja2 import Template
507
+ # prompt_template = """
508
+ # 你是一个python专家。
509
+
510
+
511
+ # Your job is to have a large language model (LLM) perform specific tasks, such as translation, planning, answering general knowledge questions, etc.
512
+ # Your job is to have a large language model (LLM) perform specific tasks, such as translation, planning, answering general knowledge questions, etc.
513
+ # Large language model calling function:
514
+
515
+ # ```python
516
+ # def xxx(xxx):
517
+ # \"\"\"
518
+ # xxx
519
+ # \"\"\"
520
+ # from GeneralAgent import skills
521
+ # # skills.simple_llm_inference
522
+ # ```
523
+
524
+ # # Task
525
+
526
+ # {{task}}
527
+
528
+ # # Note:
529
+
530
+ # - All imports should be placed inside the function.
531
+ # - While creating your function, consider the all edge cases.
532
+ # - Do not use any other libraries except simple_llm_inference and installed libraries.
533
+ # - The simple_llm_inference function requires that the input messages are less than 8000, and the output length is less than 8000. -
534
+ # - When the task cannot be completed through one simple_llm_inference, you should consider task disassembly.
535
+ # """
536
+ # prompt = Template(prompt_template).render({'task': task_description})
537
+ # if default_code is not None:
538
+ # prompt += '\n' + 'user\'s code: ' + default_code + '\nUpdate the code to complete the task'
539
+ # result = skills.llm_inference([{'role': 'system', 'content': prompt}, {'role': 'user', 'content': 'You are a python expert.'}, {'role': 'user', 'content': prompt}], model_type="smart")
540
+ # code = skills.get_python_code(result)
541
+ # return code
542
+
543
+
544
+ # def create_function(func_name:str, task_description:str):
545
+ # """
546
+ # create a function by task description. Where task_description can include functions in GeneralAgent.skills
547
+ # """
548
+ # # from GeneralAgent import skills
549
+ # import os
550
+ # code = _generate_function_code(task_description)
551
+ # file_path = os.path.join(get_code_dir(), func_name + '.py')
552
+ # with open(file_path, 'w', encoding='utf-8') as f:
553
+ # f.write(code)
554
+
555
+ # def delete_function(func_name:str) -> None:
556
+ # """
557
+ # Delete a function by name
558
+ # """
559
+ # import os
560
+ # file_path = os.path.join(get_code_dir(), func_name + '.py')
561
+ # if os.path.exists(file_path):
562
+ # os.remove(file_path)
563
+
564
+ # def list_functions() -> [str]:
565
+ # """
566
+ # list all function names
567
+ # """
568
+ # # TODO function description
569
+ # import os
570
+ # files = os.listdir(get_code_dir())
571
+ # functions = [x.split('.')[0] for x in files]
572
+ # return functions
573
+
574
+ # def show_function(func_name:str) -> str:
575
+ # """
576
+ # Show a function code by name
577
+ # """
578
+ # import os
579
+ # file_path = os.path.join(get_code_dir(), func_name + '.py')
580
+ # if os.path.exists(file_path):
581
+ # with open(file_path, 'r', encoding='utf-8') as f:
582
+ # code = f.read()
583
+ # return code
584
+ # else:
585
+ # return None
586
+
587
+
588
+ # def create_application(task_description:str) -> None:
589
+ # """
590
+ # Create a application by task_description description. The application name is application_name, the task_description is task_description(string)
591
+ # """
592
+ # import os
593
+ # from GeneralAgent import skills
594
+ # # code = application_code_generation(task_description)
595
+ # code = _generate_agent_code(task_description)
596
+ # code_path = os.path.join(get_code_dir(), 'main.py')
597
+ # with open(code_path, 'w', encoding='utf-8') as f:
598
+ # f.write(code)
GeneralAgent/skills/applications.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def load_applications():
3
+ """
4
+ load all applications(bots) metadata
5
+ """
6
+ from GeneralAgent.utils import get_applications_dir
7
+ local_bots = _load_bots(_get_local_applications_dir())
8
+ remote_bots= _load_bots(get_applications_dir())
9
+ for bot in remote_bots:
10
+ bot['nickname'] = '# ' + bot['nickname']
11
+ return local_bots + remote_bots
12
+
13
+
14
+ def _get_local_applications_dir():
15
+ from GeneralAgent.utils import get_local_applications_dir
16
+ return get_local_applications_dir()
17
+
18
+
19
+ def _load_bots(code_dir):
20
+ import os, json
21
+ result = []
22
+ for bot_name in os.listdir(code_dir):
23
+ bot_dir = os.path.join(code_dir, bot_name)
24
+ if os.path.isdir(bot_dir):
25
+ result.append(_load_bot_metadata(bot_dir))
26
+ return result
27
+
28
+
29
+ def _load_bot_metadata(bot_dir):
30
+ import os, json
31
+ bot_json_path = os.path.join(bot_dir, 'bot.json')
32
+ if os.path.exists(bot_json_path):
33
+ with open(bot_json_path, 'r', encoding='utf-8') as f:
34
+ bot_json = json.load(f)
35
+ if 'icon' in bot_json:
36
+ bot_json['icon_url'] = os.path.join(bot_dir, bot_json['icon'])
37
+ if 'js_path' in bot_json:
38
+ bot_json['js_path'] = os.path.join(bot_dir, bot_json['js_path'])
39
+ bot_json['nickname'] = bot_json['name']
40
+ return bot_json
41
+ return None
42
+
43
+
44
+ def load_bot_metadata_by_id(bot_id):
45
+ """
46
+ load bot metadata by bot id
47
+ """
48
+ import os
49
+ local_dir = _get_local_applications_dir()
50
+ from GeneralAgent.utils import get_applications_dir
51
+ remote_dir = get_applications_dir()
52
+ bot_dir = os.path.join(local_dir, bot_id)
53
+ if not os.path.exists(bot_dir):
54
+ bot_dir = os.path.join(remote_dir, bot_id)
55
+ return _load_bot_metadata(bot_dir)
56
+
57
+
58
+ def get_application_module(bot_id):
59
+ """
60
+ get application module by bot id
61
+ """
62
+ import os
63
+ from GeneralAgent.utils import get_applications_dir
64
+ local_dir = _get_local_applications_dir()
65
+ remote_dir = get_applications_dir()
66
+ code_path = os.path.join(local_dir, f"{bot_id}/main.py")
67
+ if not os.path.exists(code_path):
68
+ code_path = os.path.join(remote_dir, f"{bot_id}/main.py")
69
+ return _load_application(code_path)
70
+
71
+
72
+ def _load_application(code_path):
73
+ """
74
+ load application with code path
75
+ """
76
+ import os, importlib.util, logging
77
+ application = None
78
+ try:
79
+ spec = importlib.util.spec_from_file_location("main", code_path)
80
+ module = importlib.util.module_from_spec(spec)
81
+ spec.loader.exec_module(module)
82
+ application = module
83
+ except Exception as e:
84
+ logging.exception(e)
85
+ return application
GeneralAgent/skills/build_web.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ast import Tuple
2
+ import os
3
+
4
+ def compile_tsx(lib_name:str, code:str, target_dir:str):
5
+ """
6
+ Compile tsx code into a UI library.
7
+ @lib_name: the name of the UI library
8
+ @code: the tsx code
9
+ @target_dir: the directory to save the UI library
10
+ """
11
+ # 目标目录不存在就创建
12
+ if os.path.exists(target_dir):
13
+ import shutil
14
+ shutil.rmtree(target_dir)
15
+ os.makedirs(target_dir)
16
+
17
+ from GeneralAgent.utils import get_tsx_builder_dir
18
+ ts_builder_dir = get_tsx_builder_dir()
19
+
20
+ code_path = os.path.join(ts_builder_dir, 'src/lib/index.tsx')
21
+ with open(code_path, 'w', encoding='utf-8') as f:
22
+ f.write(code)
23
+ # 写入lib名称
24
+ webpack_template_path = os.path.join(ts_builder_dir, 'webpack.config.template.js')
25
+ webpack_template = ''
26
+ with open(webpack_template_path, 'r', encoding='utf-8') as f:
27
+ webpack_template = f.read()
28
+ webpack_template = webpack_template.replace('LibTemplate', lib_name)
29
+ webpack_path = os.path.join(ts_builder_dir, 'webpack.config.js')
30
+ with open(webpack_path, 'w', encoding='utf-8') as f:
31
+ f.write(webpack_template)
32
+ # 获取编译的标准输出
33
+ output = os.popen(f"cd {ts_builder_dir} && npm run build").read()
34
+ if 'successfully' not in output:
35
+ print(output)
36
+ return False
37
+ # 将编译后的文件移动到目标目录
38
+ os.system(f"mv {ts_builder_dir}/build/* {target_dir}")
39
+ return True
40
+
41
+
42
+ def _llm_write_ui_lib(lib_name, task):
43
+ from GeneralAgent import skills
44
+ prompt_template = """
45
+ You are a React and Typescript expert.
46
+
47
+ # Task
48
+ Create a React function component named LibTemplate in tsx language.
49
+ The component should have the following functionality:
50
+ {{task}}
51
+
52
+ # Import
53
+ Use the following import syntax:
54
+ ```
55
+ const React = (window as any).React;
56
+ const antd = (window as any).antd;
57
+ ```
58
+ No other import methods are allowed.
59
+
60
+ # DEMO
61
+
62
+ ```tsx
63
+ const React = (window as any).React;
64
+ const antd = (window as any).antd;
65
+
66
+ const [Form, Input, Button] = [antd.Form, antd.Input, antd.Button];
67
+
68
+ const LibTemplate = ({save_data}: {save_data: (data:any)=>void}) => {
69
+ // use save_data to save the data
70
+ }
71
+
72
+ export default LibTemplate;
73
+ ```
74
+
75
+ Please reponse the component code which finish the task without any explaination.
76
+ """
77
+
78
+ from jinja2 import Template
79
+ prompt = Template(prompt_template).render(task=task)
80
+ messages = [{'role': 'system', 'content': prompt}]
81
+ response = skills.llm_inference(messages, model_type="normal", stream=True)
82
+ result = ''
83
+ for token in response:
84
+ # print(token, end='', flush=True)
85
+ result += token
86
+ result = result.replace('LibTemplate', lib_name)
87
+ return result
88
+
89
+
90
+ def extract_tsx_code(content):
91
+ """
92
+ Extract tsx code from markdown content.
93
+ """
94
+ import re
95
+ # 兼容tsx和ts
96
+ pattern = re.compile(r'```tsx?\n([\s\S]*)\n```')
97
+ match = pattern.search(content)
98
+ if match:
99
+ return match.group(1)
100
+ else:
101
+ return content
102
+
103
+ # def create_ui(task: str, ui_dir: str = './ui', component_name: str = None) -> (str, str):
104
+ # """
105
+ # Convert task into UI components. Return (component_name, js_path) tuple.
106
+ # """
107
+ def create_ui(task: str, ui_dir: str = './ui', component_name: str = None) -> (str, str):
108
+ """
109
+ Convert a given task description into UI components.
110
+
111
+ Args:
112
+ task: A string representing the task description with all the necessary details.
113
+
114
+ Returns:
115
+ A tuple containing the name of the UI component and the path to the JavaScript file.
116
+
117
+ Example:
118
+ create_ui('A task description with all the necessary details')
119
+ """
120
+ import uuid
121
+ import os
122
+ lib_name = component_name
123
+ if lib_name is None:
124
+ lib_name = 'Lib' + str(uuid.uuid1())[:4]
125
+ if not os.path.exists(ui_dir):
126
+ os.makedirs(ui_dir)
127
+ target_dir = os.path.join(ui_dir, lib_name)
128
+ for _ in range(2):
129
+ content = _llm_write_ui_lib(lib_name, task)
130
+ code = extract_tsx_code(content)
131
+ success = compile_tsx(lib_name, code, target_dir)
132
+ if success:
133
+ return lib_name, os.path.join(target_dir, 'index.js')
134
+ return None
135
+
136
+ def parse_tsx_to_ui(code, save_dir=None):
137
+ import uuid
138
+ lib_name = 'Lib' + str(uuid.uuid1())[:4]
139
+ if save_dir is None:
140
+ from GeneralAgent import skills
141
+ save_dir = skills.get_code_dir()
142
+ if not os.path.exists(save_dir):
143
+ os.makedirs(save_dir)
144
+ target_dir = os.path.join(save_dir, lib_name)
145
+ success = compile_tsx(lib_name, code, target_dir)
146
+ if success:
147
+ return lib_name, os.path.join(target_dir, 'index.js')
148
+ else:
149
+ return None
GeneralAgent/skills/concatenate_videos/concatenate_videos.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def concatenate_videos(video_list: list) -> str:
3
+ """
4
+ Concatenate a list of videos into one video.
5
+ @param video_list: A list of video file paths.
6
+ @return: The file path of the concatenated video.
7
+ """
8
+ import uuid
9
+ from moviepy.editor import concatenate_videoclips, VideoFileClip
10
+ clips = [VideoFileClip(video) for video in video_list]
11
+ final_clip = concatenate_videoclips(clips)
12
+ output_path = f"{uuid.uuid4().hex}.mp4" # Generate a unique output file name
13
+ final_clip.write_videofile(output_path)
14
+ return output_path
15
+
16
+ def test_concatenate_videos():
17
+ """
18
+ Test the concatenate_videos function.
19
+ """
20
+ import os
21
+ file_path = os.path.join(os.path.dirname(__file__), "f63bfaae7b0e.mp4")
22
+ video_list = [file_path, file_path, file_path] # Use the provided video file for testing
23
+ output_path = concatenate_videos(video_list)
24
+ assert os.path.exists(output_path)
25
+
26
+ if __name__ == '__main__':
27
+ test_concatenate_videos()
GeneralAgent/skills/concatenate_videos/f63bfaae7b0e.mp4 ADDED
Binary file (217 kB). View file
 
GeneralAgent/skills/download_file.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def download_file(file_url, save_path):
3
+ """download file to save_path, return True if success, else False"""
4
+ """实现一个下载文件的函数,返回文件路径"""
5
+ import requests
6
+ import logging
7
+ try_count = 3
8
+ while try_count > 0:
9
+ try:
10
+ response = requests.get(file_url)
11
+ with open(save_path, "wb") as f:
12
+ f.write(response.content)
13
+ # print("Success: 文件(%s)已下载至 %s" % (file_url, save_path))
14
+ return True
15
+ except Exception as e:
16
+ # print("Error: 文件下载失败:", e)
17
+ logging.error(e)
18
+ import time
19
+ time.sleep(5)
20
+ try_count -= 1
21
+ continue
22
+ return False
23
+
24
+
25
+ def try_download_file(file_path):
26
+ import os
27
+ from GeneralAgent import skills
28
+ from PIL import Image
29
+ """Try to download file if it is a url, else return file_path"""
30
+ if file_path.startswith("http://") or file_path.startswith("https://"):
31
+ save_path = skills.unique_name() + '.' + file_path.split('.')[-1]
32
+ success = skills.download_file(file_path, save_path)
33
+ if success:
34
+ if save_path.endswith('.png') or save_path.endswith('.PNG'):
35
+ # 转成jpg
36
+ png_image = Image.open(save_path)
37
+ jpg_save_path = skills.unique_name() + '.jpg'
38
+ png_image.save(jpg_save_path, 'JPEG')
39
+ os.remove(save_path)
40
+ return jpg_save_path
41
+ else:
42
+ return save_path
43
+ else:
44
+ return file_path
45
+ else:
46
+ return file_path
47
+
48
+ if __name__ == '__main__':
49
+ # download_file('https://ai.tongtianta.site/file-upload/gvsAc4cEm543iaX5x/5.pdf', '1.pdf')
50
+ download_file('https://ai.tongtianta.site/file-upload/27XEb3WgyDru5eFFe/9.pdf', '3.pdf')
GeneralAgent/skills/file_operation.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def read_pdf_pages(file_path):
3
+ """Read the pdf file and return a list of strings on each page of the pdf"""
4
+ """读取pdf文件,返回pdf每页字符串的列表"""
5
+ import fitz
6
+ doc = fitz.open(file_path)
7
+ documents = []
8
+ for page in doc:
9
+ documents.append(page.get_text())
10
+ return documents
11
+
12
+ def read_word_pages(file_path):
13
+ """Read the word file and return a list of word paragraph strings"""
14
+ """读取word文件,返回word段落字符串的列表"""
15
+ # https://zhuanlan.zhihu.com/p/146363527
16
+ from docx import Document
17
+ # 打开文档
18
+ document = Document(file_path)
19
+ # 读取标题、段落、列表内容
20
+ ps = [ paragraph.text for paragraph in document.paragraphs]
21
+ return ps
22
+
23
+ def read_ppt(file_path):
24
+ import pptx
25
+ prs = pptx.Presentation(file_path)
26
+ documents = []
27
+ for slide in prs.slides:
28
+ for shape in slide.shapes:
29
+ if hasattr(shape, "text"):
30
+ documents.append(shape.text)
31
+ return '\n'.join(documents)
32
+
33
+
34
+ def read_file_content(file_path):
35
+ """return content of txt, md, pdf, docx file"""
36
+ # 支持file_path的类型包括txt、md、pdf、docx
37
+ if file_path.endswith('.pdf'):
38
+ return ' '.join(read_pdf_pages(file_path))
39
+ elif file_path.endswith('.docx'):
40
+ return ' '.join(read_word_pages(file_path))
41
+ elif file_path.endswith('.ppt') or file_path.endswith('.pptx'):
42
+ return read_ppt(file_path)
43
+ else:
44
+ # 默认当做文本文件
45
+ with open(file_path, 'r', encoding='utf-8') as f:
46
+ return '\n'.join(f.readlines())
47
+
48
+ def write_file_content(file_path, content):
49
+ """write content to txt, md"""
50
+ with open(file_path, 'w', encoding='utf-8') as f:
51
+ f.write(content)
GeneralAgent/skills/llm_inference.py ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # from retrying import retry as _retry
2
+ # from tenacity import retry, stop_after_attempt, wait_fixed
3
+
4
+ class TinyDBCache():
5
+ def __init__(self):
6
+ from tinydb import TinyDB
7
+ import os, json
8
+ llm_path = os.environ.get('CACHE_PATH', None)
9
+ if llm_path is None:
10
+ from GeneralAgent.utils import get_server_dir
11
+ llm_path = os.path.join(get_server_dir(), 'cache.json')
12
+ self.db = TinyDB(llm_path)
13
+
14
+ @property
15
+ def cache_llm(self):
16
+ import os
17
+ return os.environ.get('LLM_CACHE', 'no') in ['yes', 'y', 'YES']
18
+
19
+ @property
20
+ def cache_embedding(self):
21
+ import os
22
+ return os.environ.get('EMBEDDING_CACHE', 'no') in ['yes', 'y', 'YES']
23
+
24
+ def get(self, table, key):
25
+ from tinydb import Query
26
+ result = self.db.table(table).get(Query().key == key)
27
+ if result is not None:
28
+ return result['value']
29
+ else:
30
+ return None
31
+
32
+ def set(self, table, key, value):
33
+ from tinydb import Query
34
+ self.db.table(table).upsert({'key': key, 'value': value}, Query().key == key)
35
+
36
+ def set_llm_cache(self, key, value):
37
+ if self.cache_llm:
38
+ self.set('llm', key, value)
39
+
40
+ def get_llm_cache(self, key):
41
+ if self.cache_llm:
42
+ return self.get('llm', key)
43
+
44
+ def set_embedding_cache(self, key, value):
45
+ if self.cache_embedding:
46
+ self.set('embedding', key, value)
47
+
48
+ def get_embedding_cache(self, key):
49
+ if self.cache_embedding:
50
+ return self.get('embedding', key)
51
+
52
+
53
+ global_cache = TinyDBCache()
54
+
55
+
56
+ def embedding_single(text) -> [float]:
57
+ """
58
+ embedding the text and return a embedding (list of float) for the string
59
+ """
60
+ global global_cache
61
+ key = _md5(text)
62
+ embedding = global_cache.get_embedding_cache(key)
63
+ if embedding is not None:
64
+ # print('embedding cache hitted')
65
+ return embedding
66
+ texts = [text]
67
+ from litellm import embedding
68
+ resp = embedding(model = _get_embedding_model(),
69
+ input=texts
70
+ )
71
+ result = [x['embedding'] for x in resp['data']]
72
+ embedding = result[0]
73
+ global_cache.set_embedding_cache(key, embedding)
74
+ return embedding
75
+
76
+
77
+ def test_embedding_single():
78
+ size = None
79
+ from GeneralAgent.utils import EnvironmentVariableManager
80
+ with EnvironmentVariableManager('EMBEDDING_CACHE', 'no'):
81
+ for source_type in ['OPENAI', 'AZURE']:
82
+ with EnvironmentVariableManager('LLM_SOURCE', source_type):
83
+ result = embedding_single("Say this is a test")
84
+ # print(source_type, result)
85
+ if size is not None:
86
+ assert len(result) == size
87
+ size = len(result)
88
+
89
+ def test_embedding_batch():
90
+ size = None
91
+ from GeneralAgent.utils import EnvironmentVariableManager
92
+ with EnvironmentVariableManager('EMBEDDING_CACHE', 'no'):
93
+ for source_type in ['OPENAI', 'AZURE']:
94
+ with EnvironmentVariableManager('LLM_SOURCE', source_type):
95
+ result = embedding_batch(["Say this is a test"])
96
+ if size is not None:
97
+ assert len(result[0]) == size
98
+ size = len(result[0])
99
+
100
+
101
+ def embedding_batch(texts) -> [[float]]:
102
+ """
103
+ embedding the texts(list of string), and return a list of embedding (list of float) for every string
104
+ """
105
+ global global_cache
106
+ embeddings = {}
107
+ remain_texts = []
108
+ for text in texts:
109
+ key = _md5(text)
110
+ embedding = global_cache.get_embedding_cache(key)
111
+ if embedding is not None:
112
+ embeddings[text] = embedding
113
+ else:
114
+ remain_texts.append(text)
115
+ if len(remain_texts) > 0:
116
+ result = _embedding_many(remain_texts)
117
+ for text, embedding in zip(remain_texts, result):
118
+ key = _md5(text)
119
+ global_cache.set_embedding_cache(key, embedding)
120
+ embeddings[text] = embedding
121
+ return [embeddings[text] for text in texts]
122
+
123
+
124
+ def _embedding_many(texts) -> [[float]]:
125
+ from litellm import embedding
126
+ # 每次最多embedding 16个
127
+ max_batch_size = 16
128
+ result = []
129
+ for i in range(0, len(texts), max_batch_size):
130
+ resp = embedding(model=_get_embedding_model(),input=texts[i:i+max_batch_size])
131
+ result += [x['embedding'] for x in resp['data']]
132
+ return result
133
+
134
+
135
+ def cos_sim(a, b):
136
+ import numpy as np
137
+ from numpy.linalg import norm
138
+ # This function calculates the cosine similarity (scalar value) between two input vectors 'a' and 'b' (1-D array object), and return the similarity.
139
+ a = a if isinstance(a, np.ndarray) else np.array(a)
140
+ b = b if isinstance(b, np.ndarray) else np.array(b)
141
+ return np.dot(a, b)/(norm(a)*norm(b))
142
+
143
+
144
+ def search_similar_texts(focal:str, texts:[str], top_k=5):
145
+ """
146
+ search the most similar texts in texts, and return the top_k similar texts
147
+ """
148
+ embeddings = embedding_batch([focal] + texts)
149
+ focal_embedding = embeddings[0]
150
+ texts_embeddings = embeddings[1:]
151
+ import numpy as np
152
+ similarities = np.dot(texts_embeddings, focal_embedding)
153
+ sorted_indices = np.argsort(similarities)
154
+ sorted_indices = sorted_indices[::-1]
155
+ return [texts[i] for i in sorted_indices[:top_k]]
156
+
157
+
158
+ def _md5(obj):
159
+ import hashlib, json
160
+ if isinstance(obj, str):
161
+ return hashlib.md5(obj.encode('utf-8')).hexdigest()
162
+ else:
163
+ return hashlib.md5(json.dumps(obj).encode('utf-8')).hexdigest()
164
+
165
+
166
+ def _get_llm_model(messages, model_type):
167
+ import os
168
+ from GeneralAgent import skills
169
+ assert model_type in ['normal', 'smart', 'long']
170
+ if model_type == 'normal' and skills.messages_token_count(messages) > 3000:
171
+ model_type = 'long'
172
+ api_type = os.environ.get('LLM_SOURCE', 'OPENAI')
173
+ model_key = f'{api_type}_LLM_MODEL_{model_type.upper()}'
174
+ model = os.environ.get(model_key, None)
175
+ if model is not None:
176
+ return model
177
+ model_key = f'{api_type}_LLM_MODEL_NORMAL'
178
+ return os.environ.get(model_key, 'gpt-3.5-turbo')
179
+
180
+
181
+ def get_llm_token_limit(model_type):
182
+ """
183
+ return the token limit for the model
184
+ """
185
+ import os
186
+ assert model_type in ['normal', 'smart', 'long']
187
+ api_type = os.environ.get('LLM_SOURCE', 'OPENAI')
188
+ # OPENAI_LLM_MODEL_SMART_LIMIT
189
+ limit_key = f'{api_type}_LLM_MODEL_{model_type.upper()}_LIMIT'
190
+ limit = os.environ.get(limit_key, None)
191
+ if limit is not None:
192
+ return int(limit)
193
+ limit_key = f'{api_type}_LLM_MODEL_NORMAL_LIMIT'
194
+ return int(os.environ.get(limit_key, 4000))
195
+
196
+
197
+ def test_get_llm_token_limit():
198
+ from GeneralAgent.utils import EnvironmentVariableManager
199
+ with EnvironmentVariableManager('LLM_SOURCE', 'OPENAI'):
200
+ with EnvironmentVariableManager('OPENAI_LLM_MODEL_SMART_LIMIT', '8000'):
201
+ assert get_llm_token_limit('smart') == 8000
202
+
203
+
204
+ def _get_embedding_model():
205
+ import os
206
+ api_type = os.environ.get('LLM_SOURCE', 'OPENAI')
207
+ embedding_model = os.environ.get(f'{api_type}_EMBEDDING_MODEL', 'text-embedding-ada-002')
208
+ return embedding_model
209
+
210
+ def _get_temperature():
211
+ import os
212
+ temperature = float(os.environ.get('LLM_TEMPERATURE', 0.5))
213
+ return temperature
214
+
215
+
216
+ def llm_inference(messages, model_type='normal', stream=False, json_schema=None):
217
+ """
218
+ Run LLM (large language model) inference on the provided messages using the specified model.
219
+
220
+ Parameters:
221
+ messages: Input messages for the model, like [{'role': 'system', 'content': 'You are a helpful assistant'}, {'role': 'user', 'content': 'What is your name?'}]
222
+ model_type: Type of model to use. Options are 'normal', 'smart', 'long'
223
+ use_stream: Boolean indicating if the function should use streaming inference
224
+ json_format: Optional JSON schema string
225
+
226
+ Returns:
227
+ If use_stream is True, returns a generator that yields the inference results as they become available.
228
+ If use_stream is False, returns a string containing the inference result.
229
+ If json_format is provided, the inference result is parsed according to the provided JSON schema and returned as a dictionary.
230
+
231
+ Note:
232
+ The total number of tokens in the messages and the returned string must be less than 4000 when model_variant is 'normal', and less than 16000 when model_variant is 'long'.
233
+ """
234
+ import logging
235
+ if stream:
236
+ return _llm_inference_with_stream(messages, model_type)
237
+ else:
238
+ if json_schema is None:
239
+ return _llm_inference_without_stream(messages, model_type)
240
+ else:
241
+ import json
242
+ if not isinstance(json_schema, str):
243
+ json_schema = json.dumps(json_schema)
244
+ messages[-1]['content'] += '\n' + return_json_prompt + json_schema
245
+ # messages += [{'role': 'user', 'content': return_json_prompt + json_schema}]
246
+ logging.debug(messages)
247
+ result = _llm_inference_without_stream(messages, model_type)
248
+ logging.debug(result)
249
+ return json.loads(fix_llm_json_str(result))
250
+
251
+
252
+ def test_llm_inference():
253
+ from GeneralAgent.utils import EnvironmentVariableManager
254
+ with EnvironmentVariableManager('LLM_CACHE', 'no'):
255
+ for source_type in ['OPENAI', 'AZURE']:
256
+ with EnvironmentVariableManager('LLM_SOURCE', source_type):
257
+ model_types = ['normal', 'smart', 'long']
258
+ for model_type in model_types:
259
+ result = llm_inference([{"role": "user","content": "Say this is a test",}], model_type=model_type, stream=False)
260
+ print(source_type, model_type, result)
261
+ assert 'test' in result
262
+ result = ''
263
+ response = llm_inference([{"role": "user","content": "Say this is a test",}], model_type=model_type, stream=True)
264
+ for token in response:
265
+ result += token
266
+ print(source_type, model_type, result)
267
+ assert 'test' in result
268
+
269
+
270
+ def simple_llm_inference(messages, json_schema=None):
271
+ """
272
+ Run LLM (large language model) inference on the provided messages
273
+
274
+ Parameters:
275
+ messages: Input messages for the model, like [{'role': 'system', 'content': 'You are a helpful assistant'}, {'role': 'user', 'content': 'What is your name?'}]
276
+ json_format: Optional JSON schema string
277
+
278
+ Returns:
279
+ If json_format is provided, the inference result is parsed according to the provided JSON schema and returned as a dictionary.
280
+ Else return a string
281
+
282
+ Note:
283
+ The total number of tokens in the messages and the returned string must be less than 16000.
284
+ """
285
+ from GeneralAgent import skills
286
+ return skills.llm_inference(messages, json_schema=json_schema)
287
+
288
+
289
+ # @retry(stop=stop_after_attempt(3), wait=wait_fixed(3))
290
+ async def async_llm_inference(messages, model_type='normal'):
291
+ from litellm import acompletion
292
+ import logging
293
+ global global_cache
294
+ logging.debug(messages)
295
+ key = _md5(messages)
296
+ result = global_cache.get_llm_cache(key)
297
+ if result is not None:
298
+ return result
299
+ model = _get_llm_model(messages, model_type)
300
+ temperature = _get_temperature()
301
+ try_count = 3
302
+ while try_count > 0:
303
+ try:
304
+ response = await acompletion(model=model, messages=messages, temperature=temperature)
305
+ result = response['choices'][0]['message']['content']
306
+ global_cache.set_llm_cache(key, result)
307
+ return result
308
+ except Exception as e:
309
+ try_count -= 1
310
+ import asyncio
311
+ await asyncio.sleep(3)
312
+ if try_count == 0:
313
+ raise ValueError('LLM(Large Languate Model) error, Please check your key or base_url, or network')
314
+ return ''
315
+
316
+
317
+ # @retry(stop=stop_after_attempt(3), wait=wait_fixed(3))
318
+ def _llm_inference_with_stream(messages, model_type='normal'):
319
+ """
320
+ messages: llm messages, model_type: normal, smart, long
321
+ """
322
+ from litellm import completion
323
+ import logging
324
+ # from GeneralAgent import skills
325
+ model = _get_llm_model(messages, model_type)
326
+ logging.debug(messages)
327
+ global global_cache
328
+ key = _md5(messages)
329
+ result = global_cache.get_llm_cache(key)
330
+ if result is not None:
331
+ # print('llm_inference cache hitted')
332
+ for x in result.split(' '):
333
+ yield x + ' '
334
+ yield '\n'
335
+ # yield None
336
+ else:
337
+ temperature = _get_temperature()
338
+ try_count = 3
339
+ while try_count > 0:
340
+ try:
341
+ response = completion(model=model, messages=messages, stream=True, temperature=temperature)
342
+ result = ''
343
+ for chunk in response:
344
+ # print(chunk)
345
+ if chunk['choices'][0]['finish_reason'] is None:
346
+ token = chunk['choices'][0]['delta']['content']
347
+ if token is None:
348
+ continue
349
+ result += token
350
+ global_cache.set_llm_cache(key, result)
351
+ yield token
352
+ break
353
+ except Exception as e:
354
+ try_count -= 1
355
+ import time
356
+ time.sleep(3)
357
+ if try_count == 0:
358
+ raise ValueError('LLM(Large Languate Model) error, Please check your key or base_url, or network')
359
+ # logging.info(result)
360
+ # yield None
361
+
362
+ # if we choose to use local llm for inferce, we can use the following completion function.
363
+ #def compeltion(model,messages,temperature):
364
+ # pass
365
+
366
+
367
+ # @retry(stop=stop_after_attempt(3), wait=wait_fixed(3))
368
+ def _llm_inference_without_stream(messages, model_type='normal'):
369
+ from litellm import completion
370
+ import logging
371
+ global global_cache
372
+ logging.debug(messages)
373
+ # print(messages)
374
+ key = _md5(messages)
375
+ result = global_cache.get_llm_cache(key)
376
+ if result is not None:
377
+ return result
378
+ model = _get_llm_model(messages, model_type)
379
+ temperature = _get_temperature()
380
+ try_count = 3
381
+ while try_count > 0:
382
+ try:
383
+ response = completion(model=model, messages=messages, temperature=temperature)
384
+ result = response['choices'][0]['message']['content']
385
+ global_cache.set_llm_cache(key, result)
386
+ return result
387
+ except Exception as e:
388
+ try_count -= 1
389
+ import time
390
+ time.sleep(3)
391
+ if try_count == 0:
392
+ raise ValueError('LLM(Large Languate Model) error, Please check your key or base_url, or network')
393
+ return ''
394
+
395
+
396
+ def fix_llm_json_str(string):
397
+ import json
398
+ import re
399
+ new_string = string.strip()
400
+ if new_string.startswith('```json'):
401
+ new_string = new_string[7:]
402
+ if new_string.endswith('```'):
403
+ new_string = new_string[:-3]
404
+ try:
405
+ json.loads(new_string)
406
+ return new_string
407
+ except Exception as e:
408
+ print("fix_llm_json_str failed 1:", e)
409
+ try:
410
+ pattern = r'```json(.*?)```'
411
+ match = re.findall(pattern, new_string, re.DOTALL)
412
+ if match:
413
+ new_string = match[-1]
414
+
415
+ json.loads(new_string)
416
+ return new_string
417
+ except Exception as e:
418
+ print("fix_llm_json_str failed 2:", e)
419
+ try:
420
+ new_string = new_string.replace("\n", "\\n")
421
+ json.loads(new_string)
422
+ return new_string
423
+ except Exception as e:
424
+ print("fix_llm_json_str failed 3:", e)
425
+
426
+ messages = [{
427
+ "role": "system",
428
+ "content": """Do not change the specific content, fix the json, directly return the repaired JSON, without any explanation and dialogue.
429
+ ```
430
+ """+new_string+"""
431
+ ```"""
432
+ }]
433
+
434
+ message = llm_inference(messages)
435
+ pattern = r'```json(.*?)```'
436
+ match = re.findall(pattern, message, re.DOTALL)
437
+ if match:
438
+ return match[-1]
439
+
440
+ return message
441
+
442
+ return_json_prompt = """\n\nYou should only directly respond in JSON format without explian as described below, that must be parsed by Python json.loads.
443
+ Response JSON schema: \n"""
444
+
445
+
446
+ # def prompt_call(prompt_template, variables, json_schema=None):
447
+ # from jinja2 import Template
448
+ # import json
449
+ # prompt = Template(prompt_template).render(**variables)
450
+ # if json_schema is not None:
451
+ # prompt += return_json_prompt + json_schema
452
+ # result = llm_inference([{'role': 'system', 'content': 'You are a helpful assistant.'}, {'role': 'system', 'content': prompt}], model_type='smart')
453
+ # return json.loads(fix_llm_json_str(result))
454
+ # else:
455
+ # result = llm_inference([{'role': 'system', 'content': prompt}], model_type='smart')
456
+
457
+ if __name__ == '__main__':
458
+ test_embedding_single()
GeneralAgent/skills/memory_utils.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ def _parse_segment_llm_result(text):
4
+ import logging
5
+ lines = text.strip().split('\n')
6
+ key = None
7
+ nodes = {}
8
+ # print(lines)
9
+ for line in lines:
10
+ # print(line)
11
+ line = line.strip()
12
+ if len(line) == 0:
13
+ continue
14
+ if line.startswith('<<') and line.endswith('>>'):
15
+ key = line[2:-2]
16
+ else:
17
+ if key is None:
18
+ logging.warning(f'key is None, line: {line}')
19
+ continue
20
+ blocks = line.split(':')
21
+ if len(blocks) >= 2:
22
+ start = int(blocks[0])
23
+ end = int(blocks[1])
24
+ nodes[key] = (start, end)
25
+ return nodes
26
+
27
+
28
+ async def segment_text(text):
29
+ """
30
+ 将文本进行语义分段,返回分段后的文本和key组成的字典nodes
31
+ """
32
+ from GeneralAgent import skills
33
+ from jinja2 import Template
34
+ segment_prompt = """
35
+ ---------
36
+ {{text}}
37
+ ---------
38
+
39
+ For the text enclosed by ---------, the number following # is the line number.
40
+ Your task is to divide the text into segments (up to 6), each represented by the start and end line numbers. Additionally, assign a brief title (not exceeding 10 words) to each segment.
41
+ The output format is as follows:
42
+ ```
43
+ <<Title for Segment>>
44
+ Start_line: End_line
45
+
46
+ <<Title for Segment>>
47
+ Start_line: End_line
48
+ ```
49
+ For instance:
50
+ ```
51
+ <<Hello>>
52
+ 0:12
53
+
54
+ <<World>>
55
+ 13:20
56
+ ```
57
+ Please note, each title should not exceed 10 words. Titles exceeding this limit will be considered invalid. Strive to keep your titles concise yet reflective of the main content in the segment.
58
+ """
59
+ lines = text.strip().split('\n')
60
+ new_lines = []
61
+ for index in range(len(lines)):
62
+ new_lines.append(f'#{index} {lines[index]}')
63
+ new_text = '\n'.join(new_lines)
64
+ prompt = Template(segment_prompt).render({'text': new_text})
65
+ messages = [
66
+ {'role': 'system','content': 'You are a helpful assistant'},
67
+ {'role': 'user','content': prompt}
68
+ ]
69
+ model_type='normal'
70
+ if skills.messages_token_count(messages) > 3500:
71
+ model_type = 'long'
72
+ result = await skills.async_llm_inference(messages, model_type)
73
+ # print(result)
74
+ nodes = _parse_segment_llm_result(result)
75
+ for key in nodes:
76
+ start, end = nodes[key]
77
+ nodes[key] = '\n'.join(lines[start:end])
78
+ return nodes
79
+
80
+
81
+ async def summarize_text(text):
82
+ from GeneralAgent import skills
83
+ prompt = "Please distill the content between --------- into a concise phrase or sentence that captures the essence without any introductory phrases."
84
+ # prompt = "请将---------之间的内容提炼成一个简洁的短语或句子,抓住要点,无需任何介绍性短语。"
85
+ messages = [
86
+ {'role': 'system','content': 'You are a helpful assistant'},
87
+ {'role': 'user','content': f'{prompt}.\n---------\n{text}\n---------'}
88
+ ]
89
+ result = await skills.async_llm_inference(messages)
90
+ return result
91
+
92
+ async def extract_info(background, task):
93
+ prompt_template = """
94
+ Background (line number is indicated by #number, and <<title>> is a link to the details):
95
+ ---------
96
+ {{background}}
97
+ ---------
98
+
99
+ Task
100
+ ---------
101
+ {{task}}
102
+ ---------
103
+
104
+ Please provide the line numbers in the background that contain information relevant to solving the task.
105
+ Then, provide the <<titles>> that provide further details related to the background information.
106
+ The expected output format is as follows:
107
+ ```
108
+ #Line Number 1
109
+ #Line Number 2
110
+ ...
111
+ <<title 1>>
112
+ <<title 2>>
113
+ ...
114
+ ```
115
+ If no relevant information is found, please output "[Nothing]".
116
+ ```
117
+ [Nothing]
118
+ ```
119
+ """
120
+
121
+ from GeneralAgent import skills
122
+ from jinja2 import Template
123
+ prompt = Template(prompt_template).render({'background': background, 'task': task})
124
+ messages = [
125
+ {'role': 'system','content': 'You are a helpful assistant'},
126
+ {'role': 'user','content': prompt}
127
+ ]
128
+ result = await skills.async_llm_inference(messages)
129
+ return result
130
+
131
+
132
+ def parse_extract_info(text):
133
+ import re
134
+ numbers = re.findall(r'#(\d+)', text)
135
+ numbers = [int(x) for x in numbers]
136
+ titles = re.findall(r'<<([^>>]+)>>', text)
137
+ return numbers, titles
138
+
139
+
140
+ def extract_title(text):
141
+ """
142
+ extract title from text
143
+ """
144
+ if len(text) > 500:
145
+ text = text[:500]
146
+ prompt = "Please distill the content between --------- into a concise title of the content, less than five words.\n---------\n" + text + "\n---------"
147
+ from GeneralAgent import skills
148
+ messages = [
149
+ {'role': 'system','content': 'You are a helpful assistant'},
150
+ {'role': 'user','content': prompt}
151
+ ]
152
+ result = skills.llm_inference(messages)
153
+ return result
GeneralAgent/skills/merge_video_audio/merge_video_audio.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def merge_video_audio(video_path:str, narration_path:str=None, music_path:str=None) -> str:
2
+ """
3
+ Merge video, narration, and background music into a final video based on the shortest length among all elements.
4
+ Parameters: video_path -- path of the video file, string
5
+ narration_path -- path of the narration audio file, string, can be None
6
+ music_path -- path of the background music file, string, can be None
7
+ Returns: the path of the final video file, string
8
+ """
9
+ from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip
10
+
11
+ # Load video file
12
+ video = VideoFileClip(video_path)
13
+
14
+ # Load audio files if they exist
15
+ audio_clips = []
16
+ durations = [video.duration]
17
+ if narration_path is not None:
18
+ narration = AudioFileClip(narration_path)
19
+ audio_clips.append(narration)
20
+ durations.append(narration.duration)
21
+ if music_path is not None:
22
+ music = AudioFileClip(music_path)
23
+ audio_clips.append(music)
24
+ durations.append(music.duration)
25
+
26
+ # Determine base length
27
+ base_length = min(durations)
28
+
29
+ # Adjust lengths of elements
30
+ audio_clips = [clip.subclip(0, base_length) for clip in audio_clips]
31
+
32
+ # Merge audio files
33
+ if len(audio_clips) == 1:
34
+ final_audio = audio_clips[0]
35
+ elif audio_clips:
36
+ final_audio = CompositeAudioClip(audio_clips)
37
+ else:
38
+ final_audio = None
39
+
40
+ # Set audio of video file to final audio
41
+ final_video = video.subclip(0, base_length)
42
+ if final_audio is not None:
43
+ final_video = final_video.set_audio(final_audio)
44
+
45
+ # Save final video file
46
+ from GeneralAgent import skills
47
+ final_video_path = skills.unique_name() + ".mp4"
48
+ final_video.write_videofile(final_video_path)
49
+
50
+ return final_video_path
51
+
52
+
53
+ def test_merge_video_audio():
54
+ """
55
+ Test merge_video_audio function
56
+ """
57
+ import os
58
+ video_path = os.path.join(os.path.dirname(__file__), "video.mp4")
59
+ narration_path = os.path.join(os.path.dirname(__file__), "narration.mp3")
60
+ music_path = os.path.join(os.path.dirname(__file__), "music.wav")
61
+ final_video_path = merge_video_audio(video_path, narration_path, music_path)
62
+ assert os.path.exists(final_video_path)
63
+ os.remove(final_video_path)
64
+ # os.remove("./final_audio.mp3")
65
+
66
+ if __name__ == '__main__':
67
+ test_merge_video_audio()
GeneralAgent/skills/merge_video_audio/music.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27e302b1c36a4feb0a89f31f1cc7981d20356305396edf08fada7c36d57ed3a7
3
+ size 3840078
GeneralAgent/skills/merge_video_audio/narration.mp3 ADDED
Binary file (134 kB). View file
 
GeneralAgent/skills/merge_video_audio/tmp_audio.mp3 ADDED
Binary file (237 Bytes). View file
 
GeneralAgent/skills/merge_video_audio/video.mp4 ADDED
Binary file (249 kB). View file
 
GeneralAgent/skills/musicgen/generate_music.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def generate_music(prompt: str, model_version: str = 'stereo-melody-large', duration: int = 8, output_format: str = 'wav') -> str:
2
+ """
3
+ Generate music according to the prompt and return the music file path. prompt should be english.
4
+ @param prompt: A description of the music you want to generate.
5
+ @param model_version: Model to use for generation. Default is 'stereo-melody-large'.
6
+ @param duration: Duration of the generated audio in seconds. Default is 8.
7
+ @param output_format: Output format for generated audio. Default is 'wav'.
8
+ """
9
+ import replicate
10
+ from GeneralAgent import skills
11
+
12
+ output = replicate.run(
13
+ "meta/musicgen:7be0f12c54a8d033a0fbd14418c9af98962da9a86f5ff7811f9b3423a1f0b7d7",
14
+ input={
15
+ "model_version": model_version,
16
+ "prompt": prompt,
17
+ "duration": duration,
18
+ "output_format": output_format
19
+ }
20
+ )
21
+ music_url = output
22
+ music_path = skills.try_download_file(music_url)
23
+ print(f'Music created at {music_path}')
24
+ return music_path
25
+
26
+ def test_generate_music():
27
+ import os
28
+ music_path = generate_music('Happy birthday song', 'stereo-melody-large', 10, 'mp3')
29
+ assert os.path.exists(music_path), "Music file does not exist."
30
+
31
+ if __name__ == '__main__':
32
+ test_generate_music()
GeneralAgent/skills/python_envs.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def get_current_env_python_libs() -> str:
3
+ """
4
+ Return the python libs that installed in current env
5
+ """
6
+ import os
7
+ requirements_path = os.path.join(os.path.dirname(__file__), '../requirements.txt')
8
+ with open(requirements_path, 'r', encoding='utf-8') as f:
9
+ requirements = f.read()
10
+ requirements = requirements.replace('\n', ' ')
11
+ return requirements.strip()
12
+
13
+
14
+ def get_python_version() -> str:
15
+ """
16
+ Return the python version, like "3.9.12"
17
+ """
18
+ import platform
19
+ python_version = platform.python_version()
20
+ return python_version
21
+
22
+ def get_os_version() -> str:
23
+ import platform
24
+ system = platform.system()
25
+ if system == 'Windows':
26
+ version = platform.version()
27
+ return f"Windows version: {version}"
28
+ elif system == 'Darwin':
29
+ version = platform.mac_ver()[0]
30
+ return f"macOS version: {version}"
31
+ elif system == 'Linux':
32
+ version = platform.platform()
33
+ return f"Linux version: {version}"
34
+ else:
35
+ return "Unknown system"
36
+
37
+ def get_python_code(content:str) -> str:
38
+ """
39
+ Return the python code text from content
40
+ """
41
+ template = '```python\n(.*?)\n```'
42
+ import re
43
+ code = re.findall(template, content, re.S)
44
+ if len(code) > 0:
45
+ return code[0]
46
+ else:
47
+ return content
48
+
49
+ def test_get_python_code():
50
+ content = """
51
+ ```python
52
+ import os
53
+ print(os.getcwd())
54
+ ```
55
+ """
56
+ assert get_python_code(content) == 'import os\nprint(os.getcwd())'
57
+
58
+
59
+ def load_functions_with_path(python_code_path):
60
+ try:
61
+ import importlib.util
62
+ import inspect
63
+
64
+ # 指定要加载的文件路径和文件名
65
+ module_name = 'skills'
66
+ module_file = python_code_path
67
+
68
+ # 使用importlib加载文件
69
+ spec = importlib.util.spec_from_file_location(module_name, module_file)
70
+ module = importlib.util.module_from_spec(spec)
71
+ spec.loader.exec_module(module)
72
+
73
+ # 获取文件中的所有函数
74
+ functions = inspect.getmembers(module, inspect.isfunction)
75
+
76
+ # 过滤functions中以下划线开头的函数
77
+ functions = filter(lambda f: not f[0].startswith('_'), functions)
78
+
79
+ return [f[1] for f in functions], None
80
+ except Exception as e:
81
+ # 代码可能有错误,加载不起来
82
+ import logging
83
+ logging.exception(e)
84
+ return [], str(e)
85
+
86
+ def get_function_signature(func, module:str=None):
87
+ """Returns a description string of function"""
88
+ import inspect
89
+ sig = inspect.signature(func)
90
+ sig_str = str(sig)
91
+ desc = f"{func.__name__}{sig_str}"
92
+ if func.__doc__:
93
+ desc += ': ' + func.__doc__.strip()
94
+ if module is not None:
95
+ desc = f'{module}.{desc}'
96
+ if inspect.iscoroutinefunction(func):
97
+ desc = "async " + desc
98
+ return desc
99
+
100
+
101
+ def python_line_is_variable_expression(line):
102
+ """
103
+ Return True if line is a variable expression, else False
104
+ """
105
+ import ast
106
+ try:
107
+ tree = ast.parse(line)
108
+ except SyntaxError:
109
+ return False
110
+
111
+ if len(tree.body) != 1 or not isinstance(tree.body[0], ast.Expr):
112
+ return False
113
+
114
+ expr = tree.body[0].value
115
+ if isinstance(expr, ast.Call):
116
+ return False
117
+
118
+ return True
119
+
120
+
121
+ def test_python_line_is_variable_expression():
122
+ assert python_line_is_variable_expression('a')
123
+ assert python_line_is_variable_expression('a, b')
124
+ assert python_line_is_variable_expression('a + b')
125
+ assert python_line_is_variable_expression('vars[0]')
126
+ assert python_line_is_variable_expression('scrape_web("https://www.baidu.com")[0]')
127
+
128
+ assert python_line_is_variable_expression(' vars[0]') is False
129
+ assert python_line_is_variable_expression('print(a)') is False
130
+ assert python_line_is_variable_expression('x = a + b') is False
GeneralAgent/skills/replicate_api.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # replicate api
2
+
3
+ def _replicate_image_generation(prompt):
4
+ """generate a image with prompt (in english), return a image url"""
5
+
6
+ # import replicate
7
+ # output = replicate.run(
8
+ # "cjwbw/taiyi-stable-diffusion-1b-chinese-v0.1:36c580142e9fbbd52e5e678e30541c0da6f2021c9d2039a5c00be192a010e8c5",
9
+ # input={"prompt": prompt}
10
+ # )
11
+ # print(output)
12
+
13
+ # 图片生成切换成为sdxl的version 1 版本
14
+ import replicate
15
+
16
+ output = replicate.run(
17
+ # "stability-ai/stable-diffusion:ac732df83cea7fff18b8472768c88ad041fa750ff7682a21affe81863cbe77e4",
18
+ "stability-ai/sdxl:2f779eb9b23b34fe171f8eaa021b8261566f0d2c10cd2674063e7dbcd351509e",
19
+ input={"prompt": prompt}
20
+ )
21
+ image_url = output[0]
22
+ # print(image_url)
23
+
24
+ return image_url
25
+
26
+
27
+ def image_generation(prompt) -> str:
28
+ """
29
+ Generate an image according to the prompt and return the image path. For example, when the prompt is "apple" you will get an image of an apple. Note: The prompt should describe objective things in detail, not abstract concepts. For example, if you want to draw a picture of Chengdu, the prompt should be "Picture of Chengdu, with giant pandas playing in the bamboo forest, people eating hot pot around, and a Jinsha Sunbird totem next to it" instead of "Draw a picture of Chengdu" "painting"
30
+ @param prompt: The prompt should be detailed enough to describe the image. Tips can be in any type of language, but English is recommended.
31
+ """
32
+ from GeneralAgent import skills
33
+ if not skills.text_is_english(prompt):
34
+ prompt = skills.translate_text(prompt, 'english')
35
+ image_url = _replicate_image_generation(prompt)
36
+ image_path = skills.try_download_file(image_url)
37
+ print(f'image created at ![{image_path}]({image_path})')
38
+ return image_path
39
+
40
+ def face_restoration(image_path):
41
+ """ Practical face restoration algorithm for old photos or AI-generated faces. input image path, and return the new image path"""
42
+ import replicate
43
+ from GeneralAgent import skills
44
+ image_url = replicate.run(
45
+ "tencentarc/gfpgan:9283608cc6b7be6b65a8e44983db012355fde4132009bf99d976b2f0896856a3",
46
+ input={"img": open(image_path, "rb")}
47
+ )
48
+ new_image_path = skills.try_download_file(image_url)
49
+ return new_image_path
50
+
51
+ def qrcode_stable_diffusion(prompt, qr_code_content):
52
+ """generate a qrcode image with prompt, return a image url"""
53
+ import replicate
54
+ output = replicate.run(
55
+ "nateraw/qrcode-stable-diffusion:9cdabf8f8a991351960c7ce2105de2909514b40bd27ac202dba57935b07d29d4",
56
+ input={"prompt": prompt, 'qr_code_content': qr_code_content}
57
+ )
58
+ return output[0]
59
+
60
+ def speech_to_text(audio_file_path):
61
+ """Convert speech in audio to text, return a text and the language of the text"""
62
+ import replicate
63
+ output = replicate.run(
64
+ "openai/whisper:91ee9c0c3df30478510ff8c8a3a545add1ad0259ad3a9f78fba57fbc05ee64f7",
65
+ input={"audio": open(audio_file_path, "rb")}
66
+ )
67
+ print(output)
68
+ language = output['detected_language']
69
+ text = output['transcription']
70
+
71
+ return text, language
72
+
73
+
74
+ if __name__ == '__main__':
75
+ image_generation('a cat')
GeneralAgent/skills/scrape_dynamic_web.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def scrape_dynamic_web(url):
2
+ """
3
+ This function takes a url and returns the text content of the web page.
4
+ It uses selenium to load the dynamic content of the page, and BeautifulSoup to parse the HTML and extract the text.
5
+ It also replaces <span> tags with their text content, and <br> tags with newline characters.
6
+ """
7
+ from selenium import webdriver
8
+ from selenium.webdriver.chrome.service import Service
9
+ from webdriver_manager.chrome import ChromeDriverManager
10
+ from selenium.webdriver.common.by import By
11
+ from selenium.webdriver.chrome.options import Options
12
+ from selenium.webdriver.common.action_chains import ActionChains
13
+ from selenium.webdriver.support.ui import WebDriverWait
14
+ from selenium.webdriver.support import expected_conditions as EC
15
+ from selenium.webdriver.common.by import By
16
+ from bs4 import BeautifulSoup
17
+ import time
18
+ import re
19
+ from urllib.parse import urljoin
20
+
21
+ # Setup chrome options
22
+ chrome_options = Options()
23
+ chrome_options.add_argument("--headless") # Ensure GUI is off
24
+ chrome_options.add_argument("--no-sandbox")
25
+ chrome_options.add_argument("--disable-dev-shm-usage")
26
+
27
+ # Set path to chromedriver as per your configuration
28
+ webdriver_service = Service(ChromeDriverManager().install())
29
+
30
+ # Choose Chrome Browser
31
+ driver = webdriver.Chrome(service=webdriver_service, options=chrome_options)
32
+ driver.get(url)
33
+
34
+ # Wait for the dynamic content to load
35
+ WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.TAG_NAME, 'body')))
36
+
37
+ html = driver.page_source
38
+ driver.quit()
39
+
40
+ # Parse html content
41
+ soup = BeautifulSoup(html, "html.parser")
42
+ for span in soup.find_all("span"):
43
+ span.replace_with(span.text)
44
+ for a in soup.find_all("a"):
45
+ href = urljoin(url, a.get('href'))
46
+ a.replace_with(f"[{a.text}]({href})")
47
+ for br in soup.find_all("br"):
48
+ br.replace_with("\n")
49
+ text = soup.get_text(separator="\n")
50
+
51
+ # Replace multiple newlines and spaces around them with a single newline
52
+ text = re.sub('\s*\n\s*', '\n', text)
53
+
54
+ # Collapse whitespace
55
+ text = ' '.join(text.split())
56
+
57
+ return text
58
+
59
+ def test_scrape_dynamic_web():
60
+ """
61
+ This function tests the scrape_dynamic_web function.
62
+ It asserts that the returned text contains the string 'replicate'.
63
+ """
64
+ url = "https://replicate.com/stability-ai/stable-video-diffusion/api?tab=python"
65
+ text = scrape_dynamic_web(url)
66
+ assert 'replicate' in text
67
+ print(text)
68
+
69
+ if __name__ == '__main__':
70
+ test_scrape_dynamic_web()
GeneralAgent/skills/split_text.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ def split_text(text, max_token=3000, separators='\n'):
4
+ """
5
+ Split the text into paragraphs, each paragraph has less than max_token tokens.
6
+ """
7
+ import re
8
+ from GeneralAgent import skills
9
+ pattern = "[" + re.escape(separators) + "]"
10
+ paragraphs = list(re.split(pattern, text))
11
+ print(len(paragraphs))
12
+ result = []
13
+ current = ''
14
+ for paragraph in paragraphs:
15
+ if skills.string_token_count(current) + skills.string_token_count(paragraph) > max_token:
16
+ result.append(current)
17
+ current = ''
18
+ current += paragraph + '\n'
19
+ if len(current) > 0:
20
+ result.append(current)
21
+ new_result = []
22
+ for x in result:
23
+ if skills.string_token_count(x) > max_token:
24
+ new_result.extend(split_text(x, max_token=max_token, separators=",。,.;;"))
25
+ else:
26
+ new_result.append(x)
27
+ new_result = [x.strip() for x in new_result if len(x.strip()) > 0]
28
+ return new_result
GeneralAgent/skills/stable_video_diffusion/dab774a452f3.jpg ADDED