diff --git a/fluent_cli/config.json b/fluent_cli/config.json index e3b1295..ba9c239 100644 --- a/fluent_cli/config.json +++ b/fluent_cli/config.json @@ -41,8 +41,8 @@ "name": "LocalGPT4SupervisorWorkerFlow", "engine": "flowise", "protocol": "https", - "hostname": "flowise.fluentcli.com", - "port": 443, + "hostname": "127.0.0.1", + "port": 3000, "chat_id": "57ead6df-627f-45c9-948a-9bc2b19e6a2e", "request_path": "/api/v1/prediction/", "sessionId": "AMBER_FLUENT_SESSION_ID_01", @@ -64,6 +64,30 @@ }, "timeout_ms": 50000 }, + { + "name": "LocalGPT4SupervisorWorkerBrowserAndGithubFlow", + "engine": "flowise", + "protocol": "http", + "hostname": "127.0.0.1", + "port": 3000, + "chat_id": "d6c83c82-3423-4ee9-bb12-152cbdcb8a7c", + "request_path": "/api/v1/prediction/", + "sessionId": "AMBER_FLUENT_SESSION_ID_01", + "bearer_token": "AMBER_REPO_CLOUD_FLUENT_DEMO_KEY", + "overrideConfig": { + + "modelName": { + "chatOpenAICustom_0": "gpt-4o" + }, + "openAIApiKey": "AMBER_FLUENT_OPENAI_API_KEY_01", + + "temperature": 0 + }, + "tweaks": { + + }, + "timeout_ms": 500000 + }, { "name": "GPT4SupervisorWorkerBrowserAndGitHubFlowRepoCloud", "engine": "flowise", @@ -294,38 +318,53 @@ "hostname": "njfio-langflow-preview.hf.space", "port": 443, "request_path": "/api/v1/run/", - "chat_id": "7455a484-1e7d-46c9-8523-a8b1350d9784", + "chat_id": "b602b03e-4e32-4963-8e05-838f588a0b87", "bearer_token": "AMBER_REPO_CLOUD_FLUENT_DEMO_KEY", "input_value_key": "input_value", "sessionId": "", "output_type": "chat", "input_type": "chat", "overrideConfig": { - }, "tweaks": { - "Prompt-sU980": {}, - "URL-m0Ust": { + "Prompt-gn4sP": { + "template": "Reference 1:\n\n{reference_1}\n\n---\n\nReference 2:\n\n{reference_2}\n\n---\n\n{instructions}\n\nBlog: \n\n\n", + "reference_1": "", + "reference_2": "", + "instructions": "" + }, + "URL-TDZNk": { "urls": [ - "" + "https://www.promptingguide.ai/techniques/prompt_chaining" ] }, - "ChatOutput-804eN": { - "system_message": "You are a helpful assistant" + "ChatOutput-0RasW": { + "record_template": "{text}", + "return_record": false, + "sender": "Machine", + "sender_name": "AI" }, - "OpenAIModel-xHBgn": { - "model_kwargs": {} + "OpenAIModel-EO6fM": { + "openai_api_key": "AMBER_FLUENT_OPENAI_API_KEY_01", + "max_tokens": 1024, + "model_kwargs": "{}", + "model_name": "gpt-3.5-turbo-0125", + "stream": true, + "temperature": 0.1 }, - "URL-cJhLd": { + "URL-rJpsL": { "urls": [ - "" + "https://www.promptingguide.ai/introduction/basics" ] }, - "TextInput-eohW7": { + "TextInput-iwhDe": { + "input_value": "Use the references above for style to write a new blog/tutorial about prompt engineering techniques. Suggest non-covered topics.", "record_template": "" } }, "timeout_ms": 50000 + + }, { "name": "LangFlowChainOfThoughtExample", @@ -1178,8 +1217,101 @@ }, "tweaks": { + }, + "timeout_ms": 5000000 + }, + + { + "name": "N8NDalleImagePostTest", + "engine": "webhook", + "protocol": "https", + "hostname": "kvnnmo99-n8n.myjfmo.easypanel.host", + "port": 443, + "chat_id": "cdf12254-e7c0-49fb-91a9-00a3995d70cd", + "request_path": "/webhook-test/", + "sessionId": "", + "bearer_token": "AMBER_MAKE_LEONARDO_IMAGE_POST", + "overrideConfig": { + "size": "1024x1792", + "quality":"HD", + "style": "Vivid", + "responseFormat": "url", + "makeAuthentication": "AMBER_MAKE_LEONARDO_IMAGE_POST" + + }, + "tweaks": { + + }, + "timeout_ms": 5000000 + }, + { + "name": "N8NDalleImagePost", + "engine": "webhook", + "protocol": "https", + "hostname": "kvnnmo99-n8n.myjfmo.easypanel.host", + "port": 443, + "chat_id": "cdf12254-e7c0-49fb-91a9-00a3995d70cd", + "request_path": "/webhook/", + "sessionId": "", + "bearer_token": "AMBER_MAKE_LEONARDO_IMAGE_POST", + "overrideConfig": { + "size": "1024x1792", + "quality":"HD", + "style": "Vivid", + "responseFormat": "url", + "makeAuthentication": "AMBER_MAKE_LEONARDO_IMAGE_POST" + + }, + "tweaks": { + + }, + "timeout_ms": 5000000 + }, + { + "name": "N8NChatFlow", + "engine": "webhook", + "protocol": "https", + "hostname": "kvnnmo99-n8n.myjfmo.easypanel.host", + "port": 443, + "chat_id": "8f0d0b79-9359-427f-93c0-fa1b8824eba5", + "request_path": "/webhook/", + "sessionId": "", + "bearer_token": "AMBER_MAKE_LEONARDO_IMAGE_POST", + "overrideConfig": { + "size": "1024x1792", + "quality":"HD", + "style": "Vivid", + "responseFormat": "url", + "makeAuthentication": "AMBER_MAKE_LEONARDO_IMAGE_POST" + + }, + "tweaks": { + + }, + "timeout_ms": 5000000 + }, + { + "name": "TESTN8NChatFlow", + "engine": "webhook", + "protocol": "https", + "hostname": "kvnnmo99-n8n.myjfmo.easypanel.host", + "port": 443, + "chat_id": "8f0d0b79-9359-427f-93c0-fa1b8824eba5", + "request_path": "/webhook-test/", + "sessionId": "", + "bearer_token": "AMBER_MAKE_LEONARDO_IMAGE_POST", + "overrideConfig": { + "size": "1024x1792", + "quality":"HD", + "style": "Vivid", + "responseFormat": "url", + "makeAuthentication": "AMBER_MAKE_LEONARDO_IMAGE_POST" + + }, + "tweaks": { + }, "timeout_ms": 5000000 } - + ] diff --git a/fluent_cli/src/client.rs b/fluent_cli/src/client.rs index b2af224..c928d26 100644 --- a/fluent_cli/src/client.rs +++ b/fluent_cli/src/client.rs @@ -218,41 +218,48 @@ pub async fn handle_langflow_response(response_body: &str, matches: &clap::ArgMa } pub async fn handle_response(response_body: &str, matches: &clap::ArgMatches) -> Result<()> { - // Parse the response body, handle error properly here instead of unwrapping debug!("Response body: {}", response_body); - let result = serde_json::from_str::(response_body); + + // Parse the response body as a generic JSON value + let result: Result = serde_json::from_str(response_body); debug!("Result: {:?}", result); - // If there's an error parsing the JSON, print the error and the raw response body match result { Ok(parsed_output) => { // If parsing is successful, use the parsed data debug!("Parsed Output: {:?}", parsed_output); - // Print agent reasoning details if present - if let Some(agent_reasoning) = &parsed_output.agent_reasoning { + // Extract text field if available + if let Some(text) = parsed_output.get("text").and_then(Value::as_str) { + println!("{}", text); + } + + // Extract agent reasoning details if present + if let Some(agent_reasoning) = parsed_output.get("agentReasoning").and_then(Value::as_array) { eprintln!("\nAgent Reasoning Details:"); for agent in agent_reasoning { - eprintln!("Agent Name: {}", agent.agent_name); - if !agent.messages.is_empty() { + if let Some(agent_name) = agent.get("agentName").and_then(Value::as_str) { + eprintln!("Agent Name: {}", agent_name); + } + if let Some(messages) = agent.get("messages").and_then(Value::as_array) { eprintln!("Messages:"); - for message in &agent.messages { - eprintln!("- {}", message); + for message in messages { + if let Some(msg) = message.as_str() { + eprintln!("- {}", msg); + } } } - if let Some(next) = &agent.next { + if let Some(next) = agent.get("next").and_then(Value::as_str) { eprintln!("Next Step: {}", next); } - if let Some(instructions) = &agent.instructions { + if let Some(instructions) = agent.get("instructions").and_then(Value::as_str) { eprintln!("Instructions: {}", instructions); } - if let Some(used_tools) = &agent.used_tools { - if !used_tools.is_empty() { - eprintln!("Used Tools:"); - for tool in used_tools { - if let Some(tool_name) = tool { - eprintln!("- {}", tool_name); - } + if let Some(used_tools) = agent.get("usedTools").and_then(Value::as_array) { + eprintln!("Used Tools:"); + for tool in used_tools { + if let Some(tool_name) = tool.as_str() { + eprintln!("- {}", tool_name); } } } @@ -268,25 +275,47 @@ pub async fn handle_response(response_body: &str, matches: &clap::ArgMatches) -> if matches.get_one::("markdown-output").map_or(true, |&v| v) && !matches.get_one::("parse-code-output").map_or(false, |&v| v) && !matches.get_one::("full-output").map_or(false, |&v| v) { - pretty_format_markdown(&parsed_output.text); - if let Some(documents) = &parsed_output.source_documents { + if let Some(text) = parsed_output.get("text").and_then(Value::as_str) { + pretty_format_markdown(text); + } + if let Some(documents) = parsed_output.get("sourceDocuments").and_then(Value::as_array) { pretty_format_markdown("\n---\n"); pretty_format_markdown("\n\n# Source Documents\n"); pretty_format_markdown("\n---\n"); for doc_option in documents { - if let Some(doc) = doc_option { + if let Some(doc) = doc_option.as_object() { let markdown_link = format!( "[View Source]({}/blob/{}/{}#L{}-L{})", - doc.metadata.as_ref().unwrap().repository.as_ref().unwrap_or(&"".to_string()), - doc.metadata.as_ref().unwrap().branch, - doc.metadata.as_ref().unwrap().source, - doc.metadata.as_ref().unwrap().loc.lines.from, - doc.metadata.as_ref().unwrap().loc.lines.to + doc.get("metadata") + .and_then(|meta| meta.get("repository")) + .and_then(Value::as_str) + .unwrap_or(""), + doc.get("metadata") + .and_then(|meta| meta.get("branch")) + .and_then(Value::as_str) + .unwrap_or(""), + doc.get("metadata") + .and_then(|meta| meta.get("source")) + .and_then(Value::as_str) + .unwrap_or(""), + doc.get("metadata") + .and_then(|meta| meta.get("loc")) + .and_then(|loc| loc.get("lines")) + .and_then(|lines| lines.get("from")) + .and_then(Value::as_i64) + .unwrap_or(0), + doc.get("metadata") + .and_then(|meta| meta.get("loc")) + .and_then(|loc| loc.get("lines")) + .and_then(|lines| lines.get("to")) + .and_then(Value::as_i64) + .unwrap_or(0) ); pretty_format_markdown(&markdown_link); - match &doc.page_content { - Some(content) if !content.is_empty() => pretty_format_markdown(&format!("**Page Content:**\n{}", content)), - _ => pretty_format_markdown("**Page Content:**\nNo content available"), + if let Some(content) = doc.get("page_content").and_then(Value::as_str) { + pretty_format_markdown(&format!("**Page Content:**\n{}", content)); + } else { + pretty_format_markdown("**Page Content:**\nNo content available"); } } } @@ -298,9 +327,11 @@ pub async fn handle_response(response_body: &str, matches: &clap::ArgMatches) -> matches.get_one::("parse-code-output").map_or(true, |&v| v) && !matches.get_one::("full-output").map_or(false, |&v| v) { debug!("parse code"); - let code_blocks = extract_code_blocks(&parsed_output.text); - for block in code_blocks { - println!("{}", block); + if let Some(text) = parsed_output.get("text").and_then(Value::as_str) { + let code_blocks = extract_code_blocks(text); + for block in code_blocks { + println!("{}", block); + } } } @@ -311,22 +342,24 @@ pub async fn handle_response(response_body: &str, matches: &clap::ArgMatches) -> println!("{}", response_body); } - if !matches.get_one::("markdown-output").map_or(false, |&v| v) && + if !matches.get_one::("markdown-output").map_or(false, |&v| v) && !matches.get_one::("parse-code-output").map_or(false, |&v| v) && !matches.get_one::("full-output").map_or(false, |&v| v) { debug!("default"); - println!("{}", &parsed_output.text); + if let Some(text) = parsed_output.get("text").and_then(Value::as_str) { + println!("{}", text); + } } - }, Err(e) => { // If there's an error parsing the JSON, print the error and the raw response body - eprintln!("Failed to parse JSON: {}", e); + eprintln!("Failed to parse JSON, this might be normal if it's a webhook request: {}", e); if let Some(cause) = e.source() { eprintln!("Cause: {:?}", cause); } if let Some(directory) = matches.get_one::("download-media").map(|s| s.as_str()) { - let urls = extract_urls(response_body); // Assume extract_urls can handle any text + let urls = extract_urls(response_body); + // Assume extract_urls can handle any text debug!("Extracted URLs: {:?}", urls); download_media(urls, directory).await; } @@ -557,8 +590,13 @@ use termimad::{MadSkin}; use termimad::crossterm::style::Stylize; use base64::{engine::general_purpose::STANDARD, Engine}; - -pub async fn prepare_payload(flow: &FlowConfig, question: &str, file_path: Option<&str>, actual_final_context: Option, cli_args: &ArgMatches, _file_contents: &str, +pub async fn prepare_payload( + flow: &FlowConfig, + question: &str, + file_path: Option<&str>, + actual_final_context: Option, + cli_args: &ArgMatches, + _file_contents: &str, ) -> IoResult { let mut override_config = flow.override_config.clone(); let mut tweaks_config = flow.tweaks.clone(); @@ -577,7 +615,6 @@ pub async fn prepare_payload(flow: &FlowConfig, question: &str, file_path: Optio debug!("Engine: {}", flow.engine); let mut body = match flow.engine.as_str() { "flowise" => { - let system_prompt_inline = cli_args.get_one::("system-prompt-override-inline").map(|s| s.as_str()); let system_prompt_file = cli_args.get_one::("system-prompt-override-file").map(|s| s.as_str()); // Load override value from file if specified @@ -612,15 +649,26 @@ pub async fn prepare_payload(flow: &FlowConfig, question: &str, file_path: Optio }, "langflow" => { debug!("Langflow Engine"); - let mut tweaks_config = flow.tweaks.clone(); - debug!("Tweaks config before update: {:?}", tweaks_config); - replace_with_env_var(&mut tweaks_config); + + // Get the input value key from the flow configuration + if let Some(input_value_key) = flow.input_value_key.as_deref() { + // Update the tweaks config with the full question at the specified input value key + if let Some(tweaks_obj) = tweaks_config.as_object_mut() { + for (_, tweak) in tweaks_obj.iter_mut() { + if let Some(tweak_obj) = tweak.as_object_mut() { + if tweak_obj.contains_key(input_value_key) { + tweak_obj.insert(input_value_key.to_string(), serde_json::Value::String(full_question.clone())); + } + } + } + } + } debug!("Tweaks config after update: {:?}", tweaks_config); serde_json::json!({ "input_value": full_question, "input_type": flow.input_type, - "output_type:": flow.output_type, + "output_type": flow.output_type, "tweaks": tweaks_config, }) }, diff --git a/fluent_cli/src/config.rs b/fluent_cli/src/config.rs index 35ce0c0..a3b9da3 100644 --- a/fluent_cli/src/config.rs +++ b/fluent_cli/src/config.rs @@ -30,6 +30,7 @@ pub struct FlowConfig { pub input_type: Option, pub output_type: Option, pub input_value: Option, + pub input_value_key: Option, } diff --git a/fluent_cli/src/main.rs b/fluent_cli/src/main.rs index 8d5599c..4250a31 100644 --- a/fluent_cli/src/main.rs +++ b/fluent_cli/src/main.rs @@ -48,6 +48,20 @@ use crossterm::style::Stylize; use tokio::time::Instant; // use env_logger; // Uncomment this when you are using it to initialize logs +use serde_json::{Value, Map}; + +fn update_value(existing_value: &mut Value, new_value: &str) { + match existing_value { + Value::Array(arr) => { + // Preserve the array if the existing value is an array + *existing_value = Value::Array(vec![Value::String(new_value.to_string())]); + } + _ => { + // Default to string replacement + *existing_value = Value::String(new_value.to_string()); + } + } +} use std::collections::HashMap; @@ -290,8 +304,10 @@ async fn main() -> Result<()> { if key_parts.len() == 1 { // Update override_config if let Some(obj) = override_config.as_object_mut() { - if obj.contains_key(&key) { - obj.insert(key.clone(), serde_json::Value::String(value.clone())); + if let Some(existing_value) = obj.get_mut(&key) { + update_value(existing_value, &value); + } else { + obj.insert(key.clone(), Value::String(value.clone())); } } @@ -299,8 +315,10 @@ async fn main() -> Result<()> { if let Some(tweaks_obj) = tweaks_config.as_object_mut() { for (_, tweak) in tweaks_obj.iter_mut() { if let Some(tweak_obj) = tweak.as_object_mut() { - if tweak_obj.contains_key(&key) { - tweak_obj.insert(key.clone(), serde_json::Value::String(value.clone())); + if let Some(existing_value) = tweak_obj.get_mut(&key) { + update_value(existing_value, &value); + } else { + tweak_obj.insert(key.clone(), Value::String(value.clone())); } } } @@ -310,7 +328,11 @@ async fn main() -> Result<()> { if let Some(tweaks_obj) = tweaks_config.as_object_mut() { if let Some(tweak) = tweaks_obj.get_mut(key_parts[0]) { if let Some(tweak_obj) = tweak.as_object_mut() { - tweak_obj.insert(key_parts[1].to_string(), serde_json::Value::String(value.clone())); + if let Some(existing_value) = tweak_obj.get_mut(key_parts[1]) { + update_value(existing_value, &value); + } else { + tweak_obj.insert(key_parts[1].to_string(), Value::String(value.clone())); + } } } }