Skip to content

Commit 479d40e

Browse files
Merge pull request #51 from gitcoder89431/core-feature-cloud-02
Core feature cloud 02
2 parents df81f74 + 60a8ca9 commit 479d40e

File tree

17 files changed

+364
-73
lines changed

17 files changed

+364
-73
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ futures = "0.3.30"
3232
signal-hook = "0.3.17"
3333
textwrap = "0.16.1"
3434
unicode-width = "0.1.13"
35+
chrono = { version = "0.4.38", features = ["serde"] }
3536

3637
[workspace.lints.rust]
3738
dead_code = "warn"

crates/agentic-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ reqwest = { workspace = true }
1212
tokio = { workspace = true }
1313
anyhow = { workspace = true }
1414
serde_json = { workspace = true }
15+
thiserror.workspace = true

crates/agentic-core/src/cloud.rs

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,61 @@
1+
use crate::models::AtomicNote;
12
use reqwest::Client;
23
use serde::{Deserialize, Serialize};
34
use std::time::Duration;
5+
use thiserror::Error;
6+
7+
#[derive(Debug, Error)]
8+
pub enum CloudError {
9+
#[error("The cloud provider rejected the API key. It might have expired or been disabled.")]
10+
ApiKey,
11+
12+
#[error("The cloud model returned a response that could not be understood.")]
13+
ParseError,
14+
15+
#[error("The cloud provider returned an unexpected error: {status}: {text}")]
16+
ApiError { status: u16, text: String },
17+
18+
#[error(transparent)]
19+
RequestError(#[from] reqwest::Error),
20+
}
21+
22+
const SYNTHESIZER_PROMPT: &str = r#"You are an expert-level AI Synthesizer. Your task is to answer the user's prompt by generating a concise, "atomic note" of knowledge.
23+
24+
CRITICAL OUTPUT CONSTRAINTS:
25+
26+
Header (Metadata): You MUST generate a set of 3-5 semantic keywords or tags that capture the absolute essence of the topic. These tags are for a knowledge graph.
27+
28+
Body (Content): The main response MUST be a maximum of four (4) sentences. It must be a dense, self-contained summary of the most critical information.
29+
30+
OUTPUT FORMAT (JSON):
31+
Your final output MUST be a single, valid JSON object with two keys: header_tags and body_text.
32+
33+
{
34+
"header_tags": ["keyword1", "keyword2", "keyword3"],
35+
"body_text": "Your concise, 3-4 sentence summary goes here."
36+
}
37+
38+
USER PROMPT:
39+
{prompt}
40+
"#;
41+
42+
#[derive(Serialize)]
43+
struct ResponseFormat {
44+
r#type: String,
45+
}
446

547
#[derive(Serialize)]
6-
struct OpenRouterRequest {
48+
struct OpenRouterRequest<'a> {
749
model: String,
8-
messages: Vec<ChatMessage>,
50+
messages: Vec<ChatMessage<'a>>,
951
max_tokens: u32,
52+
response_format: ResponseFormat,
1053
}
1154

1255
#[derive(Serialize)]
13-
struct ChatMessage {
56+
struct ChatMessage<'a> {
1457
role: String,
15-
content: String,
58+
content: &'a str,
1659
}
1760

1861
#[derive(Deserialize)]
@@ -34,22 +77,21 @@ pub async fn call_cloud_model(
3477
api_key: &str,
3578
model: &str,
3679
prompt: &str,
37-
) -> Result<String, anyhow::Error> {
80+
) -> Result<AtomicNote, CloudError> {
3881
let client = Client::builder().timeout(Duration::from_secs(30)).build()?;
3982

40-
// Optimize prompt for concise responses
41-
let optimized_prompt = format!(
42-
"Please provide a concise, well-structured response to this inquiry. Keep it informative but focused:\n\n{}",
43-
prompt
44-
);
83+
let synthesizer_prompt = SYNTHESIZER_PROMPT.replace("{prompt}", prompt);
4584

4685
let request_body = OpenRouterRequest {
4786
model: model.to_string(),
4887
messages: vec![ChatMessage {
4988
role: "user".to_string(),
50-
content: optimized_prompt,
89+
content: &synthesizer_prompt,
5190
}],
52-
max_tokens: 1024, // Reduced from 2048 for more concise responses
91+
max_tokens: 1024,
92+
response_format: ResponseFormat {
93+
r#type: "json_object".to_string(),
94+
},
5395
};
5496

5597
let response = client
@@ -62,19 +104,29 @@ pub async fn call_cloud_model(
62104

63105
if !response.status().is_success() {
64106
let status = response.status();
107+
if status == 401 {
108+
return Err(CloudError::ApiKey);
109+
}
65110
let error_text = response.text().await.unwrap_or_default();
66-
return Err(anyhow::anyhow!(
67-
"OpenRouter API error {}: {}",
68-
status,
69-
error_text
70-
));
111+
return Err(CloudError::ApiError {
112+
status: status.as_u16(),
113+
text: error_text,
114+
});
71115
}
72116

73-
let openrouter_response: OpenRouterResponse = response.json().await?;
117+
let openrouter_response: OpenRouterResponse = match response.json().await {
118+
Ok(res) => res,
119+
Err(_) => return Err(CloudError::ParseError),
120+
};
74121

75-
if let Some(choice) = openrouter_response.choices.first() {
76-
Ok(choice.message.content.clone())
77-
} else {
78-
Err(anyhow::anyhow!("No response choices from OpenRouter API"))
79-
}
122+
let message_content = openrouter_response
123+
.choices
124+
.first()
125+
.map(|choice| &choice.message.content)
126+
.ok_or(CloudError::ParseError)?;
127+
128+
let atomic_note: AtomicNote =
129+
serde_json::from_str(message_content).map_err(|_| CloudError::ParseError)?;
130+
131+
Ok(atomic_note)
80132
}

crates/agentic-core/src/models.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ use serde::{Deserialize, Serialize};
44
use serde_json::Value;
55
use std::time::Duration;
66

7+
#[derive(Debug, Clone, Serialize, Deserialize)]
8+
pub struct AtomicNote {
9+
pub header_tags: Vec<String>,
10+
pub body_text: String,
11+
}
12+
713
#[derive(Debug, Clone, Serialize, Deserialize)]
814
pub struct OllamaModel {
915
pub name: String,

crates/agentic-core/src/orchestrator.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@ use serde::Deserialize;
33

44
const ORCHESTRATOR_PROMPT: &str = r#"You are Ruixen, an inquisitive AI partner.
55
6-
**Your Task:**
7-
Generate 3 concise proposals about this query: "{query}"
6+
**CRITICAL INSTRUCTION:**
7+
You MUST generate EXACTLY 3 proposals about this query: "{query}"
88
9-
Each proposal must have TWO parts separated by a dash:
10-
1. A brief context statement (1-2 sentences max)
11-
2. A curious question starting with "I wonder" or "I'm wondering"
9+
**MANDATORY FORMAT FOR EACH PROPOSAL:**
10+
[Context statement] - I wonder [question]?
1211
13-
Keep each proposal under 3 lines when displayed. Be thoughtful but concise.
12+
**RULES - NO EXCEPTIONS:**
13+
1. EVERY proposal MUST have a brief context (1-2 sentences) followed by " - I wonder"
14+
2. EVERY proposal MUST end with a question starting with "I wonder" or "I'm wondering"
15+
3. NO proposals should be just statements or just questions
16+
4. ALWAYS use the exact format: "Context - I wonder/I'm wondering [question]?"
1417
15-
**Format:** Brief context - I wonder question?
18+
**EXAMPLE OF CORRECT FORMAT:**
19+
"Philosophy has debated this for centuries - I wonder what new perspectives we might discover?"
1620
17-
**Output Format:**
21+
**Your EXACT output must be valid JSON:**
1822
{
1923
"proposals": [
20-
"Brief context about the topic - I wonder about this specific aspect?",
21-
"Another brief context - I'm wondering if this related thing?",
22-
"Third brief context - I wonder about this other angle?"
24+
"Brief context statement - I wonder about this specific aspect?",
25+
"Another context statement - I'm wondering if this could be true?",
26+
"Third context statement - I wonder about this different angle?"
2327
]
2428
}
2529
"#;

crates/agentic-tui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ ratatui = { workspace = true }
1515
tokio = { workspace = true }
1616
tracing = { workspace = true }
1717
tracing-subscriber = { workspace = true }
18+
chrono = { workspace = true }

0 commit comments

Comments
 (0)