main.py 6.6 KB

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