You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.py 6.0 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import os
  2. from typing import Any, List, Optional, Union
  3. from agnext.components.models import (
  4. AssistantMessage,
  5. AzureOpenAIChatCompletionClient,
  6. ChatCompletionClient,
  7. FunctionExecutionResult,
  8. FunctionExecutionResultMessage,
  9. LLMMessage,
  10. OpenAIChatCompletionClient,
  11. UserMessage,
  12. )
  13. from azure.identity import DefaultAzureCredential, get_bearer_token_provider
  14. from typing_extensions import Literal
  15. from .types import (
  16. FunctionCallMessage,
  17. Message,
  18. MultiModalMessage,
  19. TextMessage,
  20. )
  21. def convert_content_message_to_assistant_message(
  22. message: Union[TextMessage, MultiModalMessage, FunctionCallMessage],
  23. handle_unrepresentable: Literal["error", "ignore", "try_slice"] = "error",
  24. ) -> Optional[AssistantMessage]:
  25. match message:
  26. case TextMessage() | FunctionCallMessage():
  27. return AssistantMessage(content=message.content, source=message.source)
  28. case MultiModalMessage():
  29. if handle_unrepresentable == "error":
  30. raise ValueError("Cannot represent multimodal message as AssistantMessage")
  31. elif handle_unrepresentable == "ignore":
  32. return None
  33. elif handle_unrepresentable == "try_slice":
  34. return AssistantMessage(
  35. content="".join([x for x in message.content if isinstance(x, str)]),
  36. source=message.source,
  37. )
  38. def convert_content_message_to_user_message(
  39. message: Union[TextMessage, MultiModalMessage, FunctionCallMessage],
  40. handle_unrepresentable: Literal["error", "ignore", "try_slice"] = "error",
  41. ) -> Optional[UserMessage]:
  42. match message:
  43. case TextMessage() | MultiModalMessage():
  44. return UserMessage(content=message.content, source=message.source)
  45. case FunctionCallMessage():
  46. if handle_unrepresentable == "error":
  47. raise ValueError("Cannot represent multimodal message as UserMessage")
  48. elif handle_unrepresentable == "ignore":
  49. return None
  50. elif handle_unrepresentable == "try_slice":
  51. # TODO: what is a sliced function call?
  52. raise NotImplementedError("Sliced function calls not yet implemented")
  53. def convert_tool_call_response_message(
  54. message: FunctionExecutionResultMessage,
  55. handle_unrepresentable: Literal["error", "ignore", "try_slice"] = "error",
  56. ) -> Optional[FunctionExecutionResultMessage]:
  57. match message:
  58. case FunctionExecutionResultMessage():
  59. return FunctionExecutionResultMessage(
  60. content=[FunctionExecutionResult(content=x.content, call_id=x.call_id) for x in message.content]
  61. )
  62. def convert_messages_to_llm_messages(
  63. messages: List[Message],
  64. self_name: str,
  65. handle_unrepresentable: Literal["error", "ignore", "try_slice"] = "error",
  66. ) -> List[LLMMessage]:
  67. result: List[LLMMessage] = []
  68. for message in messages:
  69. match message:
  70. case (
  71. TextMessage(content=_, source=source)
  72. | MultiModalMessage(content=_, source=source)
  73. | FunctionCallMessage(content=_, source=source)
  74. ) if source == self_name:
  75. converted_message_1 = convert_content_message_to_assistant_message(message, handle_unrepresentable)
  76. if converted_message_1 is not None:
  77. result.append(converted_message_1)
  78. case (
  79. TextMessage(content=_, source=source)
  80. | MultiModalMessage(content=_, source=source)
  81. | FunctionCallMessage(content=_, source=source)
  82. ) if source != self_name:
  83. converted_message_2 = convert_content_message_to_user_message(message, handle_unrepresentable)
  84. if converted_message_2 is not None:
  85. result.append(converted_message_2)
  86. case FunctionExecutionResultMessage(_):
  87. converted_message_3 = convert_tool_call_response_message(message, handle_unrepresentable)
  88. if converted_message_3 is not None:
  89. result.append(converted_message_3)
  90. case _:
  91. raise AssertionError("unreachable")
  92. return result
  93. def get_chat_completion_client_from_envs(**kwargs: Any) -> ChatCompletionClient:
  94. # Check API type.
  95. api_type = os.getenv("OPENAI_API_TYPE", "openai")
  96. if api_type == "openai":
  97. # Check API key.
  98. api_key = os.getenv("OPENAI_API_KEY")
  99. if api_key is None:
  100. raise ValueError("OPENAI_API_KEY is not set")
  101. kwargs["api_key"] = api_key
  102. return OpenAIChatCompletionClient(**kwargs)
  103. elif api_type == "azure":
  104. # Check Azure API key.
  105. azure_api_key = os.getenv("AZURE_OPENAI_API_KEY")
  106. if azure_api_key is not None:
  107. kwargs["api_key"] = azure_api_key
  108. else:
  109. # Try to use token from Azure CLI.
  110. token_provider = get_bearer_token_provider(
  111. DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
  112. )
  113. kwargs["azure_ad_token_provider"] = token_provider
  114. # Check Azure API endpoint.
  115. azure_api_endpoint = os.getenv("AZURE_OPENAI_API_ENDPOINT")
  116. if azure_api_endpoint is None:
  117. raise ValueError("AZURE_OPENAI_API_ENDPOINT is not set")
  118. kwargs["azure_endpoint"] = azure_api_endpoint
  119. # Get Azure API version.
  120. kwargs["api_version"] = os.getenv("AZURE_OPENAI_API_VERSION", "2024-06-01")
  121. # Set model capabilities.
  122. if "model_capabilities" not in kwargs or kwargs["model_capabilities"] is None:
  123. kwargs["model_capabilities"] = {
  124. "vision": True,
  125. "function_calling": True,
  126. "json_output": True,
  127. }
  128. return AzureOpenAIChatCompletionClient(**kwargs) # type: ignore
  129. raise ValueError(f"Unknown API type: {api_type}")