Skip to content

Commit 414047b

Browse files
committed
Bump version to 0.1.4 for agentic-core and ruixen; enhance error handling and JSON parsing strategies
1 parent 6e1ff71 commit 414047b

File tree

9 files changed

+339
-68
lines changed

9 files changed

+339
-68
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/agentic-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "agentic-core"
3-
version = "0.1.3"
3+
version = "0.1.4"
44
edition = "2021"
55
description = "Core AI orchestration library for Ruixen - the collaborative AI agent framework"
66
license = "MIT"

crates/agentic-core/src/cloud.rs

Lines changed: 127 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ pub async fn call_cloud_model(
8282

8383
let synthesizer_prompt = SYNTHESIZER_PROMPT.replace("{prompt}", prompt);
8484

85+
// Debug: Write the synthesis prompt to see what we're sending
86+
std::fs::write("/tmp/debug_synthesis_prompt.txt", &synthesizer_prompt).ok();
87+
8588
let request_body = OpenRouterRequest {
8689
model: model.to_string(),
8790
messages: vec![ChatMessage {
@@ -114,9 +117,21 @@ pub async fn call_cloud_model(
114117
});
115118
}
116119

117-
let openrouter_response: OpenRouterResponse = match response.json().await {
120+
let response_text = response.text().await?;
121+
122+
// Debug: Write the raw cloud response to see what we got back
123+
std::fs::write("/tmp/debug_cloud_response.txt", &response_text).ok();
124+
125+
let openrouter_response: OpenRouterResponse = match serde_json::from_str(&response_text) {
118126
Ok(res) => res,
119-
Err(_) => return Err(CloudError::ParseError),
127+
Err(e) => {
128+
let debug_info = format!(
129+
"Cloud API Response Parse Error: {}\nRaw Response: {}",
130+
e, response_text
131+
);
132+
std::fs::write("/tmp/debug_cloud_api_error.txt", &debug_info).ok();
133+
return Err(CloudError::ParseError);
134+
}
120135
};
121136

122137
let message_content = openrouter_response
@@ -125,25 +140,119 @@ pub async fn call_cloud_model(
125140
.map(|choice| &choice.message.content)
126141
.ok_or(CloudError::ParseError)?;
127142

128-
// Extract JSON from markdown code blocks if present
129-
let clean_content = if message_content.contains("```json") {
130-
// Extract content between ```json and ```
131-
if let Some(json_start) = message_content.find("```json") {
132-
let after_start = &message_content[json_start + 7..]; // Skip "```json"
143+
// Try multiple parsing strategies for cloud model response
144+
parse_atomic_note_with_fallbacks(message_content)
145+
}
146+
147+
fn parse_atomic_note_with_fallbacks(message_content: &str) -> Result<AtomicNote, CloudError> {
148+
// Strategy 1: Extract from markdown code blocks
149+
let clean_content = extract_json_from_cloud_markdown(message_content);
150+
151+
// Debug: Write the cleaned content we're trying to parse
152+
std::fs::write("/tmp/debug_synthesis_json.txt", clean_content).ok();
153+
154+
// Strategy 2: Try direct JSON parsing
155+
if let Ok(note) = serde_json::from_str::<AtomicNote>(clean_content) {
156+
return Ok(note);
157+
}
158+
159+
// Strategy 3: Try to find just the JSON object
160+
if let Some(json_start) = clean_content.find("{") {
161+
let json_str = &clean_content[json_start..];
162+
if let Some(json_end) = json_str.rfind("}") {
163+
let json_only = &json_str[..=json_end];
164+
if let Ok(note) = serde_json::from_str::<AtomicNote>(json_only) {
165+
return Ok(note);
166+
}
167+
}
168+
}
169+
170+
// Strategy 4: Try to manually extract header_tags and body_text
171+
if let Some(note) = try_extract_atomic_note_fields(message_content) {
172+
return Ok(note);
173+
}
174+
175+
// All strategies failed - write comprehensive debug info
176+
let debug_info = format!(
177+
"All cloud synthesis parsing strategies failed\nRaw Message: {}\nCleaned Content: {}",
178+
message_content, clean_content
179+
);
180+
std::fs::write("/tmp/debug_synthesis_parse_failure.txt", &debug_info).ok();
181+
182+
Err(CloudError::ParseError)
183+
}
184+
185+
fn extract_json_from_cloud_markdown(content: &str) -> &str {
186+
// Try different markdown formats
187+
if content.contains("```json") {
188+
if let Some(json_start) = content.find("```json") {
189+
let after_start = &content[json_start + 7..];
133190
if let Some(json_end) = after_start.find("```") {
134-
after_start[..json_end].trim()
135-
} else {
136-
message_content
191+
return after_start[..json_end].trim();
137192
}
138-
} else {
139-
message_content
140193
}
141-
} else {
142-
message_content
143-
};
194+
}
195+
196+
// Try just ```
197+
if content.contains("```") {
198+
if let Some(first_tick) = content.find("```") {
199+
let after_first = &content[first_tick + 3..];
200+
if let Some(second_tick) = after_first.find("```") {
201+
let content = after_first[..second_tick].trim();
202+
// Skip the language identifier line if present
203+
if let Some(newline) = content.find('\n') {
204+
let potential_json = content[newline..].trim();
205+
if potential_json.starts_with('{') {
206+
return potential_json;
207+
}
208+
}
209+
return content;
210+
}
211+
}
212+
}
144213

145-
let atomic_note: AtomicNote =
146-
serde_json::from_str(clean_content).map_err(|_| CloudError::ParseError)?;
214+
content
215+
}
147216

148-
Ok(atomic_note)
217+
fn try_extract_atomic_note_fields(content: &str) -> Option<AtomicNote> {
218+
let mut header_tags = Vec::new();
219+
let mut body_text = String::new();
220+
221+
// Look for header_tags patterns
222+
if let Some(tags_start) = content.find("\"header_tags\"") {
223+
let after_tags = &content[tags_start..];
224+
if let Some(array_start) = after_tags.find('[') {
225+
if let Some(array_end) = after_tags.find(']') {
226+
let array_content = &after_tags[array_start + 1..array_end];
227+
// Simple parsing of comma-separated quoted strings
228+
for tag in array_content.split(',') {
229+
let cleaned_tag = tag.trim().trim_matches('"').trim();
230+
if !cleaned_tag.is_empty() {
231+
header_tags.push(cleaned_tag.to_string());
232+
}
233+
}
234+
}
235+
}
236+
}
237+
238+
// Look for body_text patterns
239+
if let Some(body_start) = content.find("\"body_text\"") {
240+
let after_body = &content[body_start..];
241+
if let Some(quote_start) = after_body.find('"') {
242+
let after_quote = &after_body[quote_start + 1..];
243+
if let Some(quote_end) = after_quote.find('"') {
244+
body_text = after_quote[..quote_end].to_string();
245+
}
246+
}
247+
}
248+
249+
// Only return if we found both fields with reasonable content
250+
if !header_tags.is_empty() && !body_text.is_empty() && body_text.len() > 10 {
251+
Some(AtomicNote {
252+
header_tags,
253+
body_text,
254+
})
255+
} else {
256+
None
257+
}
149258
}

crates/agentic-core/src/models.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@ use anyhow::Result;
22
use reqwest::Client;
33
use serde::{Deserialize, Serialize};
44
use serde_json::Value;
5+
use std::collections::HashMap;
6+
use std::sync::{Arc, Mutex, OnceLock};
57
use std::time::Duration;
68

9+
// Global provider cache to ensure consistency across different ModelValidator instances
10+
static PROVIDER_CACHE: OnceLock<Arc<Mutex<HashMap<String, LocalProvider>>>> = OnceLock::new();
11+
12+
fn get_global_provider_cache() -> &'static Arc<Mutex<HashMap<String, LocalProvider>>> {
13+
PROVIDER_CACHE.get_or_init(|| Arc::new(Mutex::new(HashMap::new())))
14+
}
15+
716
#[derive(Debug, Clone, Serialize, Deserialize)]
817
pub struct AtomicNote {
918
pub header_tags: Vec<String>,
@@ -109,6 +118,26 @@ impl ModelValidator {
109118
}
110119

111120
pub async fn detect_provider_type(&self, endpoint: &str) -> LocalProvider {
121+
// Check global cache first
122+
let cache = get_global_provider_cache();
123+
if let Ok(cache_lock) = cache.lock() {
124+
if let Some(cached_provider) = cache_lock.get(endpoint) {
125+
return cached_provider.clone();
126+
}
127+
}
128+
129+
// Perform actual detection
130+
let detected_provider = self.perform_provider_detection(endpoint).await;
131+
132+
// Cache the result globally
133+
if let Ok(mut cache_lock) = cache.lock() {
134+
cache_lock.insert(endpoint.to_string(), detected_provider.clone());
135+
}
136+
137+
detected_provider
138+
}
139+
140+
async fn perform_provider_detection(&self, endpoint: &str) -> LocalProvider {
112141
// Try OpenAI/LM Studio API first for port 1234
113142
if endpoint.to_lowercase().contains("1234")
114143
&& self.test_openai_endpoint(endpoint).await.is_ok()

0 commit comments

Comments
 (0)