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

utils.py 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from contextlib import contextmanager
  2. from datetime import datetime
  3. from typing import Union
  4. from urllib.parse import urlparse
  5. from sqlalchemy import select
  6. from extensions.ext_database import db
  7. from models.model import Message
  8. def filter_none_values(data: dict):
  9. new_data = {}
  10. for key, value in data.items():
  11. if value is None:
  12. continue
  13. if isinstance(value, datetime):
  14. new_data[key] = value.isoformat()
  15. else:
  16. new_data[key] = value
  17. return new_data
  18. def get_message_data(message_id: str):
  19. return db.session.scalar(select(Message).where(Message.id == message_id))
  20. @contextmanager
  21. def measure_time():
  22. timing_info = {"start": datetime.now(), "end": None}
  23. try:
  24. yield timing_info
  25. finally:
  26. timing_info["end"] = datetime.now()
  27. def replace_text_with_content(data):
  28. if isinstance(data, dict):
  29. new_data = {}
  30. for key, value in data.items():
  31. if key == "text":
  32. new_data["content"] = value
  33. else:
  34. new_data[key] = replace_text_with_content(value)
  35. return new_data
  36. elif isinstance(data, list):
  37. return [replace_text_with_content(item) for item in data]
  38. else:
  39. return data
  40. def generate_dotted_order(run_id: str, start_time: Union[str, datetime], parent_dotted_order: str | None = None) -> str:
  41. """
  42. generate dotted_order for langsmith
  43. """
  44. start_time = datetime.fromisoformat(start_time) if isinstance(start_time, str) else start_time
  45. timestamp = start_time.strftime("%Y%m%dT%H%M%S%f")[:-3] + "Z"
  46. current_segment = f"{timestamp}{run_id}"
  47. if parent_dotted_order is None:
  48. return current_segment
  49. return f"{parent_dotted_order}.{current_segment}"
  50. def validate_url(url: str, default_url: str, allowed_schemes: tuple = ("https", "http")) -> str:
  51. """
  52. Validate and normalize URL with proper error handling.
  53. NOTE: This function does not retain the `path` component of the provided URL.
  54. In most cases, it is recommended to use `validate_url_with_path` instead.
  55. This function is deprecated and retained only for compatibility purposes.
  56. New implementations should use `validate_url_with_path`.
  57. Args:
  58. url: The URL to validate
  59. default_url: Default URL to use if input is None or empty
  60. allowed_schemes: Tuple of allowed URL schemes (default: https, http)
  61. Returns:
  62. Normalized URL string
  63. Raises:
  64. ValueError: If URL format is invalid or scheme not allowed
  65. """
  66. if not url or url.strip() == "":
  67. return default_url
  68. # Parse URL to validate format
  69. parsed = urlparse(url)
  70. # Check if scheme is allowed
  71. if parsed.scheme not in allowed_schemes:
  72. raise ValueError(f"URL scheme must be one of: {', '.join(allowed_schemes)}")
  73. # Reconstruct URL with only scheme, netloc (removing path, query, fragment)
  74. normalized_url = f"{parsed.scheme}://{parsed.netloc}"
  75. return normalized_url
  76. def validate_url_with_path(url: str, default_url: str, required_suffix: str | None = None) -> str:
  77. """
  78. Validate URL that may include path components
  79. Args:
  80. url: The URL to validate
  81. default_url: Default URL to use if input is None or empty
  82. required_suffix: Optional suffix that URL must end with
  83. Returns:
  84. Validated URL string
  85. Raises:
  86. ValueError: If URL format is invalid or doesn't match required suffix
  87. """
  88. if not url or url.strip() == "":
  89. return default_url
  90. # Parse URL to validate format
  91. parsed = urlparse(url)
  92. # Check if scheme is allowed
  93. if parsed.scheme not in ("https", "http"):
  94. raise ValueError("URL must start with https:// or http://")
  95. # Check required suffix if specified
  96. if required_suffix and not url.endswith(required_suffix):
  97. raise ValueError(f"URL should end with {required_suffix}")
  98. return url
  99. def validate_project_name(project: str, default_name: str) -> str:
  100. """
  101. Validate and normalize project name
  102. Args:
  103. project: Project name to validate
  104. default_name: Default name to use if input is None or empty
  105. Returns:
  106. Normalized project name
  107. """
  108. if not project or project.strip() == "":
  109. return default_name
  110. return project.strip()