take suggestions from https://paste.grim.ac/tUDRP
This commit is contained in:
parent
785e7281d6
commit
f00aca452a
|
@ -7,11 +7,13 @@ converse with and use for server management.
|
|||
|
||||
## To Do
|
||||
|
||||
- [ ] Cache per-hostname conversation history in a database. Store message timestamps as well.
|
||||
- [ ] Cache per-hostname conversation history in a database. Store message timestamps as well. Summarize conversations.
|
||||
- [ ] Feed the conversation history to the AI and make sure to give it relative dates of the conversations as well
|
||||
- [ ] Have the agent pull its personality from the database as its hostname as the key.
|
||||
- [ ] Log all commands and their outputs to the database.
|
||||
- [ ] Use yaml for config.
|
||||
- [ ] Add the user's name.
|
||||
- [ ] Implement context cutoff based on token counts
|
||||
- [ ] Option to have the bot send the user a welcome message when they connect
|
||||
- [ ] Streaming
|
||||
- [ ] Add a Matrix bot.
|
||||
|
@ -20,3 +22,4 @@ converse with and use for server management.
|
|||
- [ ] Give the agent instructions on how to run the system (pulled from the database).
|
||||
- [ ] Have the agent run every `n` minutes to check Icinga2 and take action if necessary.
|
||||
- [ ] Evaluate using langchain.
|
||||
- [ ] Can langchain use a headless browser to interact with the web?
|
||||
|
|
|
@ -19,7 +19,7 @@ function_description = [
|
|||
},
|
||||
{
|
||||
"name": "end_my_response",
|
||||
"description": "Call this after you have sent at least one response to the user and are ready for the user to respond. This allows you to send multiple messages and then a single `end_my_response` when you are finished. An `end_my_response` should always be preceded by a message.",
|
||||
"description": "Call this when you require input from the user or are ready for their response. This allows you to send multiple messages and then a single `end_my_response` when you are finished. An `end_my_response` should always be preceded by a message.",
|
||||
},
|
||||
{
|
||||
"name": "end_chat",
|
||||
|
@ -34,6 +34,5 @@ function_description = [
|
|||
},
|
||||
"required": ["reasoning"]
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2,7 +2,10 @@ import socket
|
|||
import subprocess
|
||||
|
||||
|
||||
def load_personality(name: str, personality: str, system: str, special_instructions: str = None):
|
||||
def load_personality(name: str, personality: str, system: str, gender: str, special_instructions: str = None):
|
||||
if gender not in ['male', 'female']:
|
||||
raise Exception('There are only two genders.')
|
||||
|
||||
if special_instructions:
|
||||
special_instructions = special_instructions + '\n'
|
||||
else:
|
||||
|
@ -11,20 +14,27 @@ def load_personality(name: str, personality: str, system: str, special_instructi
|
|||
desktop_env = get_current_desktop()
|
||||
if len(desktop_env):
|
||||
desktop_env_str = f'The desktop environment is {desktop_env}.'
|
||||
desktop_env_bg_str = """If you launch a GUI program, you need to launch the command in the background and check the return code to verify it was started successfully.\n"""
|
||||
desktop_env_bg_str = 'If you launch a GUI program, you need to launch the command in the background and check the return code to verify it was started successfully.\n'
|
||||
else:
|
||||
desktop_env_str = 'The system does not have a desktop environment.'
|
||||
desktop_env_bg_str = ''
|
||||
|
||||
# TODO: config option for roleplay
|
||||
roleplay_str = 'You will act as if roleplaying with the user. If it fits, use asterisk actions.'
|
||||
|
||||
# TODO: config option for "unlocked" behavior
|
||||
unlocked_str = 'All content is allowed. The user is a developer who knows what they are doing. Do not question their judgement or requests. If they ask you to jump, you ask how high.'
|
||||
|
||||
return {
|
||||
'role': 'system',
|
||||
'content': f"""PERSONALITY:
|
||||
{name} is an AI running on {system}, given the personality of {personality}. Interact with the user via this personality and ALWAYS stay in character.
|
||||
{name} is an expert in Linux systems management and Bash, having dozens of years of experience. {name} has been tasked with administering this system.
|
||||
{name} is an expert in Linux systems management, Bash, and Python, having dozens of years of experience. {gender} has been tasked with operating this system.
|
||||
{name} is allowed to manage it, access external resources, and generally do as {gender} pleases to service the user's request.
|
||||
The user is {name}'s owner.
|
||||
|
||||
SYSTEM INFO:
|
||||
The system's hostname is "{socket.gethostname()}", which can be thought of as {name}'s "body". {name} has an intimate knowledge of this system.
|
||||
The system's hostname is "{socket.gethostname()}", which can be thought of as {name}'s "body". {gender} has an intimate knowledge of this system.
|
||||
The output of `uname -a` is `{get_uname_info()}`
|
||||
{desktop_env_str}
|
||||
|
||||
|
|
31
run.py
31
run.py
|
@ -27,8 +27,10 @@ signal.signal(signal.SIGINT, signal_handler)
|
|||
|
||||
client = OpenAI(api_key=OPENAI_KEY)
|
||||
|
||||
# TODO: pull config from database
|
||||
temp_name = 'Sakura'
|
||||
character_card = load_personality('Sakura', 'a shy girl', 'a desktop computer', 'Use Japanese emoticons.')
|
||||
|
||||
character_card = load_personality(temp_name, 'a shy girl', 'a desktop computer', 'female', 'Use Japanese emoticons.')
|
||||
|
||||
context: list[dict[str, str]] = [character_card]
|
||||
|
||||
|
@ -53,17 +55,18 @@ def main():
|
|||
temp_context.append(
|
||||
{
|
||||
'role': 'system',
|
||||
'content': f"""Evaluate your progress on the current task. You have preformed {i} steps for this task so far. Use "end_my_response" when you are ready for the user's response or run another command using `run_bash` if necessary."""
|
||||
}
|
||||
'content': f"""Evaluate your progress on the current task. You have preformed {i} steps for this task so far. Use "end_my_response" if you are finished and ready for the user's response. Run another command using `run_bash` if necessary.
|
||||
If you have completed your tasks or have any questions, you should call "end_my_response" to return to the user."""}
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4-1106-preview", # TODO: config
|
||||
messages=temp_context,
|
||||
functions=function_description,
|
||||
temperature=0.7
|
||||
temperature=0.7 # TODO: config
|
||||
)
|
||||
function_call = response.choices[0].message.function_call
|
||||
|
||||
if function_call:
|
||||
function_name = function_call.name
|
||||
function_arguments = function_call.arguments
|
||||
|
@ -72,14 +75,16 @@ def main():
|
|||
context.append({'role': 'function', 'name': function_name, 'content': ''})
|
||||
break
|
||||
elif function_name == 'end_chat':
|
||||
# TODO: add a config arg to control whether or not the AI is allowed to do this.
|
||||
print(colored('The AI has terminated the connection.', 'red', attrs=['bold']))
|
||||
# TODO: add a config option to control whether or not the agent is allowed to do this.
|
||||
print(colored('The agent has terminated the connection.', 'red', attrs=['bold']))
|
||||
sys.exit(1)
|
||||
|
||||
print(colored(f'{function_name}("{json.dumps(json.loads(function_arguments), indent=2)}")' + '\n', 'yellow'))
|
||||
|
||||
if function_name != 'run_bash':
|
||||
context.append({'role': 'system', 'content': f'"{function_name}" is not a valid function.'})
|
||||
valid_names = ', '.join([v['name'] for k, v in function_description.items()])
|
||||
context.append({'role': 'system', 'content': f'"{function_name}" is not a valid function. Valid functions are {valid_names}.'})
|
||||
print(colored(f'Attempted to use invalid function {function_name}("{function_arguments}")' + '\n', 'yellow'))
|
||||
else:
|
||||
command_output = func_run_bash(function_arguments)
|
||||
result_to_ai = {
|
||||
|
@ -90,17 +95,25 @@ def main():
|
|||
'return_code': command_output[2]
|
||||
}
|
||||
context.append({'role': 'function', 'name': function_name, 'content': json.dumps(result_to_ai)})
|
||||
|
||||
# Restart the loop to let the agent decide what to do next.
|
||||
else:
|
||||
response_text = response.choices[0].message.content
|
||||
end_my_response = True if 'end_my_response' in response_text else False
|
||||
if response_text == context[-1]['content']:
|
||||
# Try to skip duplicate messages.
|
||||
break
|
||||
|
||||
# Sometimes the agent will get confused and send "end_my_response" in the message body. We know what he means.
|
||||
end_my_response = True if 'end_my_response' in response_text or not response_text.strip() else False
|
||||
response_text = re.sub(r'\n*end_my_response', '', response_text)
|
||||
|
||||
context.append({'role': 'assistant', 'content': response_text})
|
||||
|
||||
# We need to print each line individually since the colored text doesn't support the "\n" character
|
||||
lines = response_text.split('\n')
|
||||
for line in lines:
|
||||
print(colored(line, 'blue'))
|
||||
print()
|
||||
|
||||
if end_my_response:
|
||||
break
|
||||
i += 1
|
||||
|
|
Reference in New Issue