style: Action and Observation use schema for unifying (#494)

* style: Action and Observation use schema for unifying

* merge from upstream/main

* merge from upstream/main
This commit is contained in:
Leo
2024-04-07 01:46:07 +08:00
committed by GitHub
parent 229fa988c5
commit 3313e473ea
26 changed files with 202 additions and 122 deletions

View File

@@ -2,6 +2,7 @@ from typing import List
from opendevin.agent import Agent
from opendevin.state import State
from opendevin.llm.llm import LLM
from opendevin.schema import ActionType, ObservationType
from opendevin.action import (
Action,
@@ -98,7 +99,7 @@ class MonologueAgent(Agent):
def _add_event(self, event: dict):
"""
Adds a new event to the agent's monologue and memory.
Adds a new event to the agent's monologue and memory.
Monologue automatically condenses when it gets too large.
Parameters:
@@ -107,8 +108,14 @@ class MonologueAgent(Agent):
if "extras" in event and "screenshot" in event["extras"]:
del event["extras"]["screenshot"]
if 'args' in event and 'output' in event['args'] and len(event['args']['output']) > MAX_OUTPUT_LENGTH:
event['args']['output'] = event['args']['output'][:MAX_OUTPUT_LENGTH] + "..."
if (
"args" in event
and "output" in event["args"]
and len(event["args"]["output"]) > MAX_OUTPUT_LENGTH
):
event["args"]["output"] = (
event["args"]["output"][:MAX_OUTPUT_LENGTH] + "..."
)
self.monologue.add_event(event)
self.memory.add_event(event)
@@ -121,7 +128,7 @@ class MonologueAgent(Agent):
Short circuted to return when already initialized.
Parameters:
- task (str): The initial goal statement provided by the user
- task (str): The initial goal statement provided by the user
Raises:
- ValueError: If task is not provided
@@ -140,14 +147,18 @@ class MonologueAgent(Agent):
thought = thought.replace("$TASK", task)
if output_type != "":
observation: Observation = NullObservation(content="")
if output_type == "run":
observation = CmdOutputObservation(content=thought, command_id=0, command="")
elif output_type == "read":
if output_type == ObservationType.RUN:
observation = CmdOutputObservation(
content=thought, command_id=0, command=""
)
elif output_type == ObservationType.READ:
observation = FileReadObservation(content=thought, path="")
elif output_type == "recall":
elif output_type == ObservationType.RECALL:
observation = AgentRecallObservation(content=thought, memories=[])
elif output_type == "browse":
observation = BrowserOutputObservation(content=thought, url="", screenshot="")
elif output_type == ObservationType.BROWSE:
observation = BrowserOutputObservation(
content=thought, url="", screenshot=""
)
self._add_event(observation.to_dict())
output_type = ""
else:
@@ -155,7 +166,7 @@ class MonologueAgent(Agent):
if thought.startswith("RUN"):
command = thought.split("RUN ")[1]
action = CmdRunAction(command)
output_type = "run"
output_type = ActionType.RUN
elif thought.startswith("WRITE"):
parts = thought.split("WRITE ")[1].split(" > ")
path = parts[1]
@@ -164,15 +175,15 @@ class MonologueAgent(Agent):
elif thought.startswith("READ"):
path = thought.split("READ ")[1]
action = FileReadAction(path=path)
output_type = "read"
output_type = ActionType.READ
elif thought.startswith("RECALL"):
query = thought.split("RECALL ")[1]
action = AgentRecallAction(query=query)
output_type = "recall"
output_type = ActionType.RECALL
elif thought.startswith("BROWSE"):
url = thought.split("BROWSE ")[1]
action = BrowseURLAction(url=url)
output_type = "browse"
output_type = ActionType.BROWSE
else:
action = AgentThinkAction(thought=thought)
self._add_event(action.to_dict())
@@ -200,9 +211,9 @@ class MonologueAgent(Agent):
self.monologue.get_thoughts(),
state.background_commands_obs,
)
messages = [{"content": prompt,"role": "user"}]
messages = [{"content": prompt, "role": "user"}]
resp = self.llm.completion(messages=messages)
action_resp = resp['choices'][0]['message']['content']
action_resp = resp["choices"][0]["message"]["content"]
action = prompts.parse_action_response(action_resp)
self.latest_action = action
return action
@@ -211,7 +222,7 @@ class MonologueAgent(Agent):
"""
Uses VectorIndexRetriever to find related memories within the long term memory.
Uses search to produce top 10 results.
Parameters:
- query (str): The query that we want to find related memories for
@@ -219,4 +230,3 @@ class MonologueAgent(Agent):
- List[str]: A list of top 10 text results that matched the query
"""
return self.memory.search(query)

View File

@@ -5,6 +5,7 @@ from opendevin.controller.agent_controller import print_with_color
from opendevin.plan import Plan
from opendevin.action import Action, action_from_dict
from opendevin.observation import Observation
from opendevin.schema import ActionType
from opendevin.action import (
NullAction,
@@ -26,17 +27,17 @@ from opendevin.observation import (
)
ACTION_TYPE_TO_CLASS: Dict[str, Type[Action]] = {
"run": CmdRunAction,
"kill": CmdKillAction,
"browse": BrowseURLAction,
"read": FileReadAction,
"write": FileWriteAction,
"recall": AgentRecallAction,
"think": AgentThinkAction,
"summarize": AgentSummarizeAction,
"finish": AgentFinishAction,
"add_task": AddTaskAction,
"modify_task": ModifyTaskAction,
ActionType.RUN: CmdRunAction,
ActionType.KILL: CmdKillAction,
ActionType.BROWSE: BrowseURLAction,
ActionType.READ: FileReadAction,
ActionType.WRITE: FileWriteAction,
ActionType.RECALL: AgentRecallAction,
ActionType.THINK: AgentThinkAction,
ActionType.SUMMARIZE: AgentSummarizeAction,
ActionType.FINISH: AgentFinishAction,
ActionType.ADD_TASK: AddTaskAction,
ActionType.MODIFY_TASK: ModifyTaskAction,
}
HISTORY_SIZE = 10
@@ -129,9 +130,10 @@ What is your next thought or action? Again, you must reply with JSON, and only w
%(hint)s
"""
def get_prompt(plan: Plan, history: List[Tuple[Action, Observation]]) -> str:
"""
Gets the prompt for the planner agent.
Gets the prompt for the planner agent.
Formatted with the most recent action-observation pairs, current task, and hint based on last action
Parameters:
@@ -152,7 +154,10 @@ def get_prompt(plan: Plan, history: List[Tuple[Action, Observation]]) -> str:
latest_action = action
if not isinstance(observation, NullObservation):
observation_dict = observation.to_dict()
if "extras" in observation_dict and "screenshot" in observation_dict["extras"]:
if (
"extras" in observation_dict
and "screenshot" in observation_dict["extras"]
):
del observation_dict["extras"]["screenshot"]
history_dicts.append(observation_dict)
history_str = json.dumps(history_dicts, indent=2)
@@ -167,41 +172,42 @@ def get_prompt(plan: Plan, history: List[Tuple[Action, Observation]]) -> str:
plan_status = "You're not currently working on any tasks. Your next action MUST be to mark a task as in_progress."
hint = plan_status
latest_action_id = latest_action.to_dict()['action']
latest_action_id = latest_action.to_dict()["action"]
if current_task is not None:
if latest_action_id == "":
hint = "You haven't taken any actions yet. Start by using `ls` to check out what files you're working with."
elif latest_action_id == "run":
elif latest_action_id == ActionType.RUN:
hint = "You should think about the command you just ran, what output it gave, and how that affects your plan."
elif latest_action_id == "read":
elif latest_action_id == ActionType.READ:
hint = "You should think about the file you just read, what you learned from it, and how that affects your plan."
elif latest_action_id == "write":
elif latest_action_id == ActionType.WRITE:
hint = "You just changed a file. You should think about how it affects your plan."
elif latest_action_id == "browse":
elif latest_action_id == ActionType.BROWSE:
hint = "You should think about the page you just visited, and what you learned from it."
elif latest_action_id == "think":
elif latest_action_id == ActionType.THINK:
hint = "Look at your last thought in the history above. What does it suggest? Don't think anymore--take action."
elif latest_action_id == "recall":
elif latest_action_id == ActionType.RECALL:
hint = "You should think about the information you just recalled, and how it should affect your plan."
elif latest_action_id == "add_task":
elif latest_action_id == ActionType.ADD_TASK:
hint = "You should think about the next action to take."
elif latest_action_id == "modify_task":
elif latest_action_id == ActionType.MODIFY_TASK:
hint = "You should think about the next action to take."
elif latest_action_id == "summarize":
elif latest_action_id == ActionType.SUMMARIZE:
hint = ""
elif latest_action_id == "finish":
elif latest_action_id == ActionType.FINISH:
hint = ""
print_with_color("HINT:\n" + hint, "INFO")
return prompt % {
'task': plan.main_goal,
'plan': plan_str,
'history': history_str,
'hint': hint,
'plan_status': plan_status,
"task": plan.main_goal,
"plan": plan_str,
"history": history_str,
"hint": hint,
"plan_status": plan_status,
}
def parse_response(response: str) -> Action:
"""
Parses the model output to find a valid action to take
@@ -216,9 +222,8 @@ def parse_response(response: str) -> Action:
json_end = response.rfind("}") + 1
response = response[json_start:json_end]
action_dict = json.loads(response)
if 'contents' in action_dict:
if "contents" in action_dict:
# The LLM gets confused here. Might as well be robust
action_dict['content'] = action_dict.pop('contents')
action_dict["content"] = action_dict.pop("contents")
action = action_from_dict(action_dict)
return action

View File

@@ -3,6 +3,8 @@ import { IDisposable, Terminal as XtermTerminal } from "@xterm/xterm";
import "@xterm/xterm/css/xterm.css";
import { useSelector } from "react-redux";
import { FitAddon } from "xterm-addon-fit";
import ActionType from "../types/ActionType";
import ObservationType from "../types/ObservationType";
import Socket from "../services/socket";
import { RootState } from "../store";
@@ -22,10 +24,10 @@ class JsonWebsocketAddon {
);
Socket.addEventListener("message", (event) => {
const { action, args, observation, content } = JSON.parse(event.data);
if (action === "run") {
if (action === ActionType.RUN) {
terminal.writeln(args.command);
}
if (observation === "run") {
if (observation === ObservationType.RUN) {
content.split("\n").forEach((line: string) => {
terminal.writeln(line);
});

View File

@@ -7,11 +7,12 @@ import store from "../store";
import { ActionMessage } from "../types/Message";
import { SocketMessage } from "../types/ResponseType";
import { handleObservationMessage } from "./observations";
import ActionType from "../types/ActionType";
let isInitialized = false;
const messageActions = {
initialize: () => {
[ActionType.INIT]: () => {
store.dispatch(setInitialized(true));
if (isInitialized) {
return;
@@ -23,23 +24,23 @@ const messageActions = {
);
isInitialized = true;
},
browse: (message: ActionMessage) => {
[ActionType.BROWSE]: (message: ActionMessage) => {
const { url, screenshotSrc } = message.args;
store.dispatch(setUrl(url));
store.dispatch(setScreenshotSrc(screenshotSrc));
},
write: (message: ActionMessage) => {
[ActionType.WRITE]: (message: ActionMessage) => {
const { path, content } = message.args;
store.dispatch(updatePath(path));
store.dispatch(setCode(content));
},
think: (message: ActionMessage) => {
[ActionType.THINK]: (message: ActionMessage) => {
store.dispatch(appendAssistantMessage(message.args.thought));
},
finish: (message: ActionMessage) => {
[ActionType.FINISH]: (message: ActionMessage) => {
store.dispatch(appendAssistantMessage(message.message));
},
run: (message: ActionMessage) => {
[ActionType.RUN]: (message: ActionMessage) => {
store.dispatch(appendInput(message.args.command));
},
};

View File

@@ -9,12 +9,13 @@ import {
} from "../state/chatSlice";
import Socket from "./socket";
import store from "../store";
import ActionType from "../types/ActionType";
import { SocketMessage } from "../types/ResponseType";
import { ActionMessage } from "../types/Message";
export function sendChatMessage(message: string): void {
store.dispatch(appendUserMessage(message));
const event = { action: "start", args: { task: message } };
const event = { action: ActionType.START, args: { task: message } };
const eventString = JSON.stringify(event);
Socket.send(eventString);
}

View File

@@ -1,6 +1,7 @@
import { appendAssistantMessage } from "../state/chatSlice";
import { setInitialized } from "../state/taskSlice";
import store from "../store";
import ActionType from "../types/ActionType";
import Socket from "./socket";
import {
setAgent,
@@ -61,7 +62,7 @@ export function saveSettings(
value,
]),
);
const event = { action: "initialize", args: socketSettings };
const event = { action: ActionType.INIT, args: socketSettings };
const eventString = JSON.stringify(event);
store.dispatch(setInitialized(false));
Socket.send(eventString);

View File

@@ -2,7 +2,13 @@ from .base import Action, NullAction
from .bash import CmdRunAction, CmdKillAction
from .browse import BrowseURLAction
from .fileop import FileReadAction, FileWriteAction
from .agent import AgentRecallAction, AgentThinkAction, AgentFinishAction, AgentEchoAction, AgentSummarizeAction
from .agent import (
AgentRecallAction,
AgentThinkAction,
AgentFinishAction,
AgentEchoAction,
AgentSummarizeAction,
)
from .tasks import AddTaskAction, ModifyTaskAction
actions = (
@@ -18,7 +24,8 @@ actions = (
ModifyTaskAction,
)
ACTION_TYPE_TO_CLASS = {action_class.action:action_class for action_class in actions} # type: ignore[attr-defined]
ACTION_TYPE_TO_CLASS = {action_class.action: action_class for action_class in actions} # type: ignore[attr-defined]
def action_from_dict(action: dict) -> Action:
action = action.copy()
@@ -26,10 +33,13 @@ def action_from_dict(action: dict) -> Action:
raise KeyError(f"'action' key is not found in {action=}")
action_class = ACTION_TYPE_TO_CLASS.get(action["action"])
if action_class is None:
raise KeyError(f"'{action['action']=}' is not defined. Available actions: {ACTION_TYPE_TO_CLASS.keys()}")
raise KeyError(
f"'{action['action']=}' is not defined. Available actions: {ACTION_TYPE_TO_CLASS.keys()}"
)
args = action.get("args", {})
return action_class(**args)
__all__ = [
"Action",
"NullAction",
@@ -44,5 +54,5 @@ __all__ = [
"AgentEchoAction",
"AgentSummarizeAction",
"AddTaskAction",
"ModifyTaskAction"
"ModifyTaskAction",
]

View File

@@ -1,8 +1,14 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING
from opendevin.observation import AgentRecallObservation, AgentMessageObservation, Observation
from opendevin.observation import (
AgentRecallObservation,
AgentMessageObservation,
Observation,
)
from opendevin.schema import ActionType
from .base import ExecutableAction, NotExecutableAction
if TYPE_CHECKING:
from opendevin.controller import AgentController
@@ -10,22 +16,23 @@ if TYPE_CHECKING:
@dataclass
class AgentRecallAction(ExecutableAction):
query: str
action: str = "recall"
action: str = ActionType.RECALL
def run(self, controller: "AgentController") -> AgentRecallObservation:
return AgentRecallObservation(
content="Recalling memories...",
memories=controller.agent.search_memory(self.query)
memories=controller.agent.search_memory(self.query),
)
@property
def message(self) -> str:
return f"Let me dive into my memories to find what you're looking for! Searching for: '{self.query}'. This might take a moment."
@dataclass
class AgentThinkAction(NotExecutableAction):
thought: str
action: str = "think"
action: str = ActionType.THINK
def run(self, controller: "AgentController") -> "Observation":
raise NotImplementedError
@@ -34,6 +41,7 @@ class AgentThinkAction(NotExecutableAction):
def message(self) -> str:
return self.thought
@dataclass
class AgentEchoAction(ExecutableAction):
content: str
@@ -46,19 +54,21 @@ class AgentEchoAction(ExecutableAction):
def message(self) -> str:
return self.content
@dataclass
class AgentSummarizeAction(NotExecutableAction):
summary: str
action: str = "summarize"
action: str = ActionType.SUMMARIZE
@property
def message(self) -> str:
return self.summary
@dataclass
class AgentFinishAction(NotExecutableAction):
action: str = "finish"
action: str = ActionType.FINISH
def run(self, controller: "AgentController") -> "Observation":
raise NotImplementedError

View File

@@ -1,10 +1,12 @@
from dataclasses import dataclass, asdict
from typing import TYPE_CHECKING
from opendevin.schema import ActionType
if TYPE_CHECKING:
from opendevin.controller import AgentController
from opendevin.observation import Observation
@dataclass
class Action:
def run(self, controller: "AgentController") -> "Observation":
@@ -13,10 +15,10 @@ class Action:
def to_dict(self):
d = asdict(self)
try:
v = d.pop('action')
v = d.pop("action")
except KeyError:
raise NotImplementedError(f'{self=} does not have action attribute set')
return {'action': v, "args": d, "message": self.message}
raise NotImplementedError(f"{self=} does not have action attribute set")
return {"action": v, "args": d, "message": self.message}
@property
def executable(self) -> bool:
@@ -26,6 +28,7 @@ class Action:
def message(self) -> str:
raise NotImplementedError
@dataclass
class ExecutableAction(Action):
@property
@@ -39,12 +42,14 @@ class NotExecutableAction(Action):
def executable(self) -> bool:
return False
@dataclass
class NullAction(NotExecutableAction):
"""An action that does nothing.
This is used when the agent need to receive user follow-up messages from the frontend.
"""
action: str = "null"
action: str = ActionType.NULL
@property
def message(self) -> str:

View File

@@ -2,6 +2,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING
from .base import ExecutableAction
from opendevin.schema import ActionType
if TYPE_CHECKING:
from opendevin.controller import AgentController
@@ -12,7 +13,7 @@ if TYPE_CHECKING:
class CmdRunAction(ExecutableAction):
command: str
background: bool = False
action: str = "run"
action: str = ActionType.RUN
def run(self, controller: "AgentController") -> "CmdOutputObservation":
return controller.command_manager.run_command(self.command, self.background)
@@ -21,14 +22,15 @@ class CmdRunAction(ExecutableAction):
def message(self) -> str:
return f"Running command: {self.command}"
@dataclass
class CmdKillAction(ExecutableAction):
id: int
action: str = "kill"
action: str = ActionType.KILL
def run(self, controller: "AgentController") -> "CmdOutputObservation":
return controller.command_manager.kill_command(self.id)
@property
def message(self) -> str:
return f"Killing command: {self.id}"
return f"Killing command: {self.id}"

View File

@@ -1,6 +1,7 @@
import base64
from dataclasses import dataclass
from opendevin.observation import BrowserOutputObservation
from opendevin.schema import ActionType
from typing import TYPE_CHECKING
from playwright.async_api import async_playwright
@@ -9,12 +10,13 @@ from .base import ExecutableAction
if TYPE_CHECKING:
from opendevin.controller import AgentController
@dataclass
class BrowseURLAction(ExecutableAction):
url: str
action: str = "browse"
action: str = ActionType.BROWSE
async def run(self, controller: "AgentController") -> BrowserOutputObservation: # type: ignore
async def run(self, controller: "AgentController") -> BrowserOutputObservation: # type: ignore
try:
async with async_playwright() as p:
browser = await p.chromium.launch()
@@ -34,12 +36,9 @@ class BrowseURLAction(ExecutableAction):
)
except Exception as e:
return BrowserOutputObservation(
content=str(e),
screenshot="",
error=True,
url=self.url
content=str(e), screenshot="", error=True, url=self.url
)
@property
def message(self) -> str:
return f"Browsing URL: {self.url}"
return f"Browsing URL: {self.url}"

View File

@@ -2,6 +2,7 @@ import os
from dataclasses import dataclass
from opendevin.observation import FileReadObservation, FileWriteObservation
from opendevin.schema import ActionType
from .base import ExecutableAction
@@ -19,7 +20,7 @@ def resolve_path(base_path, file_path):
@dataclass
class FileReadAction(ExecutableAction):
path: str
action: str = "read"
action: str = ActionType.READ
def run(self, controller) -> FileReadObservation:
path = resolve_path(controller.workdir, self.path)
@@ -35,7 +36,7 @@ class FileReadAction(ExecutableAction):
class FileWriteAction(ExecutableAction):
path: str
content: str
action: str = "write"
action: str = ActionType.WRITE
def run(self, controller) -> FileWriteObservation:
whole_path = resolve_path(controller.workdir, self.path)

View File

@@ -1,25 +1,27 @@
from dataclasses import dataclass, field
from .base import NotExecutableAction
from opendevin.schema import ActionType
@dataclass
class AddTaskAction(NotExecutableAction):
parent: str
goal: str
subtasks: list = field(default_factory=list)
action: str = "add_task"
action: str = ActionType.ADD_TASK
@property
def message(self) -> str:
return f"Added task: {self.goal}"
@dataclass
class ModifyTaskAction(NotExecutableAction):
id: str
state: str
action: str = "modify_task"
action: str = ActionType.MODIFY_TASK
@property
def message(self) -> str:
return f"Set task {self.id} to {self.state}"

View File

@@ -1,13 +1,18 @@
import uvicorn
from fastapi import FastAPI, WebSocket
from opendevin.schema import ActionType
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
# send message to mock connection
await websocket.send_json({"action": "initialize", "message": "Control loop started."})
await websocket.send_json(
{"action": ActionType.INIT, "message": "Control loop started."}
)
try:
while True:
# receive message
@@ -21,10 +26,12 @@ async def websocket_endpoint(websocket: WebSocket):
except Exception as e:
print(f"WebSocket Error: {e}")
@app.get("/")
def read_root():
return {"message": "This is a mock server"}
@app.get("/litellm-models")
def read_llm_models():
return [
@@ -34,6 +41,7 @@ def read_llm_models():
"gpt-4-0613",
]
@app.get("/litellm-agents")
def read_llm_agents():
return [
@@ -42,9 +50,11 @@ def read_llm_agents():
"PlannerAgent",
]
@app.get("/default-model")
def read_default_model():
return "gpt-4"
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=3000)

View File

@@ -1,5 +1,7 @@
import copy
from dataclasses import dataclass
from opendevin.schema import ObservationType
@dataclass
class Observation:
@@ -36,7 +38,8 @@ class NullObservation(Observation):
This data class represents a null observation.
This is used when the produced action is NOT executable.
"""
observation : str = "null"
observation: str = ObservationType.NULL
@property
def message(self) -> str:

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass
from .base import Observation
from opendevin.schema import ObservationType
@dataclass
class BrowserOutputObservation(Observation):
@@ -12,11 +14,8 @@ class BrowserOutputObservation(Observation):
screenshot: str
status_code: int = 200
error: bool = False
observation : str = "browse"
observation: str = ObservationType.BROWSE
@property
def message(self) -> str:
return "Visited " + self.url

View File

@@ -1,16 +1,17 @@
from dataclasses import dataclass
from .base import Observation
from opendevin.schema import ObservationType
@dataclass
class AgentErrorObservation(Observation):
"""
This data class represents an error encountered by the agent.
"""
observation : str = "error"
observation: str = ObservationType.ERROR
@property
def message(self) -> str:
return "Oops. Something went wrong: " + self.content

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass
from .base import Observation
from opendevin.schema import ObservationType
@dataclass
class FileReadObservation(Observation):
@@ -9,12 +11,13 @@ class FileReadObservation(Observation):
"""
path: str
observation : str = "read"
observation: str = ObservationType.READ
@property
def message(self) -> str:
return f"I read the file {self.path}."
@dataclass
class FileWriteObservation(Observation):
"""
@@ -22,10 +25,8 @@ class FileWriteObservation(Observation):
"""
path: str
observation : str = "write"
observation: str = ObservationType.WRITE
@property
def message(self) -> str:
return f"I wrote to the file {self.path}."

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass
from .base import Observation
from opendevin.schema import ObservationType
@dataclass
class UserMessageObservation(Observation):
@@ -9,7 +11,7 @@ class UserMessageObservation(Observation):
"""
role: str = "user"
observation : str = "message"
observation: str = ObservationType.MESSAGE
@property
def message(self) -> str:
@@ -23,11 +25,8 @@ class AgentMessageObservation(Observation):
"""
role: str = "assistant"
observation : str = "message"
observation: str = ObservationType.MESSAGE
@property
def message(self) -> str:
return ""

View File

@@ -2,6 +2,8 @@ from dataclasses import dataclass
from typing import List
from .base import Observation
from opendevin.schema import ObservationType
@dataclass
class AgentRecallObservation(Observation):
@@ -11,11 +13,8 @@ class AgentRecallObservation(Observation):
memories: List[str]
role: str = "assistant"
observation : str = "recall"
observation: str = ObservationType.RECALL
@property
def message(self) -> str:
return "The agent recalled memories."

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass
from .base import Observation
from opendevin.schema import ObservationType
@dataclass
class CmdOutputObservation(Observation):
@@ -11,7 +13,7 @@ class CmdOutputObservation(Observation):
command_id: int
command: str
exit_code: int = 0
observation : str = "run"
observation: str = ObservationType.RUN
@property
def error(self) -> bool:
@@ -19,6 +21,4 @@ class CmdOutputObservation(Observation):
@property
def message(self) -> str:
return f'Command `{self.command}` executed with exit code {self.exit_code}.'
return f"Command `{self.command}` executed with exit code {self.exit_code}."

View File

@@ -42,3 +42,13 @@ class ActionType(str, Enum):
"""If you're absolutely certain that you've completed your task and have tested your work,
use the finish action to stop working.
"""
CHAT = "chat"
SUMMARIZE = "summarize"
ADD_TASK = "add_task"
MODIFY_TASK = "modify_task"
NULL = "null"

View File

@@ -6,6 +6,8 @@ class ObservationType(str, Enum):
"""The content of a file
"""
WRITE = "write"
BROWSE = "browse"
"""The HTML content of a URL
"""
@@ -21,3 +23,9 @@ class ObservationType(str, Enum):
CHAT = "chat"
"""A message from the user
"""
MESSAGE = "message"
ERROR = "error"
NULL = "null"

View File

@@ -12,6 +12,7 @@ from opendevin.controller import AgentController
from opendevin.llm.llm import LLM
from opendevin.observation import NullObservation, Observation, UserMessageObservation
from opendevin.server.session import session_manager
from opendevin.schema import ActionType
DEFAULT_API_KEY = config.get("LLM_API_KEY")
DEFAULT_BASE_URL = config.get("LLM_BASE_URL")
@@ -69,14 +70,14 @@ class AgentManager:
await self.send_error("Invalid action")
return
if action == "initialize":
if action == ActionType.INIT:
await self.create_controller(data)
elif action == "start":
elif action == ActionType.START:
await self.start_task(data)
else:
if self.controller is None:
await self.send_error("No agent started. Please wait a second...")
elif action == "chat":
elif action == ActionType.CHAT:
self.controller.add_history(
NullAction(), UserMessageObservation(data["message"])
)
@@ -141,7 +142,7 @@ class AgentManager:
"Error creating controller. Please check Docker is running using `docker ps`."
)
return
await self.send({"action": "initialize", "message": "Control loop started."})
await self.send({"action": ActionType.INIT, "message": "Control loop started."})
async def start_task(self, start_event):
"""Starts a task for the agent.

View File

@@ -5,7 +5,7 @@ import signal
import uuid
from typing import Dict, List
from opendevin.server.schema.action import ActionType
from opendevin.schema.action import ActionType
CACHE_DIR = os.getenv("CACHE_DIR", "cache")