Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

first agents feature implementation #16

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

francois-caddet
Copy link
Contributor

Description

Added the agent completion methods.

Checklist

  • I updated the documentation and examples accordingly.
  • I added a agent module.
  • I Added the method Client::agent
  • I added the method Client::agent_async
  • I added the method Client::agent_stream (may be done in a later PR)

Copy link

coderabbitai bot commented Aug 23, 2024

Walkthrough

The changes introduce several new files that facilitate synchronous and asynchronous interactions with the Mistral API, specifically for managing agent communications. These files include examples of basic message exchanges, function calling capabilities for retrieving city temperatures, and enhancements to existing data structures and client methods for processing agent requests and responses.

Changes

Files Change Summary
examples/agent.rs, examples/agent_async.rs Introduced examples for synchronous and asynchronous interactions with the Mistral API, demonstrating message sending and response handling.
examples/agent_with_function_calling.rs, examples/agent_with_function_calling_async.rs Implemented functionality for retrieving city temperatures via function calling in both synchronous and asynchronous contexts.
src/v1/agent.rs Defined data structures for managing chat interactions, including message formatting and response handling.
src/v1/chat.rs Added HasChoices trait for flexibility in accessing choices from response objects.
src/v1/client.rs Introduced agent and agent_async methods for agent communication, modified existing function-call methods for increased flexibility.
src/v1/mod.rs Added a new public module declaration for agent to improve modularity.

Recent review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 424b005 and 759be91.

Files selected for processing (8)
  • examples/agent.rs (1 hunks)
  • examples/agent_async.rs (1 hunks)
  • examples/agent_with_function_calling.rs (1 hunks)
  • examples/agent_with_function_calling_async.rs (1 hunks)
  • src/v1/agent.rs (1 hunks)
  • src/v1/chat.rs (2 hunks)
  • src/v1/client.rs (4 hunks)
  • src/v1/mod.rs (1 hunks)
Additional comments not posted (9)
src/v1/mod.rs (1)

1-1: Approved: New module declaration.

The addition of pub mod agent; is correctly implemented and aligns with the PR's objectives to expand the client's functionality.

examples/agent_with_function_calling.rs (1)

1-68: Well-integrated example demonstrating synchronous agent interactions.

The example effectively demonstrates the new functionality related to agent interactions in a synchronous manner, aligning well with the PR objectives and AI-generated summary.

examples/agent_with_function_calling_async.rs (1)

1-72: Well-integrated example demonstrating asynchronous agent interactions.

The example effectively demonstrates the new functionality related to agent interactions in an asynchronous manner, aligning well with the PR objectives and AI-generated summary.

src/v1/agent.rs (3)

11-52: Well-designed AgentMessage enum.

The AgentMessage enum is well-structured, providing clear distinctions between different types of messages with appropriate serialization options. This design supports the new agent functionality effectively.


54-68: Clear and effective ResponseFormat struct.

The ResponseFormat struct is clearly defined, effectively communicating the required response format for agent responses. The inclusion of API documentation references enhances its usability.


136-181: Well-designed AgentRequest and AgentResponse structures.

Both the AgentRequest and AgentResponse structures are effectively designed to encapsulate the necessary data for agent interactions. The thoughtful use of serialization options and default implementations enhances their functionality and integration with the rest of the system.

src/v1/chat.rs (2)

5-7: Trait and method declaration are well-defined.

The HasChoices trait and its choices method are correctly declared with a clear and efficient signature.


204-207: Correct and efficient implementation of HasChoices for ChatResponse.

The implementation leverages the existing choices field efficiently, adhering to the trait's contract.

src/v1/client.rs (1)

558-559: Modification to call_function_if_any_async enhances flexibility.

The asynchronous version of the method now also accepts any type implementing HasChoices, maintaining consistency with the synchronous version and enhancing usability.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Comment on lines +17 to +19
async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
// Deserialize arguments, perform the logic, and return the result
let GetCityTemperatureArguments { city } = serde_json::from_str(&arguments).unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle deserialization errors gracefully.

Using unwrap can cause a panic if deserialization fails. Consider using ? to propagate errors or handling them explicitly.

Apply this diff to handle errors:

 async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
-    let GetCityTemperatureArguments { city } = serde_json::from_str(&arguments).unwrap();
+    let GetCityTemperatureArguments { city } = match serde_json::from_str(&arguments) {
+        Ok(args) => args,
+        Err(_) => return Box::new("Invalid arguments".to_string()),
+    };
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
// Deserialize arguments, perform the logic, and return the result
let GetCityTemperatureArguments { city } = serde_json::from_str(&arguments).unwrap();
async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
let GetCityTemperatureArguments { city } = match serde_json::from_str(&arguments) {
Ok(args) => args,
Err(_) => return Box::new("Invalid arguments".to_string()),
};

Comment on lines 30 to 68
fn main() {
let tools = vec![Tool::new(
"get_city_temperature".to_string(),
"Get the current temperature in a city.".to_string(),
vec![ToolFunctionParameter::new(
"city".to_string(),
"The name of the city.".to_string(),
ToolFunctionParameterType::String,
)],
)];

// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let mut client = Client::new(None, None, None, None).unwrap();
client.register_function(
"get_city_temperature".to_string(),
Box::new(GetCityTemperatureFunction),
);

let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
let messages = vec![AgentMessage {
role: AgentMessageRole::User,
content: "What's the temperature in Paris?".to_string(),
tool_calls: None,
}];
let options = AgentParams {
random_seed: Some(42),
tool_choice: Some(ToolChoice::Auto),
tools: Some(tools),
..Default::default()
};

client.agent(agid, messages, Some(options)).unwrap();
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
println!("The temperature in Paris is: {}.", temperature);
// => "The temperature in Paris is: 20°C."
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling and environment variable checks.

The code assumes environment variables are set and does not handle errors from the agent call. Consider adding checks and handling potential errors.

Apply this diff to improve error handling:

 let agid = std::env::var("MISTRAL_API_AGENT")
-    .expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
+    .unwrap_or_else(|_| panic!("Please export MISTRAL_API_AGENT with the agent id you want to use"));

 let result = client.agent(agid, messages, Some(options));
 match result {
     Ok(_) => {
         let temperature = client
             .get_last_function_call_result()
             .unwrap()
             .downcast::<String>()
             .unwrap();
         println!("The temperature in Paris is: {}.", temperature);
     }
     Err(e) => eprintln!("Agent call failed: {}", e),
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn main() {
let tools = vec![Tool::new(
"get_city_temperature".to_string(),
"Get the current temperature in a city.".to_string(),
vec![ToolFunctionParameter::new(
"city".to_string(),
"The name of the city.".to_string(),
ToolFunctionParameterType::String,
)],
)];
// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let mut client = Client::new(None, None, None, None).unwrap();
client.register_function(
"get_city_temperature".to_string(),
Box::new(GetCityTemperatureFunction),
);
let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
let messages = vec![AgentMessage {
role: AgentMessageRole::User,
content: "What's the temperature in Paris?".to_string(),
tool_calls: None,
}];
let options = AgentParams {
random_seed: Some(42),
tool_choice: Some(ToolChoice::Auto),
tools: Some(tools),
..Default::default()
};
client.agent(agid, messages, Some(options)).unwrap();
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
println!("The temperature in Paris is: {}.", temperature);
// => "The temperature in Paris is: 20°C."
}
fn main() {
let tools = vec![Tool::new(
"get_city_temperature".to_string(),
"Get the current temperature in a city.".to_string(),
vec![ToolFunctionParameter::new(
"city".to_string(),
"The name of the city.".to_string(),
ToolFunctionParameterType::String,
)],
)];
// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let mut client = Client::new(None, None, None, None).unwrap();
client.register_function(
"get_city_temperature".to_string(),
Box::new(GetCityTemperatureFunction),
);
let agid = std::env::var("MISTRAL_API_AGENT")
.unwrap_or_else(|_| panic!("Please export MISTRAL_API_AGENT with the agent id you want to use"));
let messages = vec![AgentMessage {
role: AgentMessageRole::User,
content: "What's the temperature in Paris?".to_string(),
tool_calls: None,
}];
let options = AgentParams {
random_seed: Some(42),
tool_choice: Some(ToolChoice::Auto),
tools: Some(tools),
..Default::default()
};
let result = client.agent(agid, messages, Some(options));
match result {
Ok(_) => {
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
println!("The temperature in Paris is: {}.", temperature);
}
Err(e) => eprintln!("Agent call failed: {}", e),
}
// => "The temperature in Paris is: 20°C."
}

Comment on lines +17 to +19
async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
// Deserialize arguments, perform the logic, and return the result
let GetCityTemperatureArguments { city } = serde_json::from_str(&arguments).unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle deserialization errors gracefully.

Using unwrap can cause a panic if deserialization fails. Consider using ? to propagate errors or handling them explicitly.

Apply this diff to handle errors:

 async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
-    let GetCityTemperatureArguments { city } = serde_json::from_str(&arguments).unwrap();
+    let GetCityTemperatureArguments { city } = match serde_json::from_str(&arguments) {
+        Ok(args) => args,
+        Err(_) => return Box::new("Invalid arguments".to_string()),
+    };
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
// Deserialize arguments, perform the logic, and return the result
let GetCityTemperatureArguments { city } = serde_json::from_str(&arguments).unwrap();
async fn execute(&self, arguments: String) -> Box<dyn Any + Send> {
// Deserialize arguments, perform the logic, and return the result
let GetCityTemperatureArguments { city } = match serde_json::from_str(&arguments) {
Ok(args) => args,
Err(_) => return Box::new("Invalid arguments".to_string()),
};

Comment on lines 30 to 72
#[tokio::main]
async fn main() {
let tools = vec![Tool::new(
"get_city_temperature".to_string(),
"Get the current temperature in a city.".to_string(),
vec![ToolFunctionParameter::new(
"city".to_string(),
"The name of the city.".to_string(),
ToolFunctionParameterType::String,
)],
)];

// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let mut client = Client::new(None, None, None, None).unwrap();
client.register_function(
"get_city_temperature".to_string(),
Box::new(GetCityTemperatureFunction),
);

let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
let messages = vec![AgentMessage {
role: AgentMessageRole::User,
content: "What's the temperature in Paris?".to_string(),
tool_calls: None,
}];
let options = AgentParams {
random_seed: Some(42),
tool_choice: Some(ToolChoice::Auto),
tools: Some(tools),
..Default::default()
};

client
.agent_async(agid, messages, Some(options))
.await
.unwrap();
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
println!("The temperature in Paris is: {}.", temperature);
// => "The temperature in Paris is: 20°C."
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling and environment variable checks.

The code assumes environment variables are set and does not handle errors from the agent call. Consider adding checks and handling potential errors.

Apply this diff to improve error handling:

 let agid = std::env::var("MISTRAL_API_AGENT")
-    .expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
+    .unwrap_or_else(|_| panic!("Please export MISTRAL_API_AGENT with the agent id you want to use"));

 let result = client.agent_async(agid, messages, Some(options)).await;
 match result {
     Ok(_) => {
         let temperature = client
             .get_last_function_call_result()
             .unwrap()
             .downcast::<String>()
             .unwrap();
         println!("The temperature in Paris is: {}.", temperature);
     }
     Err(e) => eprintln!("Agent call failed: {}", e),
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[tokio::main]
async fn main() {
let tools = vec![Tool::new(
"get_city_temperature".to_string(),
"Get the current temperature in a city.".to_string(),
vec![ToolFunctionParameter::new(
"city".to_string(),
"The name of the city.".to_string(),
ToolFunctionParameterType::String,
)],
)];
// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let mut client = Client::new(None, None, None, None).unwrap();
client.register_function(
"get_city_temperature".to_string(),
Box::new(GetCityTemperatureFunction),
);
let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
let messages = vec![AgentMessage {
role: AgentMessageRole::User,
content: "What's the temperature in Paris?".to_string(),
tool_calls: None,
}];
let options = AgentParams {
random_seed: Some(42),
tool_choice: Some(ToolChoice::Auto),
tools: Some(tools),
..Default::default()
};
client
.agent_async(agid, messages, Some(options))
.await
.unwrap();
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
println!("The temperature in Paris is: {}.", temperature);
// => "The temperature in Paris is: 20°C."
}
#[tokio::main]
async fn main() {
let tools = vec![Tool::new(
"get_city_temperature".to_string(),
"Get the current temperature in a city.".to_string(),
vec![ToolFunctionParameter::new(
"city".to_string(),
"The name of the city.".to_string(),
ToolFunctionParameterType::String,
)],
)];
// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let mut client = Client::new(None, None, None, None).unwrap();
client.register_function(
"get_city_temperature".to_string(),
Box::new(GetCityTemperatureFunction),
);
let agid = std::env::var("MISTRAL_API_AGENT")
.unwrap_or_else(|_| panic!("Please export MISTRAL_API_AGENT with the agent id you want to use"));
let messages = vec![AgentMessage {
role: AgentMessageRole::User,
content: "What's the temperature in Paris?".to_string(),
tool_calls: None,
}];
let options = AgentParams {
random_seed: Some(42),
tool_choice: Some(ToolChoice::Auto),
tools: Some(tools),
..Default::default()
};
let result = client.agent_async(agid, messages, Some(options)).await;
match result {
Ok(_) => {
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
println!("The temperature in Paris is: {}.", temperature);
}
Err(e) => eprintln!("Agent call failed: {}", e),
}
// => "The temperature in Paris is: 20°C."
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Comment on lines +30 to +72
#[tokio::main]
async fn main() {
let tools = vec![Tool::new(
"get_city_temperature".to_string(),
"Get the current temperature in a city.".to_string(),
vec![ToolFunctionParameter::new(
"city".to_string(),
"The name of the city.".to_string(),
ToolFunctionParameterType::String,
)],
)];

// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let mut client = Client::new(None, None, None, None).unwrap();
client.register_function(
"get_city_temperature".to_string(),
Box::new(GetCityTemperatureFunction),
);

let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
let messages = vec![AgentMessage::new_user_message(
"What's the temperature in Paris?",
)];
let options = AgentParams {
random_seed: Some(42),
tool_choice: Some(ToolChoice::Auto),
tools: Some(tools),
..Default::default()
};

client
.agent_async(agid, messages, Some(options))
.await
.unwrap();
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
println!("The temperature in Paris is: {}.", temperature);
// => "The temperature in Paris is: 20°C."
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main function setup and execution.

The main function demonstrates the registration and usage of the new agent functionality. However, it uses unwrap extensively, which is risky for production code as it may cause the program to panic if there are any errors.

Consider replacing unwrap with proper error handling to ensure the program can gracefully handle potential failures.

- .unwrap();
+ .expect("Error message describing the potential failure");

Committable suggestion was skipped due to low confidence.

"{:?}: {}",
result.choices[0].message.role, result.choices[0].message.content
);
// => "Assistant: Tower. The Eiffel Tower is a famous landmark in Paris, France."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment explaining the output format.

While the output is demonstrated in a comment, it could be beneficial to explain why this specific format is used or how it relates to the functionality being demonstrated.

Consider adding a detailed comment about the output format to help users understand the context or the data structure being printed.

Comment on lines +11 to +12
let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve error messaging for environment variable retrieval.

Using .expect provides a clear error message, which is good practice. However, consider enhancing the user experience by suggesting how to set the environment variable if it's not found.

-    .expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
+    .expect("Environment variable 'MISTRAL_API_AGENT' not found. Please set it with the agent id you want to use.");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Please export MISTRAL_API_AGENT with the agent id you want to use");
let agid = std::env::var("MISTRAL_API_AGENT")
.expect("Environment variable 'MISTRAL_API_AGENT' not found. Please set it with the agent id you want to use.");

)],
)];

// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify environment variable usage in the synchronous example.

The example assumes the MISTRAL_API_KEY environment variable is set but does not demonstrate its usage. Consider adding a comment or code to clarify.

-    // This example suppose you have set the `MISTRAL_API_KEY` environment variable.
+    // Ensure the 'MISTRAL_API_KEY' environment variable is set before running this example.
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
// Ensure the 'MISTRAL_API_KEY' environment variable is set before running this example.

Comment on lines +60 to +65
client.agent(agid, messages, Some(options)).unwrap();
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve error handling for function call results.

Using unwrap on function call results can lead to panics if the downcast fails or if the function call did not return a result. It's important to handle these cases to prevent runtime errors.

Consider adding error handling to manage these scenarios more gracefully.

-    .unwrap()
-    .downcast::<String>()
-    .unwrap();
+    .ok_or_else(|| "Function call did not return a result".to_string())?
+    .downcast::<String>()
+    .map_err(|_| "Failed to downcast to String".to_string())?;
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
client.agent(agid, messages, Some(options)).unwrap();
let temperature = client
.get_last_function_call_result()
.unwrap()
.downcast::<String>()
.unwrap();
client.agent(agid, messages, Some(options)).unwrap();
let temperature = client
.get_last_function_call_result()
.ok_or_else(|| "Function call did not return a result".to_string())?
.downcast::<String>()
.map_err(|_| "Failed to downcast to String".to_string())?;

- Add async and blocking agent completions methods into `Client`
- Implement new types into a new `agent` module.
- Add a trait to get choices from responses beeing generic over the response type
- impl this trait for `ChatResponse` and `AgentResponse`
- Add a blocking example for agent completion.
Add examples async and with function calling.
Now you can set a prefix to control the assistant output start.
It is the oportunity to improve the `AgentMessage` type. It's not a struct anymore.
It became an enum having a variant per possible roles.
@francois-caddet francois-caddet force-pushed the feature/agents-completion branch from 424b005 to 759be91 Compare September 6, 2024 15:29
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Comment on lines +6 to +8
fn main() {
// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let client = Client::new(None, None, None, None).unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify environment variable usage in the example.

The example assumes the MISTRAL_API_KEY environment variable is set, but it is not explicitly used in the code. Consider adding a comment or code to demonstrate its usage.

Comment on lines +21 to +23
let result = client.agent(agid, messages, Some(options)).unwrap();
println!("Assistant: {}", result.choices[0].message.content);
// => "Assistant: Tower. The Eiffel Tower is a famous landmark in Paris, France."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for production code.

While unwrap is acceptable in examples, for production code, consider handling errors more gracefully to avoid panics.

Comment on lines +6 to +9
#[tokio::main]
async fn main() {
// This example suppose you have set the `MISTRAL_API_KEY` environment variable.
let client = Client::new(None, None, None, None).unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify environment variable usage in the async example.

Similar to the synchronous example, the async example assumes the MISTRAL_API_KEY environment variable is set but does not demonstrate its usage. Consider adding a comment or code to clarify.

Comment on lines +22 to +25
let result = client
.agent_async(agid, messages, Some(options))
.await
.unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure proper error handling for asynchronous operations.

Using .unwrap() in asynchronous operations can lead to panics if the future resolves to an error. It's crucial to handle these errors gracefully to avoid runtime panics.

Consider using .await? to propagate errors or handle them explicitly with a match statement.

Comment on lines +73 to +134
/// The parameters for the chat request.
///
/// See the [API documentation](https://docs.mistral.ai/api/#operation/createChatCompletion) for more information.
#[derive(Clone, Debug, Serialize)]
pub struct AgentParams {
/// The maximum number of tokens to generate in the completion.
///
/// Defaults to `None`.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
/// The minimum number of tokens to generate in the completion.
///
/// Defaults to `None`.
#[serde(skip_serializing_if = "Option::is_none")]
pub min_tokens: Option<u32>,
/// Stop if one of these tokens is detected.
///
///Defaults to `None`
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
/// The seed to use for random sampling. If set, different calls will generate deterministic results.
///
/// Defaults to `None`.
#[serde(skip_serializing_if = "Option::is_none")]
pub random_seed: Option<u32>,
/// The format that the model must output.
///
/// Defaults to `None`.
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
/// A list of available tools for the model.
///
/// Defaults to `None`.
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<tool::Tool>>,
/// Specifies if/how functions are called.
///
/// Defaults to `None`.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<tool::ToolChoice>,
}
impl Default for AgentParams {
fn default() -> Self {
Self {
max_tokens: None,
min_tokens: None,
stop: None,
random_seed: None,
response_format: None,
tools: None,
tool_choice: None,
}
}
}
impl AgentParams {
pub fn json_default() -> Self {
Self {
response_format: Some(ResponseFormat::json_object()),
..Default::default()
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comprehensive and flexible AgentParams struct.

The AgentParams struct is well-designed, offering comprehensive options to customize agent interactions. The use of optional fields and defaults simplifies the API for users, making it more accessible.

Consider adding more detailed documentation for each field to further enhance understanding and usability.

Comment on lines +217 to +237
pub fn agent(
&self,
id: impl AsRef<str>,
messages: Vec<agent::AgentMessage>,
options: Option<agent::AgentParams>,
) -> Result<agent::AgentResponse, error::ApiError> {
let request = agent::AgentRequest::new(id.as_ref(), messages, false, options);

let response = self.post_sync("/agents/completions", &request)?;
let result = response.json::<agent::AgentResponse>();
match result {
Ok(data) => {
utils::debug_pretty_json_from_struct("Response Data", &data);

self.call_function_if_any(data.clone());

Ok(data)
}
Err(error) => Err(self.to_api_error(error)),
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method agent correctly implements synchronous agent interactions.

The method is well-implemented with appropriate error handling and response processing. Consider adding more detailed logging for better traceability of requests and responses.

Comment on lines +272 to +292
pub async fn agent_async(
&self,
id: impl AsRef<str>,
messages: Vec<agent::AgentMessage>,
options: Option<agent::AgentParams>,
) -> Result<agent::AgentResponse, error::ApiError> {
let request = agent::AgentRequest::new(id.as_ref(), messages, false, options);

let response = self.post_async("/agents/completions", &request).await?;
let result = response.json::<agent::AgentResponse>().await;
match result {
Ok(data) => {
utils::debug_pretty_json_from_struct("Response Data", &data);

self.call_function_if_any_async(data.clone()).await;

Ok(data)
}
Err(error) => Err(self.to_api_error(error)),
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method agent_async correctly implements asynchronous agent interactions.

The asynchronous implementation is consistent with its synchronous counterpart, ensuring non-blocking operations and proper error handling.

Consider adding automated tests to ensure consistency between the agent and agent_async methods.

Comment on lines +527 to +528
fn call_function_if_any(&self, response: impl chat::HasChoices) -> () {
let next_result = match response.choices().get(0) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modification to call_function_if_any enhances flexibility.

The method now accepts any type implementing HasChoices, increasing its usability across different response types.

Consider adding type constraints to ensure that the generic type meets certain conditions, enhancing type safety.

@crabdancing
Copy link

I think coderabbitai is kinda dumb. It's okay to call .unwrap() in an example. The point is to show how to use the lib, not to use the lib and handle all errors gracefully. Examples are generally not intended to be robust code usage -- they're intended to be conceptually simple. What do you think, @ivangabriele?

@crabdancing
Copy link

crabdancing commented Dec 8, 2024

Huh. When I tried to get examples/agent.rs working with an "ag:..." agent ID string assigned via env var, client.agent() spat out a nondescript error:

thread 'main' panicked at examples/agent.rs:22:62:
called `Result::unwrap()` on an `Err` value: ApiError { message: "error decoding response body" }

@francois-caddet Any idea why this is happening?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants