Add tool quality report contract
This commit is contained in:
@@ -20,10 +20,10 @@ use crate::{
|
|||||||
capabilities::get_capabilities,
|
capabilities::get_capabilities,
|
||||||
observability::{get_agent_usage, get_log, get_operation_usage, get_usage, list_logs},
|
observability::{get_agent_usage, get_log, get_operation_usage, get_usage, list_logs},
|
||||||
operations::{
|
operations::{
|
||||||
archive_operation, create_operation, create_version, delete_operation,
|
analyze_operation_quality, archive_operation, create_operation, create_version,
|
||||||
export_operation, generate_draft, get_operation, get_operation_version,
|
delete_operation, export_operation, generate_draft, get_operation,
|
||||||
list_operations, publish_operation, run_test, update_operation, upload_input_json,
|
get_operation_version, list_operations, publish_operation, run_test, update_operation,
|
||||||
upload_output_json,
|
upload_input_json, upload_output_json,
|
||||||
},
|
},
|
||||||
secrets::{create_secret, delete_secret, get_secret, list_secrets, rotate_secret},
|
secrets::{create_secret, delete_secret, get_secret, list_secrets, rotate_secret},
|
||||||
upstreams::{create_upstream, list_upstreams, update_upstream},
|
upstreams::{create_upstream, list_upstreams, update_upstream},
|
||||||
@@ -35,6 +35,10 @@ use crate::{
|
|||||||
pub fn build_app(state: AppState) -> Router {
|
pub fn build_app(state: AppState) -> Router {
|
||||||
let workspace_router = Router::new()
|
let workspace_router = Router::new()
|
||||||
.route("/operations", get(list_operations).post(create_operation))
|
.route("/operations", get(list_operations).post(create_operation))
|
||||||
|
.route(
|
||||||
|
"/operations/analyze-quality",
|
||||||
|
post(analyze_operation_quality),
|
||||||
|
)
|
||||||
.route("/operations/import", post(import_operation))
|
.route("/operations/import", post(import_operation))
|
||||||
.route(
|
.route(
|
||||||
"/operations/{operation_id}",
|
"/operations/{operation_id}",
|
||||||
|
|||||||
@@ -64,6 +64,18 @@ pub async fn create_operation(
|
|||||||
Ok(Json(json!(created)))
|
Ok(Json(json!(created)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn analyze_operation_quality(
|
||||||
|
Path(path): Path<WorkspacePath>,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(payload): Json<OperationPayload>,
|
||||||
|
) -> Result<Json<Value>, ApiError> {
|
||||||
|
let report = state
|
||||||
|
.service
|
||||||
|
.analyze_operation_quality(&path.workspace_id.as_str().into(), payload)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(json!(report)))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_operation(
|
pub async fn get_operation(
|
||||||
Path(path): Path<WorkspaceOperationPath>,
|
Path(path): Path<WorkspaceOperationPath>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
|
|||||||
@@ -1443,6 +1443,18 @@ impl AdminService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, payload), fields(protocol = ?payload.protocol, operation_name = %payload.name))]
|
||||||
|
pub async fn analyze_operation_quality(
|
||||||
|
&self,
|
||||||
|
workspace_id: &WorkspaceId,
|
||||||
|
payload: OperationPayload,
|
||||||
|
) -> Result<crank_core::ToolQualityReport, ApiError> {
|
||||||
|
self.validate_operation_payload(&payload)?;
|
||||||
|
self.ensure_workspace_exists(workspace_id).await?;
|
||||||
|
|
||||||
|
Ok(crank_core::ToolQualityReport::empty())
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(self, payload), fields(operation_id = %operation_id.as_str(), protocol = ?payload.operation.protocol))]
|
#[instrument(skip(self, payload), fields(operation_id = %operation_id.as_str(), protocol = ?payload.operation.protocol))]
|
||||||
pub async fn create_version(
|
pub async fn create_version(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub mod observability;
|
|||||||
pub mod operation;
|
pub mod operation;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
|
pub mod tool_quality;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
|
||||||
pub use access::{
|
pub use access::{
|
||||||
@@ -61,4 +62,5 @@ pub use operation::{
|
|||||||
};
|
};
|
||||||
pub use protocol::{AuthKind, ExportMode, HttpMethod, Protocol};
|
pub use protocol::{AuthKind, ExportMode, HttpMethod, Protocol};
|
||||||
pub use secret::{Secret, SecretKind, SecretStatus, SecretVersion};
|
pub use secret::{Secret, SecretKind, SecretStatus, SecretVersion};
|
||||||
|
pub use tool_quality::{ToolQualityFinding, ToolQualityReport, ToolQualitySeverity};
|
||||||
pub use workspace::{Workspace, WorkspaceStatus};
|
pub use workspace::{Workspace, WorkspaceStatus};
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ToolQualitySeverity {
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct ToolQualityFinding {
|
||||||
|
pub severity: ToolQualitySeverity,
|
||||||
|
pub code: String,
|
||||||
|
pub message: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub suggested_action: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub field_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct ToolQualityReport {
|
||||||
|
pub blocking: bool,
|
||||||
|
pub findings: Vec<ToolQualityFinding>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolQualityReport {
|
||||||
|
pub fn new(findings: Vec<ToolQualityFinding>) -> Self {
|
||||||
|
let blocking = findings
|
||||||
|
.iter()
|
||||||
|
.any(|finding| finding.severity == ToolQualitySeverity::Error);
|
||||||
|
|
||||||
|
Self { blocking, findings }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::new(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod unit {
|
||||||
|
mod tool_quality;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use crank_core::{ToolQualityFinding, ToolQualityReport, ToolQualitySeverity};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serializes_tool_quality_finding_contract() {
|
||||||
|
let finding = ToolQualityFinding {
|
||||||
|
severity: ToolQualitySeverity::Warning,
|
||||||
|
code: "tool_description_too_short".to_owned(),
|
||||||
|
message: "Описание инструмента слишком короткое.".to_owned(),
|
||||||
|
suggested_action: Some("Добавьте назначение и условия применения инструмента.".to_owned()),
|
||||||
|
field_path: Some("tool_description.description".to_owned()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = serde_json::to_value(finding).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(value["severity"], "warning");
|
||||||
|
assert_eq!(value["code"], "tool_description_too_short");
|
||||||
|
assert_eq!(value["field_path"], "tool_description.description");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn report_is_blocking_when_error_finding_exists() {
|
||||||
|
let report = ToolQualityReport::new(vec![ToolQualityFinding {
|
||||||
|
severity: ToolQualitySeverity::Error,
|
||||||
|
code: "tool_name_missing".to_owned(),
|
||||||
|
message: "Имя инструмента обязательно.".to_owned(),
|
||||||
|
suggested_action: None,
|
||||||
|
field_path: Some("name".to_owned()),
|
||||||
|
}]);
|
||||||
|
|
||||||
|
assert!(report.blocking);
|
||||||
|
assert_eq!(report.findings.len(), 1);
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ Community работает с одним bootstrap workspace. API не подд
|
|||||||
|
|
||||||
- `GET /api/admin/workspaces/{workspace_id}/operations`
|
- `GET /api/admin/workspaces/{workspace_id}/operations`
|
||||||
- `POST /api/admin/workspaces/{workspace_id}/operations`
|
- `POST /api/admin/workspaces/{workspace_id}/operations`
|
||||||
|
- `POST /api/admin/workspaces/{workspace_id}/operations/analyze-quality`
|
||||||
- `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
|
- `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
|
||||||
- `PATCH /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
|
- `PATCH /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
|
||||||
- `DELETE /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
|
- `DELETE /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
|
||||||
|
|||||||
Reference in New Issue
Block a user