mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-14 13:14:39 +00:00
Add result schema and add TypedDict conversion.
This commit is contained in:
@@ -2,6 +2,7 @@ from datetime import datetime
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Dict, Any, List
|
||||
from typing_extensions import TypedDict
|
||||
import requests
|
||||
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG
|
||||
|
||||
@@ -13,6 +14,14 @@ import sys
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
|
||||
class ComputeExperimentDesignReturn(TypedDict):
|
||||
solutions: list
|
||||
titration: dict
|
||||
solvents: dict
|
||||
feeding_order: list
|
||||
return_info: str
|
||||
|
||||
|
||||
class BioyondDispensingStation(BioyondWorkstation):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -102,7 +111,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
wt_percent: str = "0.25",
|
||||
m_tot: str = "70",
|
||||
titration_percent: str = "0.03",
|
||||
) -> dict:
|
||||
) -> ComputeExperimentDesignReturn:
|
||||
try:
|
||||
if isinstance(ratio, str):
|
||||
try:
|
||||
|
||||
@@ -83,6 +83,96 @@ workstation.bioyond_dispensing_station:
|
||||
title: batch_create_diamine_solution_tasks参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-brief_step_parameters:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
data: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: brief_step_parameters参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-compute_experiment_design:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
m_tot: '70'
|
||||
ratio: null
|
||||
titration_percent: '0.03'
|
||||
wt_percent: '0.25'
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
m_tot:
|
||||
default: '70'
|
||||
type: string
|
||||
ratio:
|
||||
type: object
|
||||
titration_percent:
|
||||
default: '0.03'
|
||||
type: string
|
||||
wt_percent:
|
||||
default: '0.25'
|
||||
type: string
|
||||
required:
|
||||
- ratio
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
feeding_order:
|
||||
items: {}
|
||||
title: Feeding Order
|
||||
type: array
|
||||
return_info:
|
||||
title: Return Info
|
||||
type: string
|
||||
solutions:
|
||||
items: {}
|
||||
title: Solutions
|
||||
type: array
|
||||
solvents:
|
||||
additionalProperties: true
|
||||
title: Solvents
|
||||
type: object
|
||||
titration:
|
||||
additionalProperties: true
|
||||
title: Titration
|
||||
type: object
|
||||
required:
|
||||
- solutions
|
||||
- titration
|
||||
- solvents
|
||||
- feeding_order
|
||||
- return_info
|
||||
title: ComputeExperimentDesignReturn
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: compute_experiment_design参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_order_finish_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -112,6 +202,85 @@ workstation.bioyond_dispensing_station:
|
||||
title: process_order_finish_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-project_order_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
order_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
order_id:
|
||||
type: string
|
||||
required:
|
||||
- order_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: project_order_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-query_resource_by_name:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_name:
|
||||
type: string
|
||||
required:
|
||||
- material_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: query_resource_by_name参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_materials_to_reaction_station:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
target_device_id: null
|
||||
transfer_groups: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
target_device_id:
|
||||
type: string
|
||||
transfer_groups:
|
||||
type: array
|
||||
required:
|
||||
- target_device_id
|
||||
- transfer_groups
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_materials_to_reaction_station参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_multiple_orders_and_get_reports:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -144,6 +313,31 @@ workstation.bioyond_dispensing_station:
|
||||
title: wait_for_multiple_orders_and_get_reports参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-workflow_sample_locations:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
workflow_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_id:
|
||||
type: string
|
||||
required:
|
||||
- workflow_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: workflow_sample_locations参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
create_90_10_vial_feeding_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
|
||||
@@ -5,6 +5,96 @@ bioyond_dispensing_station:
|
||||
- bioyond_dispensing_station
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-brief_step_parameters:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
data: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: brief_step_parameters参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-compute_experiment_design:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
m_tot: '70'
|
||||
ratio: null
|
||||
titration_percent: '0.03'
|
||||
wt_percent: '0.25'
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
m_tot:
|
||||
default: '70'
|
||||
type: string
|
||||
ratio:
|
||||
type: object
|
||||
titration_percent:
|
||||
default: '0.03'
|
||||
type: string
|
||||
wt_percent:
|
||||
default: '0.25'
|
||||
type: string
|
||||
required:
|
||||
- ratio
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
feeding_order:
|
||||
items: {}
|
||||
title: Feeding Order
|
||||
type: array
|
||||
return_info:
|
||||
title: Return Info
|
||||
type: string
|
||||
solutions:
|
||||
items: {}
|
||||
title: Solutions
|
||||
type: array
|
||||
solvents:
|
||||
additionalProperties: true
|
||||
title: Solvents
|
||||
type: object
|
||||
titration:
|
||||
additionalProperties: true
|
||||
title: Titration
|
||||
type: object
|
||||
required:
|
||||
- solutions
|
||||
- titration
|
||||
- solvents
|
||||
- feeding_order
|
||||
- return_info
|
||||
title: ComputeExperimentDesignReturn
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: compute_experiment_design参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_order_finish_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -34,6 +124,110 @@ bioyond_dispensing_station:
|
||||
title: process_order_finish_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-project_order_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
order_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
order_id:
|
||||
type: string
|
||||
required:
|
||||
- order_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: project_order_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-query_resource_by_name:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_name:
|
||||
type: string
|
||||
required:
|
||||
- material_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: query_resource_by_name参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_materials_to_reaction_station:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
target_device_id: null
|
||||
transfer_groups: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
target_device_id:
|
||||
type: string
|
||||
transfer_groups:
|
||||
type: array
|
||||
required:
|
||||
- target_device_id
|
||||
- transfer_groups
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_materials_to_reaction_station参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-workflow_sample_locations:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
workflow_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_id:
|
||||
type: string
|
||||
required:
|
||||
- workflow_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: workflow_sample_locations参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
batch_create_90_10_vial_feeding_tasks:
|
||||
feedback: {}
|
||||
goal:
|
||||
|
||||
@@ -61,6 +61,9 @@ camera:
|
||||
device_id:
|
||||
default: video_publisher
|
||||
type: string
|
||||
device_uuid:
|
||||
default: ''
|
||||
type: string
|
||||
period:
|
||||
default: 0.1
|
||||
type: number
|
||||
|
||||
@@ -4497,9 +4497,6 @@ liquid_handler:
|
||||
simulator:
|
||||
default: false
|
||||
type: boolean
|
||||
total_height:
|
||||
default: 310
|
||||
type: number
|
||||
required:
|
||||
- backend
|
||||
- deck
|
||||
|
||||
@@ -4,6 +4,215 @@ reaction_station.bioyond:
|
||||
- reaction_station_bioyond
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-create_order:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
json_str: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
json_str:
|
||||
type: string
|
||||
required:
|
||||
- json_str
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_order参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-hard_delete_merged_workflows:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
workflow_ids: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_ids:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- workflow_ids
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: hard_delete_merged_workflows参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-merge_workflow_with_parameters:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
json_str: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
json_str:
|
||||
type: string
|
||||
required:
|
||||
- json_str
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: merge_workflow_with_parameters参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_temperature_cutoff_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
report_request: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
report_request:
|
||||
type: string
|
||||
required:
|
||||
- report_request
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_temperature_cutoff_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_web_workflows:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
web_workflow_json: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
web_workflow_json:
|
||||
type: string
|
||||
required:
|
||||
- web_workflow_json
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_web_workflows参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-skip_titration_steps:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
preintake_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
preintake_id:
|
||||
type: string
|
||||
required:
|
||||
- preintake_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: skip_titration_steps参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_multiple_orders_and_get_reports:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
batch_create_result: null
|
||||
check_interval: 10
|
||||
timeout: 7200
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
batch_create_result:
|
||||
type: string
|
||||
check_interval:
|
||||
default: 10
|
||||
type: integer
|
||||
timeout:
|
||||
default: 7200
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_multiple_orders_and_get_reports参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-workflow_step_query:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
workflow_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_id:
|
||||
type: string
|
||||
required:
|
||||
- workflow_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: workflow_step_query参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
drip_back:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -524,19 +733,7 @@ reaction_station.bioyond:
|
||||
module: unilabos.devices.workstation.bioyond_studio.reaction_station:BioyondReactionStation
|
||||
protocol_type: []
|
||||
status_types:
|
||||
all_workflows: dict
|
||||
average_viscosity: float
|
||||
bioyond_status: dict
|
||||
force: float
|
||||
in_temperature: float
|
||||
out_temperature: float
|
||||
pt100_temperature: float
|
||||
sensor_average_temperature: float
|
||||
setting_temperature: float
|
||||
speed: float
|
||||
target_temperature: float
|
||||
viscosity: float
|
||||
workstation_status: dict
|
||||
workflow_sequence: String
|
||||
type: python
|
||||
config_info: []
|
||||
description: Bioyond反应站
|
||||
@@ -548,21 +745,19 @@ reaction_station.bioyond:
|
||||
config:
|
||||
type: object
|
||||
deck:
|
||||
type: object
|
||||
type: string
|
||||
protocol_type:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
all_workflows:
|
||||
type: object
|
||||
bioyond_status:
|
||||
type: object
|
||||
workstation_status:
|
||||
type: object
|
||||
workflow_sequence:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- bioyond_status
|
||||
- all_workflows
|
||||
- workstation_status
|
||||
- workflow_sequence
|
||||
type: object
|
||||
version: 1.0.0
|
||||
reaction_station.reactor:
|
||||
@@ -570,19 +765,34 @@ reaction_station.reactor:
|
||||
- reactor
|
||||
- reaction_station_bioyond
|
||||
class:
|
||||
action_value_mappings: {}
|
||||
action_value_mappings:
|
||||
auto-update_metrics:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
payload: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
payload:
|
||||
type: object
|
||||
required:
|
||||
- payload
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: update_metrics参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.bioyond_studio.reaction_station:BioyondReactor
|
||||
status_types:
|
||||
average_viscosity: float
|
||||
force: float
|
||||
in_temperature: float
|
||||
out_temperature: float
|
||||
pt100_temperature: float
|
||||
sensor_average_temperature: float
|
||||
setting_temperature: float
|
||||
speed: float
|
||||
target_temperature: float
|
||||
viscosity: float
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 反应站子设备-反应器
|
||||
@@ -593,30 +803,14 @@ reaction_station.reactor:
|
||||
properties:
|
||||
config:
|
||||
type: object
|
||||
deck:
|
||||
type: string
|
||||
protocol_type:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
average_viscosity:
|
||||
type: number
|
||||
force:
|
||||
type: number
|
||||
in_temperature:
|
||||
type: number
|
||||
out_temperature:
|
||||
type: number
|
||||
pt100_temperature:
|
||||
type: number
|
||||
sensor_average_temperature:
|
||||
type: number
|
||||
setting_temperature:
|
||||
type: number
|
||||
speed:
|
||||
type: number
|
||||
target_temperature:
|
||||
type: number
|
||||
viscosity:
|
||||
type: number
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -453,7 +453,7 @@ class Registry:
|
||||
return status_schema
|
||||
|
||||
def _generate_unilab_json_command_schema(
|
||||
self, method_args: List[Dict[str, Any]], method_name: str
|
||||
self, method_args: List[Dict[str, Any]], method_name: str, return_annotation: Any = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
根据UniLabJsonCommand方法信息生成JSON Schema,暂不支持嵌套类型
|
||||
@@ -461,6 +461,7 @@ class Registry:
|
||||
Args:
|
||||
method_args: 方法信息字典,包含args等
|
||||
method_name: 方法名称
|
||||
return_annotation: 返回类型注解,用于生成result schema(仅支持TypedDict)
|
||||
|
||||
Returns:
|
||||
JSON Schema格式的参数schema
|
||||
@@ -489,14 +490,68 @@ class Registry:
|
||||
if param_required:
|
||||
schema["required"].append(param_name)
|
||||
|
||||
# 生成result schema(仅当return_annotation是TypedDict时)
|
||||
result_schema = {}
|
||||
if return_annotation is not None and self._is_typed_dict(return_annotation):
|
||||
result_schema = self._generate_typed_dict_result_schema(return_annotation)
|
||||
|
||||
return {
|
||||
"title": f"{method_name}参数",
|
||||
"description": f"",
|
||||
"type": "object",
|
||||
"properties": {"goal": schema, "feedback": {}, "result": {}},
|
||||
"properties": {"goal": schema, "feedback": {}, "result": result_schema},
|
||||
"required": ["goal"],
|
||||
}
|
||||
|
||||
def _is_typed_dict(self, annotation: Any) -> bool:
|
||||
"""
|
||||
检查类型注解是否是TypedDict
|
||||
|
||||
Args:
|
||||
annotation: 类型注解对象
|
||||
|
||||
Returns:
|
||||
是否为TypedDict
|
||||
"""
|
||||
if annotation is None or annotation == inspect.Parameter.empty:
|
||||
return False
|
||||
|
||||
# 使用 typing_extensions.is_typeddict 进行检查(Python < 3.12 兼容)
|
||||
try:
|
||||
from typing_extensions import is_typeddict
|
||||
|
||||
return is_typeddict(annotation)
|
||||
except ImportError:
|
||||
# 回退方案:检查 TypedDict 特有的属性
|
||||
if isinstance(annotation, type):
|
||||
return hasattr(annotation, "__required_keys__") and hasattr(annotation, "__optional_keys__")
|
||||
return False
|
||||
|
||||
def _generate_typed_dict_result_schema(self, return_annotation: Any) -> Dict[str, Any]:
|
||||
"""
|
||||
根据TypedDict类型生成result的JSON Schema
|
||||
|
||||
Args:
|
||||
return_annotation: TypedDict类型注解
|
||||
|
||||
Returns:
|
||||
JSON Schema格式的result schema
|
||||
"""
|
||||
if not self._is_typed_dict(return_annotation):
|
||||
return {}
|
||||
|
||||
try:
|
||||
from msgcenterpy.instances.typed_dict_instance import TypedDictMessageInstance
|
||||
|
||||
result_schema = TypedDictMessageInstance.get_json_schema_from_typed_dict(return_annotation)
|
||||
return result_schema
|
||||
except ImportError:
|
||||
logger.warning("[UniLab Registry] msgcenterpy未安装,无法生成TypedDict的result schema")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.warning(f"[UniLab Registry] 生成TypedDict result schema失败: {e}")
|
||||
return {}
|
||||
|
||||
def _add_builtin_actions(self, device_config: Dict[str, Any], device_id: str):
|
||||
"""
|
||||
为设备配置添加内置的执行驱动命令动作
|
||||
@@ -577,9 +632,15 @@ class Registry:
|
||||
if "init_param_schema" not in device_config:
|
||||
device_config["init_param_schema"] = {}
|
||||
if "class" in device_config:
|
||||
if "status_types" not in device_config["class"] or device_config["class"]["status_types"] is None:
|
||||
if (
|
||||
"status_types" not in device_config["class"]
|
||||
or device_config["class"]["status_types"] is None
|
||||
):
|
||||
device_config["class"]["status_types"] = {}
|
||||
if "action_value_mappings" not in device_config["class"] or device_config["class"]["action_value_mappings"] is None:
|
||||
if (
|
||||
"action_value_mappings" not in device_config["class"]
|
||||
or device_config["class"]["action_value_mappings"] is None
|
||||
):
|
||||
device_config["class"]["action_value_mappings"] = {}
|
||||
enhanced_info = {}
|
||||
if complete_registry:
|
||||
@@ -631,7 +692,9 @@ class Registry:
|
||||
"goal": {},
|
||||
"feedback": {},
|
||||
"result": {},
|
||||
"schema": self._generate_unilab_json_command_schema(v["args"], k),
|
||||
"schema": self._generate_unilab_json_command_schema(
|
||||
v["args"], k, v.get("return_annotation")
|
||||
),
|
||||
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
||||
"handles": [],
|
||||
"placeholder_keys": {
|
||||
|
||||
@@ -5,7 +5,7 @@ import threading
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, Optional, Dict, Any, List, ClassVar, Set, Union
|
||||
from typing import TYPE_CHECKING, Optional, Dict, Any, List, ClassVar, Set, TypedDict, Union
|
||||
|
||||
from action_msgs.msg import GoalStatus
|
||||
from geometry_msgs.msg import Point
|
||||
@@ -38,6 +38,7 @@ from unilabos.ros.msgs.message_converter import (
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, ROS2DeviceNode, DeviceNodeResourceTracker
|
||||
from unilabos.ros.nodes.presets.controller_node import ControllerNode
|
||||
from unilabos.ros.nodes.resource_tracker import (
|
||||
ResourceDict,
|
||||
ResourceDictInstance,
|
||||
ResourceTreeSet,
|
||||
ResourceTreeInstance,
|
||||
@@ -48,7 +49,7 @@ from unilabos.utils.type_check import serialize_result_info
|
||||
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from unilabos.app.ws_client import QueueItem, WSResourceChatData
|
||||
from unilabos.app.ws_client import QueueItem
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -56,6 +57,11 @@ class DeviceActionStatus:
|
||||
job_ids: Dict[str, float] = field(default_factory=dict)
|
||||
|
||||
|
||||
class TestResourceReturn(TypedDict):
|
||||
resources: List[List[ResourceDict]]
|
||||
devices: List[DeviceSlot]
|
||||
|
||||
|
||||
class HostNode(BaseROS2DeviceNode):
|
||||
"""
|
||||
主机节点类,负责管理设备、资源和控制器
|
||||
@@ -1346,7 +1352,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
|
||||
def test_resource(
|
||||
self, resource: ResourceSlot, resources: List[ResourceSlot], device: DeviceSlot, devices: List[DeviceSlot]
|
||||
):
|
||||
) -> TestResourceReturn:
|
||||
return {
|
||||
"resources": ResourceTreeSet.from_plr_resources([resource, *resources]).dump(),
|
||||
"devices": [device, *devices],
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import locale
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
@@ -22,13 +23,33 @@ class EnvironmentChecker:
|
||||
"websockets": "websockets",
|
||||
"msgcenterpy": "msgcenterpy",
|
||||
"opentrons_shared_data": "opentrons_shared_data",
|
||||
"typing_extensions": "typing_extensions",
|
||||
}
|
||||
|
||||
# 特殊安装包(需要特殊处理的包)
|
||||
self.special_packages = {"pylabrobot": "git+https://github.com/Xuwznln/pylabrobot.git"}
|
||||
|
||||
# 包版本要求(包名: 最低版本)
|
||||
self.version_requirements = {
|
||||
"msgcenterpy": "0.1.5", # msgcenterpy 最低版本要求
|
||||
}
|
||||
|
||||
self.missing_packages = []
|
||||
self.failed_installs = []
|
||||
self.packages_need_upgrade = []
|
||||
|
||||
# 检测系统语言
|
||||
self.is_chinese = self._is_chinese_locale()
|
||||
|
||||
def _is_chinese_locale(self) -> bool:
|
||||
"""检测系统是否为中文环境"""
|
||||
try:
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
if lang and ("zh" in lang.lower() or "chinese" in lang.lower()):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def check_package_installed(self, package_name: str) -> bool:
|
||||
"""检查包是否已安装"""
|
||||
@@ -38,31 +59,74 @@ class EnvironmentChecker:
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def install_package(self, package_name: str, pip_name: str) -> bool:
|
||||
def get_package_version(self, package_name: str) -> str | None:
|
||||
"""获取已安装包的版本"""
|
||||
try:
|
||||
module = importlib.import_module(package_name)
|
||||
return getattr(module, "__version__", None)
|
||||
except (ImportError, AttributeError):
|
||||
return None
|
||||
|
||||
def compare_version(self, current: str, required: str) -> bool:
|
||||
"""
|
||||
比较版本号
|
||||
Returns:
|
||||
True: current >= required
|
||||
False: current < required
|
||||
"""
|
||||
try:
|
||||
current_parts = [int(x) for x in current.split(".")]
|
||||
required_parts = [int(x) for x in required.split(".")]
|
||||
|
||||
# 补齐长度
|
||||
max_len = max(len(current_parts), len(required_parts))
|
||||
current_parts.extend([0] * (max_len - len(current_parts)))
|
||||
required_parts.extend([0] * (max_len - len(required_parts)))
|
||||
|
||||
return current_parts >= required_parts
|
||||
except Exception:
|
||||
return True # 如果无法比较,假设版本满足要求
|
||||
|
||||
def install_package(self, package_name: str, pip_name: str, upgrade: bool = False) -> bool:
|
||||
"""安装包"""
|
||||
try:
|
||||
print_status(f"正在安装 {package_name} ({pip_name})...", "info")
|
||||
action = "升级" if upgrade else "安装"
|
||||
print_status(f"正在{action} {package_name} ({pip_name})...", "info")
|
||||
|
||||
# 构建安装命令
|
||||
cmd = [sys.executable, "-m", "pip", "install", pip_name]
|
||||
cmd = [sys.executable, "-m", "pip", "install"]
|
||||
|
||||
# 如果是升级操作,添加 --upgrade 参数
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
|
||||
cmd.append(pip_name)
|
||||
|
||||
# 如果是中文环境,使用清华镜像源
|
||||
if self.is_chinese:
|
||||
cmd.extend(["-i", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
|
||||
# 执行安装
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) # 5分钟超时
|
||||
|
||||
if result.returncode == 0:
|
||||
print_status(f"✓ {package_name} 安装成功", "success")
|
||||
print_status(f"✓ {package_name} {action}成功", "success")
|
||||
return True
|
||||
else:
|
||||
print_status(f"× {package_name} 安装失败: {result.stderr}", "error")
|
||||
print_status(f"× {package_name} {action}失败: {result.stderr}", "error")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print_status(f"× {package_name} 安装超时", "error")
|
||||
print_status(f"× {package_name} {action}超时", "error")
|
||||
return False
|
||||
except Exception as e:
|
||||
print_status(f"× {package_name} 安装异常: {str(e)}", "error")
|
||||
print_status(f"× {package_name} {action}异常: {str(e)}", "error")
|
||||
return False
|
||||
|
||||
def upgrade_package(self, package_name: str, pip_name: str) -> bool:
|
||||
"""升级包"""
|
||||
return self.install_package(package_name, pip_name, upgrade=True)
|
||||
|
||||
def check_all_packages(self) -> bool:
|
||||
"""检查所有必需的包"""
|
||||
print_status("开始检查环境依赖...", "info")
|
||||
@@ -71,60 +135,116 @@ class EnvironmentChecker:
|
||||
for import_name, pip_name in self.required_packages.items():
|
||||
if not self.check_package_installed(import_name):
|
||||
self.missing_packages.append((import_name, pip_name))
|
||||
else:
|
||||
# 检查版本要求
|
||||
if import_name in self.version_requirements:
|
||||
current_version = self.get_package_version(import_name)
|
||||
required_version = self.version_requirements[import_name]
|
||||
|
||||
if current_version:
|
||||
if not self.compare_version(current_version, required_version):
|
||||
print_status(
|
||||
f"{import_name} 版本过低 (当前: {current_version}, 需要: >={required_version})",
|
||||
"warning",
|
||||
)
|
||||
self.packages_need_upgrade.append((import_name, pip_name))
|
||||
|
||||
# 检查特殊包
|
||||
for package_name, install_url in self.special_packages.items():
|
||||
if not self.check_package_installed(package_name):
|
||||
self.missing_packages.append((package_name, install_url))
|
||||
|
||||
if not self.missing_packages:
|
||||
all_ok = not self.missing_packages and not self.packages_need_upgrade
|
||||
|
||||
if all_ok:
|
||||
print_status("✓ 所有依赖包检查完成,环境正常", "success")
|
||||
return True
|
||||
|
||||
print_status(f"发现 {len(self.missing_packages)} 个缺失的包", "warning")
|
||||
if self.missing_packages:
|
||||
print_status(f"发现 {len(self.missing_packages)} 个缺失的包", "warning")
|
||||
if self.packages_need_upgrade:
|
||||
print_status(f"发现 {len(self.packages_need_upgrade)} 个需要升级的包", "warning")
|
||||
|
||||
return False
|
||||
|
||||
def install_missing_packages(self, auto_install: bool = True) -> bool:
|
||||
"""安装缺失的包"""
|
||||
if not self.missing_packages:
|
||||
if not self.missing_packages and not self.packages_need_upgrade:
|
||||
return True
|
||||
|
||||
if not auto_install:
|
||||
print_status("缺失以下包:", "warning")
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
print_status(f" - {import_name} (pip install {pip_name})", "warning")
|
||||
if self.missing_packages:
|
||||
print_status("缺失以下包:", "warning")
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
print_status(f" - {import_name} (pip install {pip_name})", "warning")
|
||||
if self.packages_need_upgrade:
|
||||
print_status("需要升级以下包:", "warning")
|
||||
for import_name, pip_name in self.packages_need_upgrade:
|
||||
print_status(f" - {import_name} (pip install --upgrade {pip_name})", "warning")
|
||||
return False
|
||||
|
||||
print_status(f"开始自动安装 {len(self.missing_packages)} 个缺失的包...", "info")
|
||||
# 安装缺失的包
|
||||
if self.missing_packages:
|
||||
print_status(f"开始自动安装 {len(self.missing_packages)} 个缺失的包...", "info")
|
||||
|
||||
success_count = 0
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
if self.install_package(import_name, pip_name):
|
||||
success_count += 1
|
||||
else:
|
||||
self.failed_installs.append((import_name, pip_name))
|
||||
success_count = 0
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
if self.install_package(import_name, pip_name):
|
||||
success_count += 1
|
||||
else:
|
||||
self.failed_installs.append((import_name, pip_name))
|
||||
|
||||
print_status(f"✓ 成功安装 {success_count}/{len(self.missing_packages)} 个包", "success")
|
||||
|
||||
# 升级需要更新的包
|
||||
if self.packages_need_upgrade:
|
||||
print_status(f"开始自动升级 {len(self.packages_need_upgrade)} 个包...", "info")
|
||||
|
||||
upgrade_success_count = 0
|
||||
for import_name, pip_name in self.packages_need_upgrade:
|
||||
if self.upgrade_package(import_name, pip_name):
|
||||
upgrade_success_count += 1
|
||||
else:
|
||||
self.failed_installs.append((import_name, pip_name))
|
||||
|
||||
print_status(f"✓ 成功升级 {upgrade_success_count}/{len(self.packages_need_upgrade)} 个包", "success")
|
||||
|
||||
if self.failed_installs:
|
||||
print_status(f"有 {len(self.failed_installs)} 个包安装失败:", "error")
|
||||
print_status(f"有 {len(self.failed_installs)} 个包操作失败:", "error")
|
||||
for import_name, pip_name in self.failed_installs:
|
||||
print_status(f" - {import_name} (pip install {pip_name})", "error")
|
||||
print_status(f" - {import_name} ({pip_name})", "error")
|
||||
return False
|
||||
|
||||
print_status(f"✓ 成功安装 {success_count} 个包", "success")
|
||||
return True
|
||||
|
||||
def verify_installation(self) -> bool:
|
||||
"""验证安装结果"""
|
||||
if not self.missing_packages:
|
||||
if not self.missing_packages and not self.packages_need_upgrade:
|
||||
return True
|
||||
|
||||
print_status("验证安装结果...", "info")
|
||||
|
||||
failed_verification = []
|
||||
|
||||
# 验证新安装的包
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
if not self.check_package_installed(import_name):
|
||||
failed_verification.append((import_name, pip_name))
|
||||
|
||||
# 验证升级的包
|
||||
for import_name, pip_name in self.packages_need_upgrade:
|
||||
if not self.check_package_installed(import_name):
|
||||
failed_verification.append((import_name, pip_name))
|
||||
elif import_name in self.version_requirements:
|
||||
current_version = self.get_package_version(import_name)
|
||||
required_version = self.version_requirements[import_name]
|
||||
if current_version and not self.compare_version(current_version, required_version):
|
||||
failed_verification.append((import_name, pip_name))
|
||||
print_status(
|
||||
f" {import_name} 版本仍然过低 (当前: {current_version}, 需要: >={required_version})",
|
||||
"error",
|
||||
)
|
||||
|
||||
if failed_verification:
|
||||
print_status(f"有 {len(failed_verification)} 个包验证失败:", "error")
|
||||
for import_name, pip_name in failed_verification:
|
||||
|
||||
@@ -239,8 +239,12 @@ class ImportManager:
|
||||
cls = get_class(class_path)
|
||||
class_name = cls.__name__
|
||||
|
||||
result = {"class_name": class_name, "init_params": self._analyze_method_signature(cls.__init__)["args"],
|
||||
"status_methods": {}, "action_methods": {}}
|
||||
result = {
|
||||
"class_name": class_name,
|
||||
"init_params": self._analyze_method_signature(cls.__init__)["args"],
|
||||
"status_methods": {},
|
||||
"action_methods": {},
|
||||
}
|
||||
# 分析类的所有成员
|
||||
for name, method in cls.__dict__.items():
|
||||
if name.startswith("_"):
|
||||
@@ -374,6 +378,7 @@ class ImportManager:
|
||||
"name": method.__name__,
|
||||
"args": args,
|
||||
"return_type": self._get_type_string(signature.return_annotation),
|
||||
"return_annotation": signature.return_annotation, # 保留原始类型注解,用于TypedDict等特殊处理
|
||||
"is_async": inspect.iscoroutinefunction(method),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user