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
|
## 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.
|
- [ ] Have the agent pull its personality from the database as its hostname as the key.
|
||||||
- [ ] Log all commands and their outputs to the database.
|
- [ ] Log all commands and their outputs to the database.
|
||||||
- [ ] Use yaml for config.
|
- [ ] Use yaml for config.
|
||||||
- [ ] Add the user's name.
|
- [ ] 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
|
- [ ] Option to have the bot send the user a welcome message when they connect
|
||||||
- [ ] Streaming
|
- [ ] Streaming
|
||||||
- [ ] Add a Matrix bot.
|
- [ ] 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).
|
- [ ] 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.
|
- [ ] Have the agent run every `n` minutes to check Icinga2 and take action if necessary.
|
||||||
- [ ] Evaluate using langchain.
|
- [ ] Evaluate using langchain.
|
||||||
|
- [ ] Can langchain use a headless browser to interact with the web?
|
||||||
|
|
|
@ -19,7 +19,7 @@ function_description = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "end_my_response",
|
"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",
|
"name": "end_chat",
|
||||||
|
@ -34,6 +34,5 @@ function_description = [
|
||||||
},
|
},
|
||||||
"required": ["reasoning"]
|
"required": ["reasoning"]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,10 @@ import socket
|
||||||
import subprocess
|
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:
|
if special_instructions:
|
||||||
special_instructions = special_instructions + '\n'
|
special_instructions = special_instructions + '\n'
|
||||||
else:
|
else:
|
||||||
|
@ -11,20 +14,27 @@ def load_personality(name: str, personality: str, system: str, special_instructi
|
||||||
desktop_env = get_current_desktop()
|
desktop_env = get_current_desktop()
|
||||||
if len(desktop_env):
|
if len(desktop_env):
|
||||||
desktop_env_str = f'The desktop environment is {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:
|
else:
|
||||||
desktop_env_str = 'The system does not have a desktop environment.'
|
desktop_env_str = 'The system does not have a desktop environment.'
|
||||||
desktop_env_bg_str = ''
|
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 {
|
return {
|
||||||
'role': 'system',
|
'role': 'system',
|
||||||
'content': f"""PERSONALITY:
|
'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 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.
|
The user is {name}'s owner.
|
||||||
|
|
||||||
SYSTEM INFO:
|
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()}`
|
The output of `uname -a` is `{get_uname_info()}`
|
||||||
{desktop_env_str}
|
{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)
|
client = OpenAI(api_key=OPENAI_KEY)
|
||||||
|
|
||||||
|
# TODO: pull config from database
|
||||||
temp_name = 'Sakura'
|
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]
|
context: list[dict[str, str]] = [character_card]
|
||||||
|
|
||||||
|
@ -53,17 +55,18 @@ def main():
|
||||||
temp_context.append(
|
temp_context.append(
|
||||||
{
|
{
|
||||||
'role': 'system',
|
'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(
|
response = client.chat.completions.create(
|
||||||
model="gpt-4-1106-preview", # TODO: config
|
model="gpt-4-1106-preview", # TODO: config
|
||||||
messages=temp_context,
|
messages=temp_context,
|
||||||
functions=function_description,
|
functions=function_description,
|
||||||
temperature=0.7
|
temperature=0.7 # TODO: config
|
||||||
)
|
)
|
||||||
function_call = response.choices[0].message.function_call
|
function_call = response.choices[0].message.function_call
|
||||||
|
|
||||||
if function_call:
|
if function_call:
|
||||||
function_name = function_call.name
|
function_name = function_call.name
|
||||||
function_arguments = function_call.arguments
|
function_arguments = function_call.arguments
|
||||||
|
@ -72,14 +75,16 @@ def main():
|
||||||
context.append({'role': 'function', 'name': function_name, 'content': ''})
|
context.append({'role': 'function', 'name': function_name, 'content': ''})
|
||||||
break
|
break
|
||||||
elif function_name == 'end_chat':
|
elif function_name == 'end_chat':
|
||||||
# TODO: add a config arg to control whether or not the AI is allowed to do this.
|
# TODO: add a config option to control whether or not the agent is allowed to do this.
|
||||||
print(colored('The AI has terminated the connection.', 'red', attrs=['bold']))
|
print(colored('The agent has terminated the connection.', 'red', attrs=['bold']))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(colored(f'{function_name}("{json.dumps(json.loads(function_arguments), indent=2)}")' + '\n', 'yellow'))
|
print(colored(f'{function_name}("{json.dumps(json.loads(function_arguments), indent=2)}")' + '\n', 'yellow'))
|
||||||
|
|
||||||
if function_name != 'run_bash':
|
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:
|
else:
|
||||||
command_output = func_run_bash(function_arguments)
|
command_output = func_run_bash(function_arguments)
|
||||||
result_to_ai = {
|
result_to_ai = {
|
||||||
|
@ -90,17 +95,25 @@ def main():
|
||||||
'return_code': command_output[2]
|
'return_code': command_output[2]
|
||||||
}
|
}
|
||||||
context.append({'role': 'function', 'name': function_name, 'content': json.dumps(result_to_ai)})
|
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.
|
# Restart the loop to let the agent decide what to do next.
|
||||||
else:
|
else:
|
||||||
response_text = response.choices[0].message.content
|
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)
|
response_text = re.sub(r'\n*end_my_response', '', response_text)
|
||||||
|
|
||||||
context.append({'role': 'assistant', 'content': 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')
|
lines = response_text.split('\n')
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print(colored(line, 'blue'))
|
print(colored(line, 'blue'))
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if end_my_response:
|
if end_my_response:
|
||||||
break
|
break
|
||||||
i += 1
|
i += 1
|
||||||
|
|
Reference in New Issue