When chatgpt messes up, explain to it why. Allows recovery from errors

main
Cameron Murphy Reikes 2 years ago
parent 27bed12418
commit 9285bc5683

@ -165,7 +165,7 @@ CharacterGen characters[] = {
"The player is currently holding a tripod\n" "The player is currently holding a tripod\n"
"Edeline: ACT_none \"That tripod will be the decisive factor in your victory\"\n" "Edeline: ACT_none \"That tripod will be the decisive factor in your victory\"\n"
"\n" "\n"
"The NPC you will be acting as is named Edeline. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her “unreliable” at times and her predictions can at times be either cryptic or broadly interpreted.", "The NPC you will be acting as is named Edeline. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her 'unreliable' at times and her predictions can at times be either cryptic or broadly interpreted.",
}, },
{ {
.name = "Death", .name = "Death",

@ -2739,19 +2739,17 @@ void frame(void)
// parse out from the sentence NPC action and dialog // parse out from the sentence NPC action and dialog
Perception out = { 0 }; Perception out = { 0 };
#ifdef DO_CHATGPT_PARSING
bool text_was_well_formatted = parse_chatgpt_response(it, sentence_str, &out);
#else
bool text_was_well_formatted = parse_ai_response(it, sentence_str, &out);
#endif
if (text_was_well_formatted) ChatgptParse parse_response = parse_chatgpt_response(it, sentence_str, &out);
if (parse_response.succeeded)
{ {
process_perception(it, out, player, &gs); process_perception(it, out, player, &gs);
} }
else else
{ {
it->perceptions_dirty = true; // on poorly formatted AI, just retry request. process_perception(it, (Perception){.type = ErrorMessage, .error = parse_response.error_message}, player, &gs);
it->perceptions_dirty = true; // on poorly formatted AI, just retry request. Explain to it why it's wrong. Adapt, improve, overcome. Time stops for nothing!
} }
EM_ASM( { EM_ASM( {

@ -70,6 +70,7 @@ Escaped escape_for_json(const char *s)
typedef enum PerceptionType typedef enum PerceptionType
{ {
Invalid, // so that zero value in training structs means end of perception Invalid, // so that zero value in training structs means end of perception
ErrorMessage, // when chatgpt gives a response that we can't parse, explain to it why and retry. Can also log parse errors for training
PlayerAction, PlayerAction,
PlayerDialog, PlayerDialog,
NPCDialog, // includes an npc action in every npc dialog. So it's often ACT_none NPCDialog, // includes an npc action in every npc dialog. So it's often ACT_none
@ -87,6 +88,9 @@ typedef struct Perception
ItemKind given_item; // valid in player action and enemy action when the kind is such that there is an item to be given ItemKind given_item; // valid in player action and enemy action when the kind is such that there is an item to be given
union union
{ {
// ErrorMessage
Sentence error;
// player action // player action
struct struct
{ {
@ -505,6 +509,10 @@ void process_perception(Entity *happened_to_npc, Perception p, Entity *player, G
assert(!actions[p.npc_action_type].takes_argument); assert(!actions[p.npc_action_type].takes_argument);
} }
} }
else if(p.type == ErrorMessage)
{
Log("Failed to parse chatgippity sentence because: '%s'\n", p.error.data);
}
else else
{ {
assert(false); assert(false);
@ -609,6 +617,25 @@ void dump_json_node(PromptBuff *into, MessageType type, const char *content)
dump_json_node_trailing(into, type, content, true); dump_json_node_trailing(into, type, content, true);
} }
// returns a string like `ITEM_one, ITEM_two`
Sentence item_string(Entity *e)
{
Sentence to_return = {0};
BUFF_ITER_I(ItemKind, &e->held_items, i)
{
printf_buff(&to_return, "ITEM_%s", items[*it].enum_name);
if (i == e->held_items.cur_index - 1)
{
printf_buff(&to_return, "");
}
else
{
printf_buff(&to_return, ", ");
}
}
return to_return;
}
// outputs json // outputs json
void generate_chatgpt_prompt(Entity *it, PromptBuff *into) void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
{ {
@ -652,7 +679,13 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
MessageType sent_type = 0; MessageType sent_type = 0;
typedef BUFF(char, 1024) DialogNode; typedef BUFF(char, 1024) DialogNode;
DialogNode cur_node = { 0 }; DialogNode cur_node = { 0 };
if (it->type == PlayerAction) if (it->type == ErrorMessage)
{
assert(it->error.cur_index > 0);
printf_buff(&cur_node, "ERROR, YOU SAID SOMETHING WRONG: The program can't parse what you said because: %s", it->error.data);
sent_type = MSG_USER;
}
else if (it->type == PlayerAction)
{ {
assert(it->player_action_type < ARRLEN(actions)); assert(it->player_action_type < ARRLEN(actions));
printf_buff(&cur_node, "Player: %s", percept_action_str(*it, it->player_action_type).data); printf_buff(&cur_node, "Player: %s", percept_action_str(*it, it->player_action_type).data);
@ -748,19 +781,7 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
if(e->held_items.cur_index > 0) if(e->held_items.cur_index > 0)
{ {
printf_buff(&latest_state_node, "\nThe NPC you're acting as, %s, has these items in their inventory: [", characters[it->npc_kind].name); printf_buff(&latest_state_node, "\nThe NPC you're acting as, %s, has these items in their inventory: [%s]", characters[it->npc_kind].name, item_string(it).data);
BUFF_ITER_I(ItemKind, &e->held_items, i)
{
printf_buff(&latest_state_node, "ITEM_%s", items[*it].enum_name);
if (i == e->held_items.cur_index - 1)
{
printf_buff(&latest_state_node, "]\n");
}
else
{
printf_buff(&latest_state_node, ", ");
}
}
} }
else else
{ {
@ -933,8 +954,16 @@ bool char_in_str(char c, const char *str)
return false; return false;
} }
bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out) typedef struct
{ {
bool succeeded;
Sentence error_message;
} ChatgptParse;
ChatgptParse parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out)
{
ChatgptParse to_return = {0};
*out = (Perception) { 0 }; *out = (Perception) { 0 };
out->type = NPCDialog; out->type = NPCDialog;
@ -957,17 +986,16 @@ bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out)
if (!found_action) if (!found_action)
{ {
Log("Could not find action associated with string `%s`\n", action_string.data); printf_buff(&to_return.error_message, "Could not find action associated with parsed 'ACT_' string `%s`\n", action_string.data);
out->npc_action_type = ACT_none; out->npc_action_type = ACT_none;
return to_return;
return false;
} }
else else
{ {
SmallTextChunk dialog_str = { 0 }; SmallTextChunk dialog_str = { 0 };
if (actions[out->npc_action_type].takes_argument) if (actions[out->npc_action_type].takes_argument)
{ {
#define EXPECT(chr, val) if (chr != val) { Log("Improperly formatted sentence_str `%s`, expected %c but got %c\n", sentence_str, val, chr); return false; } #define EXPECT(chr, val) if (chr != val) { printf_buff(&to_return.error_message, "Improperly formatted sentence, expected character '%c' but got '%c'\n", sentence_str, val, chr); return to_return; }
EXPECT(*sentence_str, '('); EXPECT(*sentence_str, '(');
sentence_str += 1; sentence_str += 1;
@ -1004,18 +1032,18 @@ bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out)
} }
if (!found) if (!found)
{ {
Log("Couldn't find item in inventory of NPC to give with item string %s\n", argument.data); printf_buff(&to_return.error_message, "Couldn't find item in the inventory of the NPC to give. You said `%s`, but you have [%s] in your inventory \n", argument.data, item_string(it).data);
return false; return to_return;
} }
} }
else else
{ {
Log("Don't know how to handle argument in action of type %s\n", actions[out->npc_action_type].name); printf_buff(&to_return.error_message, "Don't know how to handle argument in action of type `%s`\n", actions[out->npc_action_type].name);
#ifdef DEVTOOLS #ifdef DEVTOOLS
// not sure if this should never happen or not, need more sleep... // not sure if this should never happen or not, need more sleep...
assert(false); assert(false);
#endif #endif
return false; return to_return;
} }
EXPECT(*sentence_str, ')'); EXPECT(*sentence_str, ')');
@ -1029,17 +1057,18 @@ bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out)
sentence_str += get_until(&dialog_str, sentence_str, "\"\n"); sentence_str += get_until(&dialog_str, sentence_str, "\"\n");
if (dialog_str.cur_index >= ARRLEN(out->npc_dialog.data)) if (dialog_str.cur_index >= ARRLEN(out->npc_dialog.data))
{ {
Log("Dialog string `%s` too big to fit in sentence size %d\n", dialog_str.data, printf_buff(&to_return.error_message, "Dialog string `%s` too big to fit in sentence size %d\n", dialog_str.data,
(int) ARRLEN(out->npc_dialog.data)); (int) ARRLEN(out->npc_dialog.data));
return false; return to_return;
} }
memcpy(out->npc_dialog.data, dialog_str.data, dialog_str.cur_index); memcpy(out->npc_dialog.data, dialog_str.data, dialog_str.cur_index);
out->npc_dialog.cur_index = dialog_str.cur_index; out->npc_dialog.cur_index = dialog_str.cur_index;
return true; to_return.succeeded = true;
return to_return;
} }
return false; to_return.succeeded = false;
return to_return;
} }

Loading…
Cancel
Save