services.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from string import Template
  2. from core_shared import JSONValue
  3. from app.db.models import SkillDefinition, SkillInstallation, SkillRun, SkillVersion
  4. from app.domain.repositories import (
  5. SkillDefinitionRepository,
  6. SkillInstallationRepository,
  7. SkillRunRepository,
  8. SkillVersionRepository)
  9. from app.schemas.skill import (
  10. SkillCreateRequest,
  11. SkillInstallRequest,
  12. SkillInstallationStatusUpdateRequest,
  13. SkillRunCreateRequest,
  14. SkillRunExecuteRequest,
  15. SkillStatusUpdateRequest,
  16. SkillVersionCreateRequest)
  17. class SkillApplicationService:
  18. def __init__(
  19. self,
  20. *,
  21. skill_repository: SkillDefinitionRepository,
  22. skill_version_repository: SkillVersionRepository,
  23. installation_repository: SkillInstallationRepository,
  24. skill_run_repository: SkillRunRepository) -> None:
  25. self.skill_repository = skill_repository
  26. self.skill_version_repository = skill_version_repository
  27. self.installation_repository = installation_repository
  28. self.skill_run_repository = skill_run_repository
  29. def create_skill(self, payload: SkillCreateRequest) -> SkillDefinition:
  30. return self.skill_repository.create(
  31. code=payload.code,
  32. name=payload.name,
  33. skill_type=payload.skill_type,
  34. description=payload.description,
  35. owner_user_id=payload.owner_user_id,
  36. metadata_json=payload.metadata_json)
  37. def list_skills(self) -> list[SkillDefinition]:
  38. return self.skill_repository.list_all()
  39. def update_skill_status(
  40. self,
  41. *,
  42. skill_id: str,
  43. payload: SkillStatusUpdateRequest) -> SkillDefinition | None:
  44. return self.skill_repository.update_status(
  45. skill_id=skill_id,
  46. status=payload.status)
  47. def create_skill_version(self, payload: SkillVersionCreateRequest) -> SkillVersion:
  48. skill = self.skill_repository.get_by_id(skill_id=payload.skill_id)
  49. if skill is None:
  50. raise ValueError(f"skill not found: {payload.skill_id}")
  51. return self.skill_version_repository.create(
  52. skill_id=payload.skill_id,
  53. status=payload.status,
  54. runtime_type=payload.runtime_type,
  55. entrypoint=payload.entrypoint,
  56. parameter_schema_json=payload.parameter_schema_json,
  57. output_schema_json=payload.output_schema_json,
  58. implementation_json=payload.implementation_json)
  59. def list_skill_versions(self, *, skill_id: str) -> list[SkillVersion]:
  60. return self.skill_version_repository.list_by_skill(skill_id=skill_id)
  61. def install_skill(self, payload: SkillInstallRequest) -> SkillInstallation:
  62. version = self._resolve_skill_version(
  63. skill_id=payload.skill_id,
  64. skill_version_id=payload.skill_version_id)
  65. if version is None:
  66. raise ValueError("published skill version not found")
  67. return self.installation_repository.create(
  68. skill_id=payload.skill_id,
  69. skill_version_id=version.id,
  70. install_scope=payload.install_scope,
  71. scope_id=payload.scope_id,
  72. config_json=payload.config_json,
  73. installed_by=payload.installed_by)
  74. def list_installations(
  75. self,
  76. *,
  77. install_scope: str | None = None,
  78. scope_id: str | None = None) -> list[SkillInstallation]:
  79. return self.installation_repository.list_by_scope(
  80. install_scope=install_scope,
  81. scope_id=scope_id)
  82. def update_installation_status(
  83. self,
  84. *,
  85. installation_id: str,
  86. payload: SkillInstallationStatusUpdateRequest) -> SkillInstallation | None:
  87. return self.installation_repository.update_status(
  88. installation_id=installation_id,
  89. status=payload.status)
  90. def create_skill_run(self, payload: SkillRunCreateRequest) -> SkillRun:
  91. version = self._resolve_skill_version(
  92. skill_id=payload.skill_id,
  93. skill_version_id=payload.skill_version_id)
  94. if version is None:
  95. raise ValueError("published skill version not found")
  96. return self.skill_run_repository.create(
  97. skill_id=payload.skill_id,
  98. skill_version_id=version.id,
  99. installation_id=payload.installation_id,
  100. input_json=payload.input_json)
  101. def execute_skill_run(
  102. self,
  103. *,
  104. skill_run_id: str,
  105. payload: SkillRunExecuteRequest) -> SkillRun | None:
  106. run = self.skill_run_repository.get_by_id(
  107. skill_run_id=skill_run_id)
  108. if run is None:
  109. return None
  110. version = self.skill_version_repository.get_by_id(
  111. skill_version_id=run.skill_version_id)
  112. if version is None:
  113. return self.skill_run_repository.update_status(
  114. skill_run_id=run.id,
  115. status="failed",
  116. worker_key=payload.worker_key,
  117. error_code="skill_version_missing",
  118. error_message=f"skill version not found: {run.skill_version_id}")
  119. self.skill_run_repository.update_status(
  120. skill_run_id=run.id,
  121. status="running",
  122. worker_key=payload.worker_key)
  123. try:
  124. output_text, output_json = self._execute_version(version=version, input_json=run.input_json)
  125. except ValueError as exc:
  126. return self.skill_run_repository.update_status(
  127. skill_run_id=run.id,
  128. status="failed",
  129. worker_key=payload.worker_key,
  130. error_code="skill_execution_error",
  131. error_message=str(exc))
  132. return self.skill_run_repository.update_status(
  133. skill_run_id=run.id,
  134. status="completed",
  135. worker_key=payload.worker_key,
  136. output_text=output_text,
  137. output_json=output_json)
  138. def _resolve_skill_version(
  139. self,
  140. *,
  141. skill_id: str,
  142. skill_version_id: str | None) -> SkillVersion | None:
  143. if skill_version_id is not None:
  144. return self.skill_version_repository.get_by_id(
  145. skill_version_id=skill_version_id)
  146. return self.skill_version_repository.get_latest_published(
  147. skill_id=skill_id)
  148. def _execute_version(
  149. self,
  150. *,
  151. version: SkillVersion,
  152. input_json: dict[str, JSONValue]) -> tuple[str | None, dict[str, JSONValue]]:
  153. if version.runtime_type != "template":
  154. raise ValueError(f"unsupported skill runtime_type: {version.runtime_type}")
  155. template_value = version.implementation_json.get("template")
  156. if not isinstance(template_value, str):
  157. raise ValueError("template skill requires implementation_json.template")
  158. substitutions = {
  159. key: str(value)
  160. for key, value in input_json.items()
  161. if isinstance(value, (str, int, float, bool))
  162. }
  163. output_text = Template(template_value).safe_substitute(substitutions)
  164. return output_text, {
  165. "runtime_type": version.runtime_type,
  166. "entrypoint": version.entrypoint,
  167. "result": output_text,
  168. }