Skip to content

Commit

Permalink
Merge pull request #103 from polywrap/nerfzael-better-message-logs
Browse files Browse the repository at this point in the history
Better message logs
  • Loading branch information
nerfZael authored Aug 31, 2023
2 parents a9ec63d + e7f702b commit 287a4e3
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 119 deletions.
39 changes: 31 additions & 8 deletions apps/browser/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { faMarkdown } from '@fortawesome/free-brands-svg-icons';
import "./Chat.css";

export interface ChatMessage {
text: string;
title: string;
content?: string;
user: string;
color?: string;
}
Expand All @@ -29,6 +30,7 @@ const Chat: React.FC<ChatProps> = ({ evo, onMessage, messages, goalEnded }: Chat
undefined
);
const [stopped, setStopped] = useState<boolean>(false);
const [hoveredMsgIndex, setHoveredMsgIndex] = useState<number>(-1);

const pausedRef = useRef(paused);
useEffect(() => {
Expand All @@ -54,7 +56,7 @@ const Chat: React.FC<ChatProps> = ({ evo, onMessage, messages, goalEnded }: Chat

// Create a new iteration thread
if (!evoItr) {
const goal = messages.filter((msg) => msg.user === "user")[0].text;
const goal = messages.filter((msg) => msg.user === "user")[0].title;
setEvoItr(evo.run(goal));
return Promise.resolve();
}
Expand All @@ -71,9 +73,10 @@ const Chat: React.FC<ChatProps> = ({ evo, onMessage, messages, goalEnded }: Chat

const response = await evoItr.next();

if (response.value && response.value.message) {
if (!response.done && response.value.message) {
const evoMessage = {
text: response.value.message,
title: response.value.message.title,
content: response.value.message.content,
user: "evo"
};
messageLog = [...messageLog, evoMessage];
Expand All @@ -97,7 +100,7 @@ const Chat: React.FC<ChatProps> = ({ evo, onMessage, messages, goalEnded }: Chat

const handleSend = async () => {
onMessage({
text: message,
title: message,
user: "user"
});
setSending(true);
Expand Down Expand Up @@ -127,7 +130,7 @@ const Chat: React.FC<ChatProps> = ({ evo, onMessage, messages, goalEnded }: Chat
let exportedContent = '';
if (format === 'md') {
exportedContent = messages.map((msg) => {
return `# ${msg.user.toUpperCase()}\n${msg.text}\n---\n`;
return `# ${msg.user.toUpperCase()}\n${msg.title}\n${msg.content}\n---\n`;
}).join('\n');
}

Expand Down Expand Up @@ -155,8 +158,28 @@ const Chat: React.FC<ChatProps> = ({ evo, onMessage, messages, goalEnded }: Chat
{messages.map((msg, index) => (
<div key={index} className={`MessageContainer ${msg.user}`}>
<div className="SenderName">{msg.user.toUpperCase()}</div>
<div className={`Message ${msg.user}`} style={msg.color ? { borderColor: msg.color } : undefined}>
<ReactMarkdown>{msg.text}</ReactMarkdown>
<div
className={`Message ${msg.user}`}
style={msg.color ? { borderColor: msg.color, cursor: 'pointer' } : { cursor: 'pointer'}}
onMouseEnter={() => setHoveredMsgIndex(index)}
onMouseLeave={() => setHoveredMsgIndex(-1)}
>
<div>
{
hoveredMsgIndex === index
? (
<>
<div>{msg.title}</div>
<ReactMarkdown>{msg.content ?? ""}</ReactMarkdown>
</>
)
: (
<>
<div>{msg.title}</div>
</>
)
}
</div>
</div>
</div>
))}
Expand Down
2 changes: 1 addition & 1 deletion apps/browser/src/pages/Dojo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ function Dojo() {
onLog: (markdown: string, color?: string) => {
onMessage({
user: "evo",
text: markdown,
title: markdown,
color
});
}
Expand Down
9 changes: 8 additions & 1 deletion apps/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,15 @@ export async function cli(): Promise<void> {
while(true) {
const response = await iterator.next();

if (response.done) {
if (!response.value.ok) {
logger.error(response.value.error ?? "Unknown error");
}
break;
}

if (response.value) {
response.value.message && logger.info(response.value.message);
response.value.message && logger.info(response.value.message.title);
}
}
}
Expand Down
49 changes: 19 additions & 30 deletions packages/core/src/agents/agent-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import {
UNDEFINED_FUNCTION_ARGS,
UNDEFINED_FUNCTION_NAME,
UNPARSABLE_FUNCTION_ARGS,
FUNCTION_CALL_FAILED,
EXECUTE_SCRIPT_OUTPUT,
OTHER_EXECUTE_FUNCTION_OUTPUT,
READ_GLOBAL_VAR_OUTPUT
} from "./prompts";
import { Workspace, Logger } from "../sys";
import { Scripts } from "../Scripts";
Expand All @@ -25,55 +21,48 @@ export interface AgentContext {
logger: Logger;
}

export type AgentFunctionResult = Result<string, any>;

export interface AgentChatMessage {
type: "success" | "error" | "info" | "warning",
title: string,
content?: string,
}

export interface AgentFunction {
definition: any;
buildExecutor: (
buildChatMessage(args: any, result: AgentFunctionResult): AgentChatMessage;
buildExecutor(
context: AgentContext
) => (options: any) => Promise<Result<string, any>>;
): (options: any) => Promise<any>;
}

export type ExecuteAgentFunction = (
name: string | undefined,
args: string | undefined,
context: AgentContext,
agentFunctions: AgentFunction[],
) => Promise<Result<string, string>>;
) => Promise<Result<AgentChatMessage, string>>;

export const executeAgentFunction: ExecuteAgentFunction = async (
name: string | undefined,
args: string | undefined,
context: AgentContext,
agentFunctions: AgentFunction[],
): Promise<Result<string, string>> => {
const result = processFunctionAndArgs(name, args, agentFunctions);
): Promise<Result<AgentChatMessage, string>> => {
const parseResult = processFunctionAndArgs(name, args, agentFunctions);

if (!result.ok) {
return ResultErr(result.error);
if (!parseResult.ok) {
return ResultErr(parseResult.error);
}

const [fnArgs, func] = result.value;
const fnName = name as string;

const argsStr = JSON.stringify(fnArgs, null, 2);
let functionCallSummary = `# Function Call:\n\`\`\`javascript\n${fnName}(${argsStr})\n\`\`\`\n`;
const [fnArgs, func] = parseResult.value;

const executor = func.buildExecutor(context);

const response = await executor(fnArgs);

if (!response.ok) {
return ResultErr(FUNCTION_CALL_FAILED(fnName, JSON.stringify(response.error, null, 2), args));
}

if (fnName === "executeScript") {
functionCallSummary += EXECUTE_SCRIPT_OUTPUT(fnArgs.result, response.value);
} else if (fnName === "readVar") {
functionCallSummary += READ_GLOBAL_VAR_OUTPUT(fnArgs.name, response.value);
} else {
functionCallSummary += OTHER_EXECUTE_FUNCTION_OUTPUT(response.value);
}
const result = await executor(fnArgs);

return ResultOk(functionCallSummary);
return ResultOk(func.buildChatMessage(fnArgs, result));
}

function processFunctionAndArgs(
Expand Down
41 changes: 14 additions & 27 deletions packages/core/src/agents/agent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Result } from "@polywrap/result";
import { AgentChatMessage } from "./agent-function";

export abstract class Agent {
abstract run(
namespace: string,
Expand All @@ -6,23 +9,7 @@ export abstract class Agent {
): AsyncGenerator<StepOutput, RunResult, string | undefined>;
}

export class RunResult {
message?: string;
isError?: boolean;

constructor(message?: string, isError?: boolean) {
this.message = message;
this.isError = isError;
}

static ok(msg?: string): RunResult {
return new RunResult(msg);
}

static error(msg?: string): RunResult {
return new RunResult(msg, true);
}
}
export type RunResult = Result<undefined, string>;

export enum PromptType {
None,
Expand All @@ -31,23 +18,23 @@ export enum PromptType {
}

export class StepOutput {
message: string;
message: AgentChatMessage;
promptType: PromptType;

constructor(message: string, promptType?: PromptType) {
this.message = message;
this.promptType = promptType ?? PromptType.None;
constructor(message: AgentChatMessage, promptType?: PromptType) {
this.message = message;
this.promptType = promptType ?? PromptType.None;
}

static message(msg?: string): StepOutput {
return new StepOutput(msg ?? "");
static message(msg: AgentChatMessage): StepOutput {
return new StepOutput(msg);
}

static prompt(msg: string): StepOutput {
return new StepOutput(msg, PromptType.Prompt);
static prompt(msg: AgentChatMessage): StepOutput {
return new StepOutput(msg, PromptType.Prompt);
}

static question(msg: string): StepOutput {
return new StepOutput(msg, PromptType.Question);
static question(msg: AgentChatMessage): StepOutput {
return new StepOutput(msg, PromptType.Question);
}
}
3 changes: 2 additions & 1 deletion packages/core/src/agents/evo/Evo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { WrapClient } from "../../wrap";
import { Scripts } from "../../Scripts";
import { InMemoryWorkspace, Workspace, Logger } from "../../sys";
import { IWrapPackage } from "@polywrap/client-js";
import { ResultErr } from "@polywrap/result";

export class Evo implements Agent {
private client: WrapClient;
Expand Down Expand Up @@ -52,7 +53,7 @@ export class Evo implements Agent {
);
} catch (err) {
this.logger.error(err);
return RunResult.error("Unrecoverable error encountered.");
return ResultErr("Unrecoverable error encountered.");
}
}
}
39 changes: 32 additions & 7 deletions packages/core/src/agents/evo/agent-functions/createScript.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ScriptWriter } from "../../script-writer";
import { AgentContext, AgentFunction } from "../../agent-function";
import { Result, ResultErr, ResultOk } from "@polywrap/result";
import { AgentChatMessage, AgentContext, AgentFunction, AgentFunctionResult } from "../../agent-function";
import { FUNCTION_CALL_FAILED, OTHER_EXECUTE_FUNCTION_OUTPUT } from "../../prompts";
import { ResultErr, ResultOk } from "@polywrap/result";

const FN_NAME = "createScript";

export function createScript(createScriptWriter: () => ScriptWriter): AgentFunction {
return {
Expand All @@ -27,10 +30,25 @@ export function createScript(createScriptWriter: () => ScriptWriter): AgentFunct
additionalProperties: false
},
},
buildExecutor: (
context: AgentContext
) => {
return async (options: { namespace: string, description: string, arguments: string }): Promise<Result<string, any>> => {
buildChatMessage(args: any, result: AgentFunctionResult): AgentChatMessage {
const argsStr = JSON.stringify(args, null, 2);

return result.ok
? {
type: "success",
title: `Created '${args.namespace}' script.`,
content:
`# Function Call:\n\`\`\`javascript\n${FN_NAME}(${argsStr})\n\`\`\`\n` +
OTHER_EXECUTE_FUNCTION_OUTPUT(result.value),
}
: {
type: "error",
title: `Failed to create '${args.namespace}' script!`,
content: FUNCTION_CALL_FAILED(FN_NAME, result.error, args),
};
},
buildExecutor(context: AgentContext) {
return async (options: { namespace: string, description: string, arguments: string }): Promise<AgentFunctionResult> => {
if (options.namespace.startsWith("agent.")) {
return ResultErr(`Cannot create an script with namespace ${options.namespace}. Try searching for script in that namespace instead.`);
}
Expand All @@ -45,7 +63,14 @@ export function createScript(createScriptWriter: () => ScriptWriter): AgentFunct
while(true) {
const response = await iterator.next();

response.value.message && context.logger.info(response.value.message);
if (response.done) {
if (!response.value.ok) {
return ResultErr(response.value.error);
}
break;
}

response.value && context.logger.info(response.value.message.title);

// TODO: we should not be communicating the ScriptWriter's completion
// via a special file in the workspace
Expand Down
32 changes: 25 additions & 7 deletions packages/core/src/agents/evo/agent-functions/executeScript.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { AgentFunction, AgentContext } from "../../agent-function";
import { AgentFunction, AgentContext, AgentFunctionResult, AgentChatMessage } from "../../agent-function";
import {
JsEngine_GlobalVar,
JsEngine_Module,
shimCode
} from "../../../wrap";
import JSON5 from "json5";
import { Result, ResultErr, ResultOk } from "@polywrap/result";
import { EXECUTE_SCRIPT_OUTPUT, FUNCTION_CALL_FAILED } from "../../prompts";
import { ResultErr, ResultOk } from "@polywrap/result";

const FN_NAME = "executeScript";

export const executeScript: AgentFunction = {
definition: {
name: "executeScript",
name: FN_NAME,
description: `Execute an script.`,
parameters: {
type: "object",
Expand All @@ -31,10 +34,25 @@ export const executeScript: AgentFunction = {
additionalProperties: false
},
},
buildExecutor: (
context: AgentContext
) => {
return async (options: { namespace: string, arguments: any, result: string }): Promise<Result<string, any>> => {
buildChatMessage(args: any, result: AgentFunctionResult): AgentChatMessage {
const argsStr = JSON.stringify(args, null, 2);

return result.ok
? {
type: "success",
title: `Executed '${args.namespace}' script.`,
content:
`# Function Call:\n\`\`\`javascript\n${FN_NAME}(${argsStr})\n\`\`\`\n` +
EXECUTE_SCRIPT_OUTPUT(args.result, result.value),
}
: {
type: "error",
title: `'${args.namespace}' script failed to execute!`,
content: FUNCTION_CALL_FAILED(FN_NAME, result.error, args),
};
},
buildExecutor(context: AgentContext) {
return async (options: { namespace: string, arguments: any, result: string }): Promise<AgentFunctionResult> => {
try {
const script = context.scripts.getScriptByName(options.namespace);

Expand Down
Loading

0 comments on commit 287a4e3

Please sign in to comment.