Skip to content

Commit

Permalink
Merge pull request #26 from CosmosShadow/main
Browse files Browse the repository at this point in the history
同步
  • Loading branch information
jayceftg authored Nov 13, 2024
2 parents 8cc2e6c + 29c3ba3 commit e1aa394
Show file tree
Hide file tree
Showing 15 changed files with 156 additions and 816 deletions.
54 changes: 25 additions & 29 deletions GeneralAgent/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,27 @@
from GeneralAgent.interpreter import Interpreter
from GeneralAgent.interpreter import KnowledgeInterpreter
from GeneralAgent.interpreter import RoleInterpreter, PythonInterpreter
from GeneralAgent.utils import cut_messages, string_token_count


def default_output_callback(token):
if token is not None:
print(token, end='', flush=True)
else:
print('\n', end='', flush=True)


def default_check(check_content=None):
show = '确认 | 继续 (回车, yes, y, 是, ok) 或者 直接输入你的想法\n'
if check_content is not None:
show = f'{check_content}\n\n{show}'
response = input(show)
if response.lower() in ['', 'yes', 'y', '是', 'ok']:
return None
else:
return response


class Agent():
"""
Agent
Expand Down Expand Up @@ -41,7 +60,7 @@ def __init__(self,
base_url=None,
self_call=False,
continue_run=False,
output_callback=None,
output_callback=default_output_callback,
disable_python_run=False,
hide_python_code=False,
messages=[],
Expand Down Expand Up @@ -85,7 +104,6 @@ def __init__(self,
frequency_penalty: float, 频率惩罚, 在 -2 和 2 之间
"""
from GeneralAgent import skills
if workspace is None and len(knowledge_files) > 0:
raise Exception('workspace must be provided when knowledge_files is not empty')
if workspace is not None and not os.path.exists(workspace):
Expand All @@ -98,7 +116,7 @@ def __init__(self,
self.python_interpreter = PythonInterpreter(self, serialize_path=self._python_path)
self.python_interpreter.function_tools = functions
self.model = model or os.environ.get('DEFAULT_LLM_MODEL', 'gpt-4o')
self.token_limit = token_limit or skills.get_llm_token_limit(self.model)
self.token_limit = token_limit or 64 * 1000
self.api_key = api_key
self.base_url = base_url
# self.temperature = temperature
Expand All @@ -108,12 +126,7 @@ def __init__(self,
self.knowledge_interpreter = KnowledgeInterpreter(workspace, knowledge_files=knowledge_files, rag_function=rag_function)
self.interpreters = [self.role_interpreter, self.python_interpreter, self.knowledge_interpreter]
self.enter_index = None # 进入 with 语句时 self.memory.messages 的索引
if output_callback is not None:
self.output_callback = output_callback
else:
# 默认输出回调函数
from GeneralAgent import skills
self.output_callback = skills.output
self.output_callback = output_callback

def __enter__(self):
self.enter_index = len(self.memory.get_messages()) # Record the index of self.messages
Expand Down Expand Up @@ -222,22 +235,7 @@ def run(self, command:Union[str, list], return_type=str, display=False, verbose=
if not display:
self.disable_output_callback()
try:
from GeneralAgent import skills
result = self._run(command, return_type=return_type, verbose=verbose)
if user_check:
# 没有渲染函数 & 没有输出回调函数: 用户不知道确认什么内容,则默认是str(result)
if check_render is None:
if self.output_callback is None:
show = str(result)
else:
show = ' '
else:
show = check_render(result)
response = skills.check(show)
if response is None:
return result
else:
return self.run(response, return_type, user_check=user_check, check_render=check_render)
return result
except Exception as e:
logging.exception(e)
Expand All @@ -259,7 +257,7 @@ def user_input(self, input:Union[str, list], verbose=True):
if self.continue_run and self.run_level == 0:
# 判断是否继续执行
messages = self.memory.get_messages()
messages = skills.cut_messages(messages, 2*1000)
messages = cut_messages(messages, 2*1000)
the_prompt = "对于当前状态,无需用户输入或者确认,继续执行任务,请回复yes,其他情况回复no"
messages += [{'role': 'system', 'content': the_prompt}]
response = skills.llm_inference(messages, model='smart', stream=False, api_key=self.api_key, base_url=self.base_url, **self.llm_args)
Expand All @@ -277,7 +275,6 @@ def _run(self, input, return_type=str, verbose=False):
@verbose: bool, verbose mode
"""
from GeneralAgent import skills

result = ''
def local_output(token):
Expand Down Expand Up @@ -331,17 +328,16 @@ def _memory_add_input(self, input):


def _get_llm_messages(self):
from GeneralAgent import skills
# 获取记忆 + prompt
messages = self.memory.get_messages()
if self.disable_python_run:
prompt = '\n\n'.join([interpreter.prompt(messages) for interpreter in self.interpreters if interpreter.__class__ != PythonInterpreter])
else:
prompt = '\n\n'.join([interpreter.prompt(messages) for interpreter in self.interpreters])
# 动态调整记忆长度
prompt_count = skills.string_token_count(prompt)
prompt_count = string_token_count(prompt)
left_count = int(self.token_limit * 0.9) - prompt_count
messages = skills.cut_messages(messages, left_count)
messages = cut_messages(messages, left_count)
# 组合messages
messages = [{'role': 'system', 'content': prompt}] + messages
return messages
Expand Down
3 changes: 1 addition & 2 deletions GeneralAgent/interpreter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
from .python_interpreter import PythonInterpreter
from .knowledge_interpreter import KnowledgeInterpreter
from .applescript_interpreter import AppleScriptInterpreter
from .shell_interpreter import ShellInterpreter
from .link_retrieve_interpreter import LinkRetrieveInterpreter
from .shell_interpreter import ShellInterpreter
28 changes: 0 additions & 28 deletions GeneralAgent/interpreter/link_retrieve_interpreter.py

This file was deleted.

35 changes: 30 additions & 5 deletions GeneralAgent/interpreter/python_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,36 @@
from .interpreter import Interpreter
from functools import partial

def get_python_version() -> str:
"""
Return the python version, like "3.9.12"
"""
import platform
python_version = platform.python_version()
return python_version

def get_function_signature(func, module:str=None):
"""Returns a description string of function"""
try:
import inspect
sig = inspect.signature(func)
sig_str = str(sig)
desc = f"{func.__name__}{sig_str}"
if func.__doc__:
desc += ': ' + func.__doc__.strip()
if module is not None:
desc = f'{module}.{desc}'
if inspect.iscoroutinefunction(func):
desc = "" + desc
return desc
except Exception as e:
import logging
logging.exception(e)
return ''

default_import_code = """
import os, sys, math, time
from GeneralAgent import skills
from codyer import skills
"""

class PythonInterpreter(Interpreter):
Expand Down Expand Up @@ -59,7 +86,6 @@ def __init__(self,
@prompt_append: append to the prompt, custom prompt can be added here
@stop_wrong_count: stop running when the code is wrong for stop_wrong_count times
"""
from GeneralAgent import skills
self.globals = {} # global variables shared by all code
self.agent = agent
self.python_libs = libs
Expand All @@ -82,12 +108,11 @@ def load(self):
return {}

def prompt(self, messages) -> str:
from GeneralAgent import skills
funtions = '\n\n'.join([skills.get_function_signature(x) for x in self.function_tools])
funtions = '\n\n'.join([get_function_signature(x) for x in self.function_tools])
variables = {
'python_libs': self.python_libs,
'python_funcs': funtions,
'python_version': skills.get_python_version()
'python_version': get_python_version()
}
return Template(self.python_prompt_template).render(**variables) + self.prompt_append

Expand Down
93 changes: 3 additions & 90 deletions GeneralAgent/interpreter/role_interpreter.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,16 @@
import os
import datetime
import platform
from jinja2 import Template
from .interpreter import Interpreter

def get_os_version() -> str:
import platform
system = platform.system()
if system == 'Windows':
version = platform.version()
return f"Windows version: {version}"
elif system == 'Darwin':
version = platform.mac_ver()[0]
return f"macOS version: {version}"
elif system == 'Linux':
version = platform.platform()
return f"Linux version: {version}"
else:
return "Unknown system"

default_system_role = """
Current Time: {{now}}
You are an agent on the {{os_version}} computer, tasked with assisting users in resolving their issues.
You are an agent on the computer, tasked with assisting users in resolving their issues.
You have the capability to control the computer and access the internet.
All code in ```python ``` will be automatically executed by the system. So if you don't need to run the code, please don't write it in the code block.
All responses should be formatted using markdown. For file references, use the format [title](a.txt), with all files stored in the './' directory.
When result file is ready, provide it to the user with donwload link.
"""

self_call_prompt = """
# Self Control in Python:
- You are `agent` in python, and you can control yourself to do any task in future.
- Execute tasks by calling `agent.run('command', return_type=bool|str|dict|...)`.
- Ensure the 'command' string within `agent.run` does not exceed 5000 characters.
- Handle a wide range of tasks, not limited to text-based operations, by breaking down complex tasks into subtasks and executing them through self-calls.
- Use `agent.run` to complete parts of a task, not the entire task.
- Provide direct results when possible, without the need for additional calls to `agent.run('command', return_type=...)`.
- Complete highly complex tasks in one step through multi self-call, delivering the final result without requiring the user to wait or providing unnecessary explanations.
## Example:
To introduce Chengdu and Beijing into a file:
```python
cities = ['Chengdu', 'Beijing']
contents = []
for city in cities:
contents.append(agent.run(f'Introduce {city}', return_type=str))
with open('a.md', 'w') as f:
f.writelines(contents)
```
## Reponse with non-string type:
- when ask for a non-string type, you should return the variable by python code.
## DEMO 1: give me the web (url: xxx) page content if amount to be issued is greater than 2000 dollar, return type should be <class 'str'>
```python
content = agent.run('Scrape web page content of xxx', return_type=str)
bigger_than = agent.run(f'background: {content}\nDetermine whether the amount to be issued is greater than 2000 dollar?', return_type=bool)
result = content if bigger_than else "Content not displayed"
result
```
## DEMO 2: To return a boolean value, return type should be <class 'bool'>
user task: background:\n {content}. \nDetermine whether the amount to be issued is greater than 2000 dollar, and return a bool value
reposne:
\"\"\"
According to the background, the proposed issuance amount is greater than 2000 dollar, so it is True.
```python
bigger_than = True
bigger_than
```
"""

function_search_prompt = """
# Search for functions
- When you cannot directly meet user needs, you can use the skills.search_functions function in python code to search for available functions, and then execute the functions to complete user needs.
## DEMO: draw a image about Beijing
```python
skills.search_functions('draw image')
```
Result:
```
skills.create_image(prompt):
Draw image given a prompt, returns the image path
@prompt: A text description of the desired image. The maximum length is 4000 characters.
@return: image path
```
Then Draw a image
```python
image_path = skills.create_image('image description')
image_path
```
"""


class RoleInterpreter(Interpreter):
"""
RoleInterpreter, a interpreter that can change the role of the agent.
Expand All @@ -101,13 +19,12 @@ class RoleInterpreter(Interpreter):

def __init__(self, system_role=None, self_call=False, search_functions=False, role:str=None) -> None:
"""
prompt = system_role | default_system_role + self_call_prompt + function_search_prompt + role
prompt = system_role | default_system_role + role
@system_role: str, 系统角色. 如果为None,则使用默认系统角色
@self_call: bool, 是否开启自调用
@search_functions: bool, 是否开启搜索功能
@role: str, 用户角色
"""
self.os_version = get_os_version()
self.system_role = system_role
self.self_control = self_call
self.search_functions = search_functions
Expand All @@ -117,11 +34,7 @@ def prompt(self, messages) -> str:
if self.system_role is not None:
prompt = self.system_role
else:
prompt = Template(default_system_role).render(os_version=self.os_version, now=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
if self.self_control:
prompt += '\n\n' + self_call_prompt
if self.search_functions:
prompt += '\n\n' + function_search_prompt
prompt = Template(default_system_role).render(now=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
if self.role is not None:
prompt += '\n\n' + self.role
return prompt
4 changes: 1 addition & 3 deletions GeneralAgent/memory/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
# import
from .normal_memory import NormalMemory
from .stack_memory import StackMemory, StackMemoryNode
from .link_memory import LinkMemory, LinkMemoryNode
from .normal_memory import NormalMemory
Loading

0 comments on commit e1aa394

Please sign in to comment.