mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-31 03:12:16 +00:00
Migrate new modules to rust, more compile time abs, remove python fallback
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
//! PyO3 bindings for A2A (Agent-to-Agent) protocol types and task store.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(name = "AgentCard")]
|
||||
pub struct PyAgentCard {
|
||||
inner: openjarvis_a2a::AgentCard,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyAgentCard {
|
||||
#[new]
|
||||
#[pyo3(signature = (name, description, version, url))]
|
||||
fn new(name: &str, description: &str, version: &str, url: &str) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_a2a::AgentCard::new(name, description, version, url),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_skills(&mut self, skills: Vec<String>) {
|
||||
let refs: Vec<&str> = skills.iter().map(|s| s.as_str()).collect();
|
||||
self.inner = self.inner.clone().with_skills(&refs);
|
||||
}
|
||||
|
||||
fn with_modes(&mut self, modes: Vec<String>) {
|
||||
let refs: Vec<&str> = modes.iter().map(|s| s.as_str()).collect();
|
||||
self.inner = self.inner.clone().with_modes(&refs);
|
||||
}
|
||||
|
||||
fn to_json(&self) -> String {
|
||||
serde_json::to_string(&self.inner).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn name(&self) -> &str {
|
||||
&self.inner.name
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn description(&self) -> &str {
|
||||
&self.inner.description
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn version(&self) -> &str {
|
||||
&self.inner.version
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn url(&self) -> &str {
|
||||
&self.inner.url
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn skills(&self) -> Vec<String> {
|
||||
self.inner.skills.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "A2ATaskStore")]
|
||||
pub struct PyA2ATaskStore {
|
||||
inner: openjarvis_a2a::A2ATaskStore,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyA2ATaskStore {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: openjarvis_a2a::A2ATaskStore::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_task(&mut self, input: &str) -> String {
|
||||
let task = self.inner.create_task(input);
|
||||
serde_json::to_string(&task).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_task(&self, id: &str) -> Option<String> {
|
||||
self.inner
|
||||
.get_task(id)
|
||||
.map(|t| serde_json::to_string(t).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn update_state(&mut self, id: &str, state: &str) -> bool {
|
||||
let s = match state {
|
||||
"pending" => openjarvis_a2a::TaskState::Pending,
|
||||
"active" => openjarvis_a2a::TaskState::Active,
|
||||
"completed" => openjarvis_a2a::TaskState::Completed,
|
||||
"cancelled" => openjarvis_a2a::TaskState::Cancelled,
|
||||
"failed" => openjarvis_a2a::TaskState::Failed,
|
||||
_ => return false,
|
||||
};
|
||||
self.inner.update_state(id, s)
|
||||
}
|
||||
|
||||
fn set_output(&mut self, id: &str, output: &str) -> bool {
|
||||
self.inner.set_output(id, output)
|
||||
}
|
||||
|
||||
fn list_tasks(&self) -> String {
|
||||
serde_json::to_string(self.inner.list_tasks()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
pub fn parse_a2a_request(json_str: &str) -> PyResult<String> {
|
||||
let req = openjarvis_a2a::parse_request(json_str)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?;
|
||||
Ok(serde_json::to_string(&req).unwrap_or_default())
|
||||
}
|
||||
@@ -1,24 +1,67 @@
|
||||
//! PyO3 bindings for agent types.
|
||||
//!
|
||||
//! At the Python boundary, agents use `Box<dyn OjAgent>` for type erasure
|
||||
//! since Python can't handle Rust generics. The shared tokio Runtime
|
||||
//! bridges async→sync.
|
||||
//! Uses `AgentEnum` for static dispatch instead of `Box<dyn OjAgent>`.
|
||||
|
||||
use crate::core::PyAgentResult;
|
||||
use crate::RUNTIME;
|
||||
use openjarvis_agents::OjAgent;
|
||||
use openjarvis_engine::rig_adapter::RigModelAdapter;
|
||||
use openjarvis_engine::Engine;
|
||||
use pyo3::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Python wrapper for SimpleAgent (type-erased via Box<dyn OjAgent>).
|
||||
type DefaultAdapter = RigModelAdapter<Engine>;
|
||||
|
||||
enum AgentEnum {
|
||||
Simple(openjarvis_agents::SimpleAgent<DefaultAdapter>),
|
||||
Orchestrator(openjarvis_agents::OrchestratorAgent<DefaultAdapter>),
|
||||
NativeReAct(openjarvis_agents::NativeReActAgent<DefaultAdapter>),
|
||||
}
|
||||
|
||||
impl AgentEnum {
|
||||
fn agent_id(&self) -> &str {
|
||||
match self {
|
||||
AgentEnum::Simple(a) => a.agent_id(),
|
||||
AgentEnum::Orchestrator(a) => a.agent_id(),
|
||||
AgentEnum::NativeReAct(a) => a.agent_id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn accepts_tools(&self) -> bool {
|
||||
match self {
|
||||
AgentEnum::Simple(a) => a.accepts_tools(),
|
||||
AgentEnum::Orchestrator(a) => a.accepts_tools(),
|
||||
AgentEnum::NativeReAct(a) => a.accepts_tools(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
input: &str,
|
||||
context: Option<&openjarvis_core::AgentContext>,
|
||||
) -> Result<openjarvis_core::AgentResult, openjarvis_core::OpenJarvisError> {
|
||||
match self {
|
||||
AgentEnum::Simple(a) => a.run(input, context).await,
|
||||
AgentEnum::Orchestrator(a) => a.run(input, context).await,
|
||||
AgentEnum::NativeReAct(a) => a.run(input, context).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_adapter(engine_key: &str, model: &str) -> PyResult<DefaultAdapter> {
|
||||
let config = openjarvis_core::JarvisConfig::default();
|
||||
let engine = openjarvis_engine::get_engine_static(&config, Some(engine_key))
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
|
||||
Ok(RigModelAdapter::new(Arc::new(engine), model.to_string()))
|
||||
}
|
||||
|
||||
#[pyclass(name = "SimpleAgent")]
|
||||
pub struct PySimpleAgent {
|
||||
inner: Box<dyn OjAgent>,
|
||||
inner: AgentEnum,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySimpleAgent {
|
||||
/// Create a SimpleAgent backed by an Engine enum.
|
||||
#[new]
|
||||
#[pyo3(signature = (engine_key="ollama", host="http://localhost:11434", model="qwen3:8b", system_prompt="You are a helpful assistant.", temperature=0.7))]
|
||||
fn new(
|
||||
@@ -28,17 +71,9 @@ impl PySimpleAgent {
|
||||
system_prompt: &str,
|
||||
temperature: f64,
|
||||
) -> PyResult<Self> {
|
||||
let config = openjarvis_core::JarvisConfig::default();
|
||||
let engine = openjarvis_engine::get_engine_static(&config, Some(engine_key))
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
|
||||
let adapter = openjarvis_engine::rig_adapter::RigModelAdapter::new(
|
||||
Arc::new(engine),
|
||||
model.to_string(),
|
||||
);
|
||||
let adapter = make_adapter(engine_key, model)?;
|
||||
let agent = openjarvis_agents::SimpleAgent::new(adapter, system_prompt, temperature);
|
||||
Ok(Self {
|
||||
inner: Box::new(agent),
|
||||
})
|
||||
Ok(Self { inner: AgentEnum::Simple(agent) })
|
||||
}
|
||||
|
||||
fn agent_id(&self) -> &str {
|
||||
@@ -60,10 +95,9 @@ impl PySimpleAgent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Python wrapper for OrchestratorAgent.
|
||||
#[pyclass(name = "OrchestratorAgent")]
|
||||
pub struct PyOrchestratorAgent {
|
||||
inner: Box<dyn OjAgent>,
|
||||
inner: AgentEnum,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -78,24 +112,12 @@ impl PyOrchestratorAgent {
|
||||
max_turns: usize,
|
||||
temperature: f64,
|
||||
) -> PyResult<Self> {
|
||||
let config = openjarvis_core::JarvisConfig::default();
|
||||
let engine = openjarvis_engine::get_engine_static(&config, Some(engine_key))
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
|
||||
let adapter = openjarvis_engine::rig_adapter::RigModelAdapter::new(
|
||||
Arc::new(engine),
|
||||
model.to_string(),
|
||||
);
|
||||
let adapter = make_adapter(engine_key, model)?;
|
||||
let executor = Arc::new(openjarvis_tools::ToolExecutor::new(None, None));
|
||||
let agent = openjarvis_agents::OrchestratorAgent::new(
|
||||
adapter,
|
||||
system_prompt,
|
||||
executor,
|
||||
max_turns,
|
||||
temperature,
|
||||
adapter, system_prompt, executor, max_turns, temperature,
|
||||
);
|
||||
Ok(Self {
|
||||
inner: Box::new(agent),
|
||||
})
|
||||
Ok(Self { inner: AgentEnum::Orchestrator(agent) })
|
||||
}
|
||||
|
||||
fn agent_id(&self) -> &str {
|
||||
@@ -117,10 +139,9 @@ impl PyOrchestratorAgent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Python wrapper for NativeReActAgent.
|
||||
#[pyclass(name = "NativeReActAgent")]
|
||||
pub struct PyNativeReActAgent {
|
||||
inner: Box<dyn OjAgent>,
|
||||
inner: AgentEnum,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -134,23 +155,12 @@ impl PyNativeReActAgent {
|
||||
max_turns: usize,
|
||||
temperature: f64,
|
||||
) -> PyResult<Self> {
|
||||
let config = openjarvis_core::JarvisConfig::default();
|
||||
let engine = openjarvis_engine::get_engine_static(&config, Some(engine_key))
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
|
||||
let adapter = openjarvis_engine::rig_adapter::RigModelAdapter::new(
|
||||
Arc::new(engine),
|
||||
model.to_string(),
|
||||
);
|
||||
let adapter = make_adapter(engine_key, model)?;
|
||||
let executor = Arc::new(openjarvis_tools::ToolExecutor::new(None, None));
|
||||
let agent = openjarvis_agents::NativeReActAgent::new(
|
||||
adapter,
|
||||
executor,
|
||||
max_turns,
|
||||
temperature,
|
||||
adapter, executor, max_turns, temperature,
|
||||
);
|
||||
Ok(Self {
|
||||
inner: Box::new(agent),
|
||||
})
|
||||
Ok(Self { inner: AgentEnum::NativeReAct(agent) })
|
||||
}
|
||||
|
||||
fn agent_id(&self) -> &str {
|
||||
@@ -172,7 +182,6 @@ impl PyNativeReActAgent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Python wrapper for LoopGuard.
|
||||
#[pyclass(name = "LoopGuard")]
|
||||
pub struct PyLoopGuard {
|
||||
inner: openjarvis_agents::LoopGuard,
|
||||
|
||||
@@ -96,3 +96,351 @@ impl PyGRPORouterPolicy {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// --- SFT Router Policy ---
|
||||
|
||||
#[pyclass(name = "SFTRouterPolicy")]
|
||||
pub struct PySFTRouterPolicy {
|
||||
inner: openjarvis_learning::SFTRouterPolicy,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySFTRouterPolicy {
|
||||
#[new]
|
||||
#[pyo3(signature = (min_samples=5))]
|
||||
fn new(min_samples: usize) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::SFTRouterPolicy::new(min_samples),
|
||||
}
|
||||
}
|
||||
|
||||
fn policy_map(&self) -> std::collections::HashMap<String, String> {
|
||||
self.inner.policy_map()
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn classify_query(query: &str) -> &'static str {
|
||||
openjarvis_learning::SFTRouterPolicy::classify_query(query)
|
||||
}
|
||||
|
||||
fn update_from_data(&self, traces_json: &str) -> PyResult<String> {
|
||||
let traces: Vec<(String, String, String, Option<f64>)> =
|
||||
serde_json::from_str(traces_json)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
||||
let result = self.inner.update_from_data(&traces);
|
||||
Ok(serde_json::to_string(&result).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
// --- HeuristicRewardFunction ---
|
||||
|
||||
#[pyclass(name = "HeuristicRewardFunction")]
|
||||
pub struct PyHeuristicRewardFunction {
|
||||
inner: openjarvis_learning::HeuristicRewardFunction,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyHeuristicRewardFunction {
|
||||
#[new]
|
||||
#[pyo3(signature = (weight_latency=0.4, weight_cost=0.3, weight_efficiency=0.3, max_latency=30.0, max_cost=0.01))]
|
||||
fn new(
|
||||
weight_latency: f64,
|
||||
weight_cost: f64,
|
||||
weight_efficiency: f64,
|
||||
max_latency: f64,
|
||||
max_cost: f64,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::HeuristicRewardFunction::new(
|
||||
weight_latency,
|
||||
weight_cost,
|
||||
weight_efficiency,
|
||||
max_latency,
|
||||
max_cost,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn compute(
|
||||
&self,
|
||||
latency_seconds: f64,
|
||||
cost_usd: f64,
|
||||
prompt_tokens: u64,
|
||||
completion_tokens: u64,
|
||||
) -> f64 {
|
||||
self.inner
|
||||
.compute(latency_seconds, cost_usd, prompt_tokens, completion_tokens)
|
||||
}
|
||||
}
|
||||
|
||||
// --- SkillDiscovery ---
|
||||
|
||||
#[pyclass(name = "SkillDiscovery")]
|
||||
pub struct PySkillDiscovery {
|
||||
inner: openjarvis_learning::SkillDiscovery,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySkillDiscovery {
|
||||
#[new]
|
||||
#[pyo3(signature = (min_frequency=3, min_sequence_length=2, max_sequence_length=4, min_outcome=0.5))]
|
||||
fn new(
|
||||
min_frequency: usize,
|
||||
min_sequence_length: usize,
|
||||
max_sequence_length: usize,
|
||||
min_outcome: f64,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::SkillDiscovery::new(
|
||||
min_frequency,
|
||||
min_sequence_length,
|
||||
max_sequence_length,
|
||||
min_outcome,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze(&mut self, traces_json: &str) -> PyResult<String> {
|
||||
let traces: Vec<(Vec<String>, f64, String)> = serde_json::from_str(traces_json)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
||||
self.inner.analyze(&traces);
|
||||
Ok(serde_json::to_string(&self.inner.to_manifests()).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
// --- TraceDrivenPolicy ---
|
||||
|
||||
#[pyclass(name = "TraceDrivenPolicy")]
|
||||
pub struct PyTraceDrivenPolicy {
|
||||
inner: openjarvis_learning::TraceDrivenPolicy,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyTraceDrivenPolicy {
|
||||
#[new]
|
||||
#[pyo3(signature = (available_models=vec![], default_model="", fallback_model=""))]
|
||||
fn new(available_models: Vec<String>, default_model: &str, fallback_model: &str) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::TraceDrivenPolicy::new(
|
||||
available_models,
|
||||
default_model.to_string(),
|
||||
fallback_model.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn select_model(&self, query: &str) -> String {
|
||||
self.inner.select_model(query)
|
||||
}
|
||||
|
||||
fn policy_map(&self) -> std::collections::HashMap<String, String> {
|
||||
self.inner.policy_map()
|
||||
}
|
||||
|
||||
#[pyo3(signature = (query, model, outcome=None, feedback=None))]
|
||||
fn observe(&self, query: &str, model: &str, outcome: Option<String>, feedback: Option<f64>) {
|
||||
self.inner
|
||||
.observe(query, model, outcome.as_deref(), feedback);
|
||||
}
|
||||
}
|
||||
|
||||
// --- AgentAdvisorPolicy ---
|
||||
|
||||
#[pyclass(name = "AgentAdvisorPolicy")]
|
||||
pub struct PyAgentAdvisorPolicy {
|
||||
inner: openjarvis_learning::AgentAdvisorPolicy,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyAgentAdvisorPolicy {
|
||||
#[new]
|
||||
#[pyo3(signature = (max_traces=50))]
|
||||
fn new(max_traces: usize) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::AgentAdvisorPolicy::new(max_traces),
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze_patterns(&self, traces_json: &str) -> PyResult<String> {
|
||||
let traces: Vec<openjarvis_learning::TraceInfo> = serde_json::from_str(traces_json)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
||||
let recs = self.inner.analyze_patterns(&traces);
|
||||
Ok(serde_json::to_string(&recs).unwrap_or_default())
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn classify(query: &str) -> &'static str {
|
||||
openjarvis_learning::AgentAdvisorPolicy::classify(query)
|
||||
}
|
||||
}
|
||||
|
||||
// --- ICLUpdaterPolicy ---
|
||||
|
||||
#[pyclass(name = "ICLUpdaterPolicy")]
|
||||
pub struct PyICLUpdaterPolicy {
|
||||
inner: openjarvis_learning::ICLUpdaterPolicy,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyICLUpdaterPolicy {
|
||||
#[new]
|
||||
#[pyo3(signature = (min_score=0.7, max_examples=20, min_skill_occurrences=3))]
|
||||
fn new(min_score: f64, max_examples: usize, min_skill_occurrences: usize) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::ICLUpdaterPolicy::new(
|
||||
min_score,
|
||||
max_examples,
|
||||
min_skill_occurrences,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_example(&mut self, query: &str, response: &str, outcome: f64) -> bool {
|
||||
self.inner.add_example(
|
||||
query.to_string(),
|
||||
response.to_string(),
|
||||
outcome,
|
||||
std::collections::HashMap::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn rollback(&mut self, version: u32) {
|
||||
self.inner.rollback(version);
|
||||
}
|
||||
|
||||
fn get_examples(&self, query_class: &str, top_k: usize) -> String {
|
||||
serde_json::to_string(&self.inner.get_examples(query_class, top_k)).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn version(&self) -> u32 {
|
||||
self.inner.version()
|
||||
}
|
||||
}
|
||||
|
||||
// --- TrainingDataMiner ---
|
||||
|
||||
#[pyclass(name = "TrainingDataMiner")]
|
||||
pub struct PyTrainingDataMiner {
|
||||
inner: openjarvis_learning::TrainingDataMiner,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyTrainingDataMiner {
|
||||
#[new]
|
||||
#[pyo3(signature = (min_quality=0.7, min_samples_per_class=1))]
|
||||
fn new(min_quality: f64, min_samples_per_class: usize) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::TrainingDataMiner::new(min_quality, min_samples_per_class),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_sft_pairs(&self, traces_json: &str) -> PyResult<String> {
|
||||
let traces: Vec<openjarvis_learning::MinerTraceData> = serde_json::from_str(traces_json)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
||||
let pairs = self.inner.extract_sft_pairs(&traces);
|
||||
Ok(serde_json::to_string(&pairs).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn extract_routing_pairs(&self, traces_json: &str) -> PyResult<String> {
|
||||
let traces: Vec<openjarvis_learning::MinerTraceData> = serde_json::from_str(traces_json)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
||||
let pairs = self.inner.extract_routing_pairs(&traces);
|
||||
Ok(serde_json::to_string(&pairs).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
// --- AgentConfigEvolver ---
|
||||
|
||||
#[pyclass(name = "AgentConfigEvolver")]
|
||||
pub struct PyAgentConfigEvolver {
|
||||
inner: openjarvis_learning::AgentConfigEvolver,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyAgentConfigEvolver {
|
||||
#[new]
|
||||
#[pyo3(signature = (min_quality=0.5))]
|
||||
fn new(min_quality: f64) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::AgentConfigEvolver::new(min_quality),
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze(&self, traces_json: &str) -> PyResult<String> {
|
||||
let traces: Vec<openjarvis_learning::EvolutionTraceData> =
|
||||
serde_json::from_str(traces_json)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
||||
let recs = self.inner.analyze(&traces);
|
||||
Ok(serde_json::to_string(&recs).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
// --- MultiObjectiveReward ---
|
||||
|
||||
#[pyclass(name = "MultiObjectiveReward")]
|
||||
pub struct PyMultiObjectiveReward {
|
||||
inner: openjarvis_learning::MultiObjectiveReward,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyMultiObjectiveReward {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::MultiObjectiveReward::new(
|
||||
openjarvis_learning::RewardWeights::default(),
|
||||
openjarvis_learning::Normalizers::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn compute(&self, episode_json: &str) -> PyResult<f64> {
|
||||
let ep: openjarvis_learning::Episode = serde_json::from_str(episode_json)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
||||
Ok(self.inner.compute(&ep))
|
||||
}
|
||||
}
|
||||
|
||||
// --- LearningOrchestrator ---
|
||||
|
||||
#[pyclass(name = "LearningOrchestrator")]
|
||||
pub struct PyLearningOrchestrator {
|
||||
inner: openjarvis_learning::LearningOrchestrator,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyLearningOrchestrator {
|
||||
#[new]
|
||||
#[pyo3(signature = (min_improvement=0.02, min_sft_pairs=10, min_quality=0.7))]
|
||||
fn new(min_improvement: f64, min_sft_pairs: usize, min_quality: f64) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_learning::LearningOrchestrator::new(
|
||||
min_improvement,
|
||||
min_sft_pairs,
|
||||
min_quality,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[pyo3(signature = (sft_pairs, routing_count, agent_count, recs_count, baseline=None, post=None))]
|
||||
fn evaluate_cycle(
|
||||
&self,
|
||||
sft_pairs: usize,
|
||||
routing_count: usize,
|
||||
agent_count: usize,
|
||||
recs_count: usize,
|
||||
baseline: Option<f64>,
|
||||
post: Option<f64>,
|
||||
) -> String {
|
||||
let result = self.inner.evaluate_cycle(
|
||||
sft_pairs,
|
||||
routing_count,
|
||||
agent_count,
|
||||
recs_count,
|
||||
baseline,
|
||||
post,
|
||||
);
|
||||
serde_json::to_string(&result).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,23 @@ pub(crate) static RUNTIME: Lazy<tokio::runtime::Runtime> = Lazy::new(|| {
|
||||
tokio::runtime::Runtime::new().expect("Failed to create tokio runtime")
|
||||
});
|
||||
|
||||
pub mod a2a;
|
||||
pub mod agents;
|
||||
pub mod core;
|
||||
pub mod engine;
|
||||
pub mod learning;
|
||||
pub mod mcp;
|
||||
pub mod recipes;
|
||||
pub mod scheduler;
|
||||
pub mod security;
|
||||
pub mod sessions;
|
||||
pub mod skills;
|
||||
pub mod storage;
|
||||
pub mod telemetry;
|
||||
pub mod templates;
|
||||
pub mod tools;
|
||||
pub mod traces;
|
||||
pub mod workflow;
|
||||
|
||||
// Module-level functions
|
||||
|
||||
@@ -46,6 +53,16 @@ fn is_sensitive_file(path: &str) -> bool {
|
||||
openjarvis_security::is_sensitive_file(std::path::Path::new(path))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn register_builtin_models() {
|
||||
openjarvis_core::model_catalog::register_builtin_models();
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn classify_query(query: &str) -> &'static str {
|
||||
openjarvis_learning::classify_query(query)
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn openjarvis_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// --- Core types ---
|
||||
@@ -116,15 +133,55 @@ fn openjarvis_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<learning::PyHeuristicRouter>()?;
|
||||
m.add_class::<learning::PyBanditRouterPolicy>()?;
|
||||
m.add_class::<learning::PyGRPORouterPolicy>()?;
|
||||
m.add_class::<learning::PySFTRouterPolicy>()?;
|
||||
m.add_class::<learning::PyHeuristicRewardFunction>()?;
|
||||
m.add_class::<learning::PySkillDiscovery>()?;
|
||||
m.add_class::<learning::PyTraceDrivenPolicy>()?;
|
||||
m.add_class::<learning::PyAgentAdvisorPolicy>()?;
|
||||
m.add_class::<learning::PyICLUpdaterPolicy>()?;
|
||||
m.add_class::<learning::PyTrainingDataMiner>()?;
|
||||
m.add_class::<learning::PyAgentConfigEvolver>()?;
|
||||
m.add_class::<learning::PyMultiObjectiveReward>()?;
|
||||
m.add_class::<learning::PyLearningOrchestrator>()?;
|
||||
|
||||
// --- MCP ---
|
||||
m.add_class::<mcp::PyMcpServer>()?;
|
||||
|
||||
// --- Sessions ---
|
||||
m.add_class::<sessions::PySessionStore>()?;
|
||||
|
||||
// --- Workflow ---
|
||||
m.add_class::<workflow::PyWorkflowGraph>()?;
|
||||
m.add_class::<workflow::PyWorkflowBuilder>()?;
|
||||
|
||||
// --- Skills ---
|
||||
m.add_class::<skills::PySkillManifest>()?;
|
||||
|
||||
// --- Recipes ---
|
||||
m.add_class::<recipes::PyRecipe>()?;
|
||||
|
||||
// --- Templates ---
|
||||
m.add_class::<templates::PyAgentTemplate>()?;
|
||||
|
||||
// --- A2A ---
|
||||
m.add_class::<a2a::PyAgentCard>()?;
|
||||
m.add_class::<a2a::PyA2ATaskStore>()?;
|
||||
|
||||
// --- Scheduler ---
|
||||
m.add_class::<scheduler::PySchedulerStore>()?;
|
||||
|
||||
// --- Module-level functions ---
|
||||
m.add_function(wrap_pyfunction!(load_config, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(detect_hardware, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(check_ssrf, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(is_sensitive_file, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(register_builtin_models, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(classify_query, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(skills::load_skill, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(recipes::load_recipe, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(templates::load_template, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(a2a::parse_a2a_request, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(scheduler::parse_cron_next, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//! PyO3 bindings for composable recipes.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(name = "Recipe")]
|
||||
pub struct PyRecipe {
|
||||
inner: openjarvis_recipes::Recipe,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyRecipe {
|
||||
#[getter]
|
||||
fn name(&self) -> &str {
|
||||
&self.inner.name
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn description(&self) -> Option<&str> {
|
||||
self.inner.description.as_deref()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn model(&self) -> Option<&str> {
|
||||
self.inner.model.as_deref()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn engine_key(&self) -> Option<&str> {
|
||||
self.inner.engine_key.as_deref()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn agent_type(&self) -> Option<&str> {
|
||||
self.inner.agent_type.as_deref()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn max_turns(&self) -> Option<usize> {
|
||||
self.inner.max_turns
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn temperature(&self) -> Option<f64> {
|
||||
self.inner.temperature
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn tools(&self) -> Option<Vec<String>> {
|
||||
self.inner.tools.clone()
|
||||
}
|
||||
|
||||
fn to_builder_kwargs(&self) -> String {
|
||||
let kwargs = self.inner.to_builder_kwargs();
|
||||
serde_json::to_string(&kwargs).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> String {
|
||||
serde_json::to_string(&self.inner).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
pub fn load_recipe(toml_str: &str) -> PyResult<PyRecipe> {
|
||||
let recipe = openjarvis_recipes::load_recipe(toml_str)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?;
|
||||
Ok(PyRecipe { inner: recipe })
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//! PyO3 bindings for task scheduler.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(name = "SchedulerStore", unsendable)]
|
||||
pub struct PySchedulerStore {
|
||||
inner: openjarvis_scheduler::SchedulerStore,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySchedulerStore {
|
||||
#[new]
|
||||
#[pyo3(signature = (db_path=":memory:"))]
|
||||
fn new(db_path: &str) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_scheduler::SchedulerStore::new(db_path),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_task(&self, name: &str, schedule_type: &str, schedule_value: &str) -> PyResult<String> {
|
||||
let st = openjarvis_scheduler::ScheduleType::from_str(schedule_type).ok_or_else(|| {
|
||||
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||
"invalid schedule_type '{}', expected cron/interval/once",
|
||||
schedule_type
|
||||
))
|
||||
})?;
|
||||
let task = self.inner.create_task(name, st, schedule_value);
|
||||
Ok(serde_json::to_string(&task).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn get_task(&self, id: &str) -> Option<String> {
|
||||
self.inner
|
||||
.get_task(id)
|
||||
.map(|t| serde_json::to_string(&t).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn list_tasks(&self) -> String {
|
||||
serde_json::to_string(&self.inner.list_tasks()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn update_status(&self, id: &str, status: &str) -> PyResult<bool> {
|
||||
let s = openjarvis_scheduler::TaskStatus::from_str(status).ok_or_else(|| {
|
||||
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||
"invalid status '{}', expected active/paused/cancelled/completed",
|
||||
status
|
||||
))
|
||||
})?;
|
||||
Ok(self.inner.update_status(id, s))
|
||||
}
|
||||
|
||||
fn record_run(&self, id: &str, timestamp: f64) -> bool {
|
||||
self.inner.record_run(id, timestamp)
|
||||
}
|
||||
|
||||
fn delete_task(&self, id: &str) -> bool {
|
||||
self.inner.delete_task(id)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
pub fn parse_cron_next(expr: &str, after: f64) -> Option<f64> {
|
||||
openjarvis_scheduler::parse_cron_next(expr, after)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//! PyO3 bindings for session management.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(name = "SessionStore", unsendable)]
|
||||
pub struct PySessionStore {
|
||||
inner: openjarvis_sessions::SessionStore,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySessionStore {
|
||||
#[new]
|
||||
#[pyo3(signature = (db_path=":memory:", max_age_hours=24.0, consolidation_threshold=100))]
|
||||
fn new(db_path: &str, max_age_hours: f64, consolidation_threshold: usize) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_sessions::SessionStore::new(
|
||||
db_path,
|
||||
max_age_hours,
|
||||
consolidation_threshold,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create(
|
||||
&self,
|
||||
user_id: &str,
|
||||
channel: &str,
|
||||
channel_user_id: &str,
|
||||
display_name: &str,
|
||||
) -> String {
|
||||
let session = self
|
||||
.inner
|
||||
.get_or_create(user_id, channel, channel_user_id, display_name);
|
||||
serde_json::to_string(&session).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn save_message(
|
||||
&self,
|
||||
session_id: &str,
|
||||
role: &str,
|
||||
content: &str,
|
||||
channel: &str,
|
||||
) -> PyResult<()> {
|
||||
self.inner
|
||||
.save_message(session_id, role, content, channel)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))
|
||||
}
|
||||
|
||||
fn consolidate(&self, session_id: &str) {
|
||||
self.inner.consolidate(session_id);
|
||||
}
|
||||
|
||||
fn link_channel(&self, session_id: &str, channel: &str, channel_user_id: &str) {
|
||||
self.inner.link_channel(session_id, channel, channel_user_id);
|
||||
}
|
||||
|
||||
fn list_sessions(&self, active_only: bool, limit: usize) -> String {
|
||||
let sessions = self.inner.list_sessions(active_only, limit);
|
||||
serde_json::to_string(&sessions).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[pyo3(signature = (max_age_hours=None))]
|
||||
fn decay(&self, max_age_hours: Option<f64>) -> usize {
|
||||
self.inner.decay(max_age_hours)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//! PyO3 bindings for skill manifests and verification.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(name = "SkillManifest")]
|
||||
pub struct PySkillManifest {
|
||||
inner: openjarvis_skills::SkillManifest,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySkillManifest {
|
||||
#[getter]
|
||||
fn name(&self) -> &str {
|
||||
&self.inner.name
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn version(&self) -> &str {
|
||||
&self.inner.version
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn description(&self) -> &str {
|
||||
&self.inner.description
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn author(&self) -> &str {
|
||||
&self.inner.author
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn steps_count(&self) -> usize {
|
||||
self.inner.steps.len()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn required_capabilities(&self) -> Vec<String> {
|
||||
self.inner.required_capabilities.clone()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> String {
|
||||
serde_json::to_string(&self.inner).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn manifest_bytes(&self) -> Vec<u8> {
|
||||
self.inner.manifest_bytes()
|
||||
}
|
||||
|
||||
fn verify_signature(&self, public_key_hex: &str) -> bool {
|
||||
let key_bytes: Vec<u8> = (0..public_key_hex.len())
|
||||
.step_by(2)
|
||||
.filter_map(|i| u8::from_str_radix(&public_key_hex[i..i + 2], 16).ok())
|
||||
.collect();
|
||||
openjarvis_skills::verify_signature(&self.inner, &key_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
pub fn load_skill(toml_str: &str) -> PyResult<PySkillManifest> {
|
||||
let manifest = openjarvis_skills::load_skill(toml_str)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?;
|
||||
Ok(PySkillManifest { inner: manifest })
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//! PyO3 bindings for agent templates.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(name = "AgentTemplate")]
|
||||
pub struct PyAgentTemplate {
|
||||
inner: openjarvis_templates::AgentTemplate,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyAgentTemplate {
|
||||
#[getter]
|
||||
fn name(&self) -> &str {
|
||||
&self.inner.name
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn description(&self) -> &str {
|
||||
&self.inner.description
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn system_prompt(&self) -> &str {
|
||||
&self.inner.system_prompt
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn agent_type(&self) -> &str {
|
||||
&self.inner.agent_type
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn tools(&self) -> Vec<String> {
|
||||
self.inner.tools.clone()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn max_turns(&self) -> usize {
|
||||
self.inner.max_turns
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn temperature(&self) -> f64 {
|
||||
self.inner.temperature
|
||||
}
|
||||
|
||||
fn to_json(&self) -> String {
|
||||
serde_json::to_string(&self.inner).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
pub fn load_template(toml_str: &str) -> PyResult<PyAgentTemplate> {
|
||||
let tpl = openjarvis_templates::load_template(toml_str)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?;
|
||||
Ok(PyAgentTemplate { inner: tpl })
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
//! PyO3 bindings for workflow engine.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[pyclass(name = "WorkflowGraph")]
|
||||
pub struct PyWorkflowGraph {
|
||||
inner: openjarvis_workflow::WorkflowGraph,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyWorkflowGraph {
|
||||
#[new]
|
||||
#[pyo3(signature = (name=""))]
|
||||
fn new(name: &str) -> Self {
|
||||
Self {
|
||||
inner: openjarvis_workflow::WorkflowGraph::new(name),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_node(
|
||||
&mut self,
|
||||
id: &str,
|
||||
node_type: &str,
|
||||
agent: &str,
|
||||
tools: Vec<String>,
|
||||
) -> PyResult<()> {
|
||||
let nt = match node_type {
|
||||
"tool" => openjarvis_workflow::NodeType::Tool,
|
||||
"condition" => openjarvis_workflow::NodeType::Condition,
|
||||
"parallel" => openjarvis_workflow::NodeType::Parallel,
|
||||
"loop" => openjarvis_workflow::NodeType::Loop,
|
||||
"transform" => openjarvis_workflow::NodeType::Transform,
|
||||
_ => openjarvis_workflow::NodeType::Agent,
|
||||
};
|
||||
let node = openjarvis_workflow::WorkflowNode {
|
||||
id: id.to_string(),
|
||||
node_type: nt,
|
||||
agent: agent.to_string(),
|
||||
tools,
|
||||
config: HashMap::new(),
|
||||
condition_expr: String::new(),
|
||||
max_iterations: 10,
|
||||
transform_expr: String::new(),
|
||||
};
|
||||
self.inner
|
||||
.add_node(node)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, source: &str, target: &str) -> PyResult<()> {
|
||||
let edge = openjarvis_workflow::WorkflowEdge {
|
||||
source: source.to_string(),
|
||||
target: target.to_string(),
|
||||
condition: String::new(),
|
||||
};
|
||||
self.inner
|
||||
.add_edge(edge)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))
|
||||
}
|
||||
|
||||
fn validate(&self) -> (bool, String) {
|
||||
self.inner.validate()
|
||||
}
|
||||
|
||||
fn topological_sort(&self) -> PyResult<Vec<String>> {
|
||||
self.inner
|
||||
.topological_sort()
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))
|
||||
}
|
||||
|
||||
fn execution_stages(&self) -> Vec<Vec<String>> {
|
||||
self.inner.execution_stages()
|
||||
}
|
||||
|
||||
fn predecessors(&self, node_id: &str) -> Vec<String> {
|
||||
self.inner.predecessors(node_id)
|
||||
}
|
||||
|
||||
fn successors(&self, node_id: &str) -> Vec<String> {
|
||||
self.inner.successors(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "WorkflowBuilder")]
|
||||
pub struct PyWorkflowBuilder {
|
||||
inner: Option<openjarvis_workflow::WorkflowBuilder>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyWorkflowBuilder {
|
||||
#[new]
|
||||
#[pyo3(signature = (name=""))]
|
||||
fn new(name: &str) -> Self {
|
||||
Self {
|
||||
inner: Some(openjarvis_workflow::WorkflowBuilder::new(name)),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_agent_node(&mut self, id: &str, agent: &str) {
|
||||
if let Some(ref mut b) = self.inner {
|
||||
b.add_agent_node(id, agent);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_tool_node(&mut self, id: &str, tools: Vec<String>) {
|
||||
if let Some(ref mut b) = self.inner {
|
||||
b.add_tool_node(id, tools);
|
||||
}
|
||||
}
|
||||
|
||||
fn connect(&mut self, source: &str, target: &str) {
|
||||
if let Some(ref mut b) = self.inner {
|
||||
b.connect(source, target);
|
||||
}
|
||||
}
|
||||
|
||||
fn build(&mut self) -> PyResult<PyWorkflowGraph> {
|
||||
let builder = self.inner.take().ok_or_else(|| {
|
||||
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("builder already consumed")
|
||||
})?;
|
||||
let graph = builder
|
||||
.build()
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?;
|
||||
Ok(PyWorkflowGraph { inner: graph })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user