@@ -8,11 +8,11 @@ use rig::agent::{Agent as RigAgent, PromptResponse};
88use rig:: client:: CompletionClient ;
99use rig:: completion:: Prompt ;
1010use rig:: completion:: message:: { Message , UserContent } ;
11- use rig:: providers:: anthropic;
11+ use rig:: providers:: { anthropic, openai } ;
1212use rig:: tool:: ToolDyn ;
1313
1414use crate :: chatwoot:: ChatwootClient ;
15- use crate :: config:: Settings ;
15+ use crate :: config:: { Provider , ProviderConfig , Settings } ;
1616use crate :: images:: ImageAttachment ;
1717use crate :: preamble;
1818use crate :: slack:: SlackClient ;
@@ -22,14 +22,67 @@ use crate::tools::{
2222 ShellTool , SlackHistoryTool , SlackPostTool , TelegramPostTool , ToolName ,
2323} ;
2424
25- type Inner = RigAgent < anthropic :: completion :: CompletionModel > ;
25+ const VENICE_BASE_URL : & str = "https://api.venice.ai/api/v1" ;
2626
27- pub ( crate ) fn build_client ( provider : & crate :: config:: ProviderConfig ) -> Result < anthropic:: Client > {
28- let mut builder = anthropic:: Client :: builder ( ) . api_key ( & provider. key ) ;
29- if !provider. base . is_empty ( ) {
30- builder = builder. base_url ( & provider. base ) ;
27+ type AnthropicInner = RigAgent < anthropic:: completion:: CompletionModel > ;
28+ type OpenAiInner = RigAgent < openai:: completion:: CompletionModel > ;
29+
30+ enum Inner {
31+ Anthropic ( AnthropicInner ) ,
32+ OpenAi ( OpenAiInner ) ,
33+ }
34+
35+ impl Inner {
36+ async fn prompt_response ( & self , msg : & str ) -> Result < PromptResponse > {
37+ match self {
38+ Inner :: Anthropic ( inner) => Ok ( inner. prompt ( msg) . extended_details ( ) . await ?) ,
39+ Inner :: OpenAi ( inner) => Ok ( inner. prompt ( msg) . extended_details ( ) . await ?) ,
40+ }
41+ }
42+
43+ async fn prompt_message ( & self , msg : Message ) -> Result < String > {
44+ match self {
45+ Inner :: Anthropic ( inner) => Ok ( inner. prompt ( msg) . await ?) ,
46+ Inner :: OpenAi ( inner) => Ok ( inner. prompt ( msg) . await ?) ,
47+ }
48+ }
49+ }
50+
51+ pub ( crate ) fn build_anthropic_client ( provider : Provider , config : & ProviderConfig ) -> Result < anthropic:: Client > {
52+ let name = provider_name ( provider) ;
53+ let mut builder = anthropic:: Client :: builder ( ) . api_key ( & config. key ) ;
54+ if !config. base . is_empty ( ) {
55+ builder = builder. base_url ( & config. base ) ;
56+ }
57+ builder. build ( ) . map_err ( |e| format ! ( "building {name} client: {e}" ) . into ( ) )
58+ }
59+
60+ pub ( crate ) fn build_openai_client ( provider : Provider , config : & ProviderConfig ) -> Result < openai:: CompletionsClient > {
61+ let name = provider_name ( provider) ;
62+ let base_url = openai_base_url ( provider, config) ;
63+ let mut builder = openai:: CompletionsClient :: builder ( ) . api_key ( & config. key ) ;
64+ if let Some ( base_url) = base_url {
65+ builder = builder. base_url ( base_url) ;
66+ }
67+ builder. build ( ) . map_err ( |e| format ! ( "building {name} client: {e}" ) . into ( ) )
68+ }
69+
70+ fn openai_base_url ( provider : Provider , config : & ProviderConfig ) -> Option < & str > {
71+ if !config. base . is_empty ( ) {
72+ return Some ( & config. base ) ;
73+ }
74+ match provider {
75+ Provider :: Venice => Some ( VENICE_BASE_URL ) ,
76+ Provider :: Anthropic | Provider :: Deepseek => None ,
77+ }
78+ }
79+
80+ fn provider_name ( provider : Provider ) -> & ' static str {
81+ match provider {
82+ Provider :: Anthropic => "anthropic" ,
83+ Provider :: Deepseek => "deepseek" ,
84+ Provider :: Venice => "venice" ,
3185 }
32- builder. build ( ) . map_err ( |e| format ! ( "building Anthropic client: {e}" ) . into ( ) )
3386}
3487
3588pub struct GemmyAgent {
@@ -45,14 +98,22 @@ impl GemmyAgent {
4598 slack : Arc < SlackClient > ,
4699 mcp_tools : Vec < Box < dyn ToolDyn > > ,
47100 ) -> Result < Self > {
48- let provider = settings. llm_provider ( ) ;
49- if provider . key . is_empty ( ) {
101+ let config = settings. llm_provider ( ) ;
102+ if config . key . is_empty ( ) {
50103 return Err ( format ! ( "no key for the active provider {:?} — set its key in vault/.env" , settings. provider) . into ( ) ) ;
51104 }
52105 let preamble = preamble:: render ( settings) ?;
53- let client = build_client ( provider) ?;
54-
55- let inner = build_inner ( & client, settings, & preamble, memory, chatwoot, slack, mcp_tools) ;
106+ let tools = build_tools ( settings, memory, chatwoot, slack, mcp_tools) ;
107+ let inner = match settings. provider {
108+ Provider :: Anthropic | Provider :: Deepseek => {
109+ let client = build_anthropic_client ( settings. provider , config) ?;
110+ Inner :: Anthropic ( build_inner ( & client, settings, & preamble, tools) )
111+ }
112+ Provider :: Venice => {
113+ let client = build_openai_client ( settings. provider , config) ?;
114+ Inner :: OpenAi ( build_inner ( & client, settings, & preamble, tools) )
115+ }
116+ } ;
56117
57118 info ! (
58119 agent = %settings. agent_name,
@@ -77,7 +138,7 @@ impl GemmyAgent {
77138 images = 0 ,
78139 "agent.prompt"
79140 ) ;
80- Ok ( self . inner . prompt ( msg) . extended_details ( ) . await ? )
141+ self . inner . prompt_response ( msg) . await
81142 }
82143
83144 pub async fn prompt_with_images ( & self , msg : & str , images : Vec < ImageAttachment > ) -> Result < String > {
@@ -96,20 +157,17 @@ impl GemmyAgent {
96157 blocks. push ( UserContent :: image_base64 ( engine. encode ( & img. bytes ) , Some ( img. media_type ) , None ) ) ;
97158 }
98159 let content = OneOrMany :: many ( blocks) . expect ( "text block always present" ) ;
99- Ok ( self . inner . prompt ( Message :: User { content } ) . await ? )
160+ self . inner . prompt_message ( Message :: User { content } ) . await
100161 }
101162}
102163
103- fn build_inner (
104- client : & anthropic:: Client ,
164+ fn build_tools (
105165 settings : & Settings ,
106- preamble : & str ,
107166 memory : Option < Arc < MemoryStore > > ,
108167 chatwoot : Option < Arc < ChatwootClient > > ,
109168 slack : Arc < SlackClient > ,
110169 mcp_tools : Vec < Box < dyn ToolDyn > > ,
111- ) -> Inner {
112- let model_id = & settings. agent . model ;
170+ ) -> Vec < Box < dyn ToolDyn > > {
113171 let mut tools: Vec < Box < dyn ToolDyn > > = mcp_tools;
114172 for entry in & settings. agent . tools {
115173 let policy = entry. policy ( ) ;
@@ -159,6 +217,14 @@ fn build_inner(
159217 tools. push ( Box :: new ( GatedTool { inner, policy } ) ) ;
160218 }
161219 }
220+ tools
221+ }
222+
223+ fn build_inner < C > ( client : & C , settings : & Settings , preamble : & str , tools : Vec < Box < dyn ToolDyn > > ) -> RigAgent < C :: CompletionModel >
224+ where
225+ C : CompletionClient ,
226+ {
227+ let model_id = & settings. agent . model ;
162228 let tool_names: Vec < String > = tools. iter ( ) . map ( |t| t. name ( ) ) . collect ( ) ;
163229 info ! ( model = %model_id, tools = ?tool_names, "built agent" ) ;
164230 client
0 commit comments