main.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import asyncio
  2. import os
  3. import sys
  4. import uuid
  5. from typing import Callable, Type
  6. import agenthub # noqa F401 (we import this to get the agents registered)
  7. from opendevin.controller import AgentController
  8. from opendevin.controller.agent import Agent
  9. from opendevin.controller.state.state import State
  10. from opendevin.core.config import (
  11. AppConfig,
  12. get_llm_config_arg,
  13. load_app_config,
  14. parse_arguments,
  15. )
  16. from opendevin.core.logger import opendevin_logger as logger
  17. from opendevin.core.schema import AgentState
  18. from opendevin.events import EventSource, EventStream, EventStreamSubscriber
  19. from opendevin.events.action import MessageAction
  20. from opendevin.events.event import Event
  21. from opendevin.events.observation import AgentStateChangedObservation
  22. from opendevin.llm.llm import LLM
  23. from opendevin.runtime import get_runtime_cls
  24. from opendevin.runtime.runtime import Runtime
  25. from opendevin.runtime.server.runtime import ServerRuntime
  26. from opendevin.storage import get_file_store
  27. def read_task_from_file(file_path: str) -> str:
  28. """Read task from the specified file."""
  29. with open(file_path, 'r', encoding='utf-8') as file:
  30. return file.read()
  31. def read_task_from_stdin() -> str:
  32. """Read task from stdin."""
  33. return sys.stdin.read()
  34. async def create_runtime(
  35. config: AppConfig,
  36. sid: str | None = None,
  37. runtime_tools_config: dict | None = None,
  38. ) -> Runtime:
  39. """Create a runtime for the agent to run on.
  40. config: The app config.
  41. sid: The session id.
  42. runtime_tools_config: (will be deprecated) The runtime tools config.
  43. """
  44. # set up the event stream
  45. file_store = get_file_store(config.file_store, config.file_store_path)
  46. session_id = 'main' + ('_' + sid if sid else str(uuid.uuid4()))
  47. event_stream = EventStream(session_id, file_store)
  48. # agent class
  49. agent_cls = agenthub.Agent.get_cls(config.default_agent)
  50. # runtime and tools
  51. runtime_cls = get_runtime_cls(config.runtime)
  52. logger.info(f'Initializing runtime: {runtime_cls}')
  53. runtime: Runtime = runtime_cls(
  54. config=config,
  55. event_stream=event_stream,
  56. sid=session_id,
  57. plugins=agent_cls.sandbox_plugins,
  58. )
  59. await runtime.ainit()
  60. if isinstance(runtime, ServerRuntime):
  61. runtime.init_runtime_tools(
  62. agent_cls.runtime_tools,
  63. runtime_tools_config=runtime_tools_config,
  64. )
  65. # browser eval specific
  66. # NOTE: This will be deprecated when we move to the new runtime
  67. if runtime.browser and runtime.browser.eval_dir:
  68. logger.info(f'Evaluation directory: {runtime.browser.eval_dir}')
  69. with open(
  70. os.path.join(runtime.browser.eval_dir, 'goal.txt'),
  71. 'r',
  72. encoding='utf-8',
  73. ) as f:
  74. task_str = f.read()
  75. logger.info(f'Dynamic Eval task: {task_str}')
  76. return runtime
  77. async def run_controller(
  78. config: AppConfig,
  79. task_str: str,
  80. runtime: Runtime | None = None,
  81. agent: Agent | None = None,
  82. exit_on_message: bool = False,
  83. fake_user_response_fn: Callable[[State | None], str] | None = None,
  84. headless_mode: bool = True,
  85. ) -> State | None:
  86. """Main coroutine to run the agent controller with task input flexibility.
  87. It's only used when you launch opendevin backend directly via cmdline.
  88. Args:
  89. config: The app config.
  90. task_str: The task to run. It can be a string.
  91. runtime: (optional) A runtime for the agent to run on.
  92. agent: (optional) A agent to run.
  93. exit_on_message: quit if agent asks for a message from user (optional)
  94. fake_user_response_fn: An optional function that receives the current state (could be None) and returns a fake user response.
  95. headless_mode: Whether the agent is run in headless mode.
  96. """
  97. # Create the agent
  98. if agent is None:
  99. agent_cls: Type[Agent] = Agent.get_cls(config.default_agent)
  100. agent = agent_cls(
  101. llm=LLM(config=config.get_llm_config_from_agent(config.default_agent))
  102. )
  103. if runtime is None:
  104. runtime = await create_runtime(config)
  105. event_stream = runtime.event_stream
  106. # restore cli session if enabled
  107. initial_state = None
  108. if config.enable_cli_session:
  109. try:
  110. logger.info('Restoring agent state from cli session')
  111. initial_state = State.restore_from_session(
  112. event_stream.sid, event_stream.file_store
  113. )
  114. except Exception as e:
  115. logger.info('Error restoring state', e)
  116. # init controller with this initial state
  117. controller = AgentController(
  118. agent=agent,
  119. max_iterations=config.max_iterations,
  120. max_budget_per_task=config.max_budget_per_task,
  121. agent_to_llm_config=config.get_agent_to_llm_config_map(),
  122. event_stream=event_stream,
  123. initial_state=initial_state,
  124. headless_mode=headless_mode,
  125. )
  126. assert isinstance(task_str, str), f'task_str must be a string, got {type(task_str)}'
  127. # Logging
  128. logger.info(
  129. f'Agent Controller Initialized: Running agent {agent.name}, model {agent.llm.config.model}, with task: "{task_str}"'
  130. )
  131. # start event is a MessageAction with the task, either resumed or new
  132. if config.enable_cli_session and initial_state is not None:
  133. # we're resuming the previous session
  134. event_stream.add_event(
  135. MessageAction(
  136. content="Let's get back on track. If you experienced errors before, do NOT resume your task. Ask me about it."
  137. ),
  138. EventSource.USER,
  139. )
  140. elif initial_state is None:
  141. # init with the provided task
  142. event_stream.add_event(MessageAction(content=task_str), EventSource.USER)
  143. async def on_event(event: Event):
  144. if isinstance(event, AgentStateChangedObservation):
  145. if event.agent_state == AgentState.AWAITING_USER_INPUT:
  146. if exit_on_message:
  147. message = '/exit'
  148. elif fake_user_response_fn is None:
  149. message = input('Request user input >> ')
  150. else:
  151. message = fake_user_response_fn(controller.get_state())
  152. action = MessageAction(content=message)
  153. event_stream.add_event(action, EventSource.USER)
  154. event_stream.subscribe(EventStreamSubscriber.MAIN, on_event)
  155. while controller.state.agent_state not in [
  156. AgentState.FINISHED,
  157. AgentState.REJECTED,
  158. AgentState.ERROR,
  159. AgentState.PAUSED,
  160. AgentState.STOPPED,
  161. ]:
  162. await asyncio.sleep(1) # Give back control for a tick, so the agent can run
  163. # save session when we're about to close
  164. if config.enable_cli_session:
  165. end_state = controller.get_state()
  166. end_state.save_to_session(event_stream.sid, event_stream.file_store)
  167. # close when done
  168. await controller.close()
  169. state = controller.get_state()
  170. return state
  171. if __name__ == '__main__':
  172. args = parse_arguments()
  173. # Determine the task
  174. if args.file:
  175. task_str = read_task_from_file(args.file)
  176. elif args.task:
  177. task_str = args.task
  178. elif not sys.stdin.isatty():
  179. task_str = read_task_from_stdin()
  180. else:
  181. raise ValueError('No task provided. Please specify a task through -t, -f.')
  182. # Load the app config
  183. # this will load config from config.toml in the current directory
  184. # as well as from the environment variables
  185. config = load_app_config()
  186. # Override default LLM configs ([llm] section in config.toml)
  187. if args.llm_config:
  188. llm_config = get_llm_config_arg(args.llm_config)
  189. if llm_config is None:
  190. raise ValueError(f'Invalid toml file, cannot read {args.llm_config}')
  191. config.set_llm_config(llm_config)
  192. # Set default agent
  193. config.default_agent = args.agent_cls
  194. # if max budget per task is not sent on the command line, use the config value
  195. if args.max_budget_per_task is not None:
  196. config.max_budget_per_task = args.max_budget_per_task
  197. if args.max_iterations is not None:
  198. config.max_iterations = args.max_iterations
  199. asyncio.run(
  200. run_controller(
  201. config=config,
  202. task_str=task_str,
  203. )
  204. )