Skip to content

Commit

Permalink
Make ChatConversationalAgentOutputParser be more optimistic and parse…
Browse files Browse the repository at this point in the history
… JSON and then attempt to find JSON in text
  • Loading branch information
mikkoh committed May 10, 2023
1 parent 0048372 commit fece0e3
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 12 deletions.
62 changes: 50 additions & 12 deletions langchain/src/agents/chat_convo/outputParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,67 @@ import { FORMAT_INSTRUCTIONS } from "./prompt.js";

export class ChatConversationalAgentOutputParser extends AgentActionOutputParser {
async parse(text: string) {
let jsonOutput = text.trim();
if (jsonOutput.includes("```json")) {
jsonOutput = jsonOutput.split("```json")[1].trimStart();
} else if (jsonOutput.includes("```")) {
const firstIndex = jsonOutput.indexOf("```");
jsonOutput = jsonOutput.slice(firstIndex + 3).trimStart();
}
const lastIndex = jsonOutput.lastIndexOf("```");
if (lastIndex !== -1) {
jsonOutput = jsonOutput.slice(0, lastIndex).trimEnd();
const trimmedText = text.trim();
let action: string | undefined;
let action_input: string | undefined;

try {
({ action, action_input } = JSON.parse(trimmedText));
} catch (_error) {
({ action, action_input } = this.findActionAndInput(trimmedText));
}

const response = JSON.parse(jsonOutput);
if (!action) {
throw new Error(`\`action\` could not be found in: "${trimmedText}"`);
}

const { action, action_input } = response;
if (!action_input) {
throw new Error(
`\`action_input\` could not be found in: "${trimmedText}"`
);
}

if (action === "Final Answer") {
return { returnValues: { output: action_input }, log: text };
}

return { tool: action, toolInput: action_input, log: text };
}

getFormatInstructions(): string {
return FORMAT_INSTRUCTIONS;
}

private findActionAndInput(text: string): {
action: string | undefined;
action_input: string | undefined;
} {
const jsonOutput = this.findJson(text);

try {
const response = JSON.parse(jsonOutput);

return response;
} catch (error) {
return { action: undefined, action_input: undefined };
}
}

private findJson(text: string) {
let jsonOutput = text;

if (jsonOutput.includes("```json")) {
jsonOutput = jsonOutput.split("```json")[1].trimStart();
} else if (jsonOutput.includes("```")) {
const firstIndex = jsonOutput.indexOf("```");
jsonOutput = jsonOutput.slice(firstIndex + 3).trimStart();
}
const lastIndex = jsonOutput.lastIndexOf("```");

if (lastIndex !== -1) {
jsonOutput = jsonOutput.slice(0, lastIndex).trimEnd();
}

return jsonOutput;
}
}
71 changes: 71 additions & 0 deletions langchain/src/agents/tests/chat_output_parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ test("Can parse JSON with text in front of it", async () => {
toolInput:
"```sql\nSELECT * FROM orders\nJOIN users ON users.id = orders.user_id\nWHERE users.email = 'bud'```",
},
{
input:
'{"action":"ToolWithJson","action_input":"The tool input ```json\\n{\\"yes\\":true}\\n```"}',
output:
'{"action":"ToolWithJson","action_input":"The tool input ```json\\n{\\"yes\\":true}\\n```"}',
tool: "ToolWithJson",
toolInput: 'The tool input ```json\n{"yes":true}\n```',
},
];

const p = new ChatConversationalAgentOutputParser();
Expand All @@ -62,3 +70,66 @@ test("Can parse JSON with text in front of it", async () => {
}
}
});

test("will throw exceptions if action or action_input are not found", async () => {
const parser = new ChatConversationalAgentOutputParser();

type MissingItem = "action" | "action_input";
type TestCase = { message: string; missing: MissingItem };

const testCases: TestCase[] = [
{
message: "",
missing: "action",
},
{
message: '{"action": "Final Answer"}',
missing: "action_input",
},
{
message: '{"action_input": "I have no action"}',
missing: "action",
},
{
message:
'I have a prefix ```json\n{"action_input": "I have no action"}```',
missing: "action",
},
{
message: 'I have a prefix ```{"action_input": "I have no action"}```',
missing: "action",
},
{
message:
'I have a prefix ```json\n{"action_input": "I have no action"}\n```',
missing: "action",
},
{
message: 'I have a prefix ```\n{"action_input": "I have no action"}\n```',
missing: "action",
},

{
message: 'I have a prefix ```json\n{"action": "ToolThing"}```',
missing: "action_input",
},
{
message: 'I have a prefix ```{"action": "ToolThing"}```',
missing: "action_input",
},
{
message: 'I have a prefix ```json\n{"action": "ToolThing"}\n```',
missing: "action_input",
},
{
message: 'I have a prefix ```\n{"action": "ToolThing"}\n```',
missing: "action_input",
},
];

for (const { message, missing } of testCases) {
await expect(parser.parse(message)).rejects.toThrow(
`\`${missing}\` could not be found in: "${message}"`
);
}
});

0 comments on commit fece0e3

Please sign in to comment.