agent_runner.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import logging
  2. from typing import Optional, cast
  3. from langchain.tools import BaseTool
  4. from core.agent.agent.agent_llm_callback import AgentLLMCallback
  5. from core.agent.agent_executor import AgentConfiguration, AgentExecutor, PlanningStrategy
  6. from core.application_queue_manager import ApplicationQueueManager
  7. from core.callback_handler.agent_loop_gather_callback_handler import AgentLoopGatherCallbackHandler
  8. from core.callback_handler.index_tool_callback_handler import DatasetIndexToolCallbackHandler
  9. from core.callback_handler.std_out_callback_handler import DifyStdOutCallbackHandler
  10. from core.entities.application_entities import (
  11. AgentEntity,
  12. AppOrchestrationConfigEntity,
  13. InvokeFrom,
  14. ModelConfigEntity,
  15. )
  16. from core.memory.token_buffer_memory import TokenBufferMemory
  17. from core.model_runtime.entities.model_entities import ModelFeature, ModelType
  18. from core.model_runtime.model_providers import model_provider_factory
  19. from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
  20. from core.tools.tool.dataset_retriever.dataset_retriever_tool import DatasetRetrieverTool
  21. from extensions.ext_database import db
  22. from models.dataset import Dataset
  23. from models.model import Message
  24. logger = logging.getLogger(__name__)
  25. class AgentRunnerFeature:
  26. def __init__(self, tenant_id: str,
  27. app_orchestration_config: AppOrchestrationConfigEntity,
  28. model_config: ModelConfigEntity,
  29. config: AgentEntity,
  30. queue_manager: ApplicationQueueManager,
  31. message: Message,
  32. user_id: str,
  33. agent_llm_callback: AgentLLMCallback,
  34. callback: AgentLoopGatherCallbackHandler,
  35. memory: Optional[TokenBufferMemory] = None,) -> None:
  36. """
  37. Agent runner
  38. :param tenant_id: tenant id
  39. :param app_orchestration_config: app orchestration config
  40. :param model_config: model config
  41. :param config: dataset config
  42. :param queue_manager: queue manager
  43. :param message: message
  44. :param user_id: user id
  45. :param agent_llm_callback: agent llm callback
  46. :param callback: callback
  47. :param memory: memory
  48. """
  49. self.tenant_id = tenant_id
  50. self.app_orchestration_config = app_orchestration_config
  51. self.model_config = model_config
  52. self.config = config
  53. self.queue_manager = queue_manager
  54. self.message = message
  55. self.user_id = user_id
  56. self.agent_llm_callback = agent_llm_callback
  57. self.callback = callback
  58. self.memory = memory
  59. def run(self, query: str,
  60. invoke_from: InvokeFrom) -> Optional[str]:
  61. """
  62. Retrieve agent loop result.
  63. :param query: query
  64. :param invoke_from: invoke from
  65. :return:
  66. """
  67. provider = self.config.provider
  68. model = self.config.model
  69. tool_configs = self.config.tools
  70. # check model is support tool calling
  71. provider_instance = model_provider_factory.get_provider_instance(provider=provider)
  72. model_type_instance = provider_instance.get_model_instance(ModelType.LLM)
  73. model_type_instance = cast(LargeLanguageModel, model_type_instance)
  74. # get model schema
  75. model_schema = model_type_instance.get_model_schema(
  76. model=model,
  77. credentials=self.model_config.credentials
  78. )
  79. if not model_schema:
  80. return None
  81. planning_strategy = PlanningStrategy.REACT
  82. features = model_schema.features
  83. if features:
  84. if ModelFeature.TOOL_CALL in features \
  85. or ModelFeature.MULTI_TOOL_CALL in features:
  86. planning_strategy = PlanningStrategy.FUNCTION_CALL
  87. tools = self.to_tools(
  88. tool_configs=tool_configs,
  89. invoke_from=invoke_from,
  90. callbacks=[self.callback, DifyStdOutCallbackHandler()],
  91. )
  92. if len(tools) == 0:
  93. return None
  94. agent_configuration = AgentConfiguration(
  95. strategy=planning_strategy,
  96. model_config=self.model_config,
  97. tools=tools,
  98. memory=self.memory,
  99. max_iterations=10,
  100. max_execution_time=400.0,
  101. early_stopping_method="generate",
  102. agent_llm_callback=self.agent_llm_callback,
  103. callbacks=[self.callback, DifyStdOutCallbackHandler()]
  104. )
  105. agent_executor = AgentExecutor(agent_configuration)
  106. try:
  107. # check if should use agent
  108. should_use_agent = agent_executor.should_use_agent(query)
  109. if not should_use_agent:
  110. return None
  111. result = agent_executor.run(query)
  112. return result.output
  113. except Exception as ex:
  114. logger.exception("agent_executor run failed")
  115. return None
  116. def to_dataset_retriever_tool(self, tool_config: dict,
  117. invoke_from: InvokeFrom) \
  118. -> Optional[BaseTool]:
  119. """
  120. A dataset tool is a tool that can be used to retrieve information from a dataset
  121. :param tool_config: tool config
  122. :param invoke_from: invoke from
  123. """
  124. show_retrieve_source = self.app_orchestration_config.show_retrieve_source
  125. hit_callback = DatasetIndexToolCallbackHandler(
  126. queue_manager=self.queue_manager,
  127. app_id=self.message.app_id,
  128. message_id=self.message.id,
  129. user_id=self.user_id,
  130. invoke_from=invoke_from
  131. )
  132. # get dataset from dataset id
  133. dataset = db.session.query(Dataset).filter(
  134. Dataset.tenant_id == self.tenant_id,
  135. Dataset.id == tool_config.get("id")
  136. ).first()
  137. # pass if dataset is not available
  138. if not dataset:
  139. return None
  140. # pass if dataset is not available
  141. if (dataset and dataset.available_document_count == 0
  142. and dataset.available_document_count == 0):
  143. return None
  144. # get retrieval model config
  145. default_retrieval_model = {
  146. 'search_method': 'semantic_search',
  147. 'reranking_enable': False,
  148. 'reranking_model': {
  149. 'reranking_provider_name': '',
  150. 'reranking_model_name': ''
  151. },
  152. 'top_k': 2,
  153. 'score_threshold_enabled': False
  154. }
  155. retrieval_model_config = dataset.retrieval_model \
  156. if dataset.retrieval_model else default_retrieval_model
  157. # get top k
  158. top_k = retrieval_model_config['top_k']
  159. # get score threshold
  160. score_threshold = None
  161. score_threshold_enabled = retrieval_model_config.get("score_threshold_enabled")
  162. if score_threshold_enabled:
  163. score_threshold = retrieval_model_config.get("score_threshold")
  164. tool = DatasetRetrieverTool.from_dataset(
  165. dataset=dataset,
  166. top_k=top_k,
  167. score_threshold=score_threshold,
  168. hit_callbacks=[hit_callback],
  169. return_resource=show_retrieve_source,
  170. retriever_from=invoke_from.to_source()
  171. )
  172. return tool