11"""
22Function-calling example using a local LLM, with ollama.
33
4- "Function-calling" refers to the ability to ability of the LLM to generate
4+ "Function-calling" refers to the ability of the LLM to generate
55a structured response, typically a JSON object, instead of a plain text response,
66which is then interpreted by your code to perform some action.
77This is also referred to in various scenarios as "Tools", "Actions" or "Plugins".
3131Local model with best results is dolphin-mixtral:
3232```
3333ollama run dolphin-mixtral
34- python3 examples/basic/fn-call-local-numerical.py -m litellm/ollama_chat /dolphin-mixtral:latest
34+ python3 examples/basic/fn-call-local-numerical.py -m ollama /dolphin-mixtral:latest
3535```
3636
3737See here for how to set up a Local LLM to work with Langroid:
4343import fire
4444
4545import langroid as lr
46+ from langroid .language_models .openai_gpt import OpenAICallParams
4647from langroid .utils .configuration import settings
4748from langroid .agent .tool_message import ToolMessage
4849import langroid .language_models as lm
5758
5859
5960class PolinskyTool (lr .agent .ToolMessage ):
60- """A fictitious number transformation tool. We intentially use
61+ """A fictitious number transformation tool. We intentionally use
6162 a fictitious tool rather than something like "square" or "double"
6263 to prevent the LLM from trying to answer the question directly.
6364 """
6465
6566 request : str = "polinsky"
66- purpose : str = "To respond to user request for the Polinsky transform of a <number>."
67+ purpose : str = (
68+ "To respond to user request for the Polinsky transform of a <number>."
69+ )
6770 number : int
6871
69- def handle (self ) -> str :
70- """Handle LLM's structured output if it matches Polinsky tool"""
71- result = self .number * 3 + 1
72- msg = f"""
73- SUCCESS! The Polinksy transform of { self .number } is { result } .
74- Present this result to me, and ask for another number.
75- """
76- return msg
77-
7872 @classmethod
7973 def json_instructions (cls ) -> str :
8074 inst = super ().json_instructions ()
@@ -84,20 +78,6 @@ def json_instructions(cls) -> str:
8478 ONLY USE THIS TOOL AFTER THE USER ASKS FOR A POLINSKY TRANSFORM.
8579 """
8680
87- @staticmethod
88- def handle_message_fallback (
89- agent : lr .ChatAgent , msg : str | ChatDocument
90- ) -> str | ChatDocument | None :
91- """Fallback method when LLM forgets to generate a tool"""
92- if isinstance (msg , ChatDocument ) and msg .metadata .sender == "LLM" :
93- return """
94- You must use the "polinsky" tool/function to
95- request the Polinsky transform of a number.
96- You either forgot to use it, or you used it with the wrong format.
97- Make sure all fields are filled out and pay attention to the
98- required types of the fields.
99- """
100-
10181 @classmethod
10282 def examples (cls ) -> List ["ToolMessage" ]:
10383 # Used to provide few-shot examples in the system prompt
@@ -111,6 +91,38 @@ def examples(cls) -> List["ToolMessage"]:
11191 ]
11292
11393
94+ class MyChatAgent (lr .ChatAgent ):
95+ tool_called : bool = False
96+
97+ def polinsky (self , msg : PolinskyTool ) -> str :
98+ """Handle LLM's structured output if it matches Polinsky tool"""
99+ self .tool_called = True
100+ result = msg .number * 3 + 1
101+ response = f"""
102+ SUCCESS! The Polinksy transform of { msg .number } is { result } .
103+ Present this result to me, and ask for another number.
104+ """
105+ return response
106+
107+ def handle_message_fallback (
108+ self , msg : str | ChatDocument
109+ ) -> str | ChatDocument | None :
110+ """Fallback method when LLM does not generate a tool,
111+ and agent ends up handling the msg"""
112+ if isinstance (msg , ChatDocument ) and msg .metadata .sender == "LLM" :
113+ if self .tool_called :
114+ self .tool_called = False
115+ return "Ask the user what they need help with"
116+ else :
117+ return """
118+ You must use the "polinsky" tool/function to
119+ request the Polinsky transform of a number.
120+ You either forgot to use it, or you used it with the wrong format.
121+ Make sure all fields are filled out and pay attention to the
122+ required types of the fields.
123+ """
124+
125+
114126def app (
115127 m : str = DEFAULT_LLM , # model name
116128 d : bool = False , # debug
@@ -123,9 +135,13 @@ def app(
123135 chat_model = m or DEFAULT_LLM ,
124136 chat_context_length = 16_000 , # for dolphin-mixtral
125137 max_output_tokens = 100 ,
138+ params = OpenAICallParams (
139+ presence_penalty = 0.8 ,
140+ frequency_penalty = 0.8 ,
141+ ),
126142 temperature = 0 ,
127143 stream = True ,
128- timeout = 45 ,
144+ timeout = 100 ,
129145 )
130146
131147 # Recommended: First test if basic chat works with this llm setup as below:
@@ -143,7 +159,6 @@ def app(
143159 # verify you can interact with this in a chat loop on cmd line:
144160 # task.run("Concisely answer some questions")
145161
146-
147162 # Define a ChatAgentConfig and ChatAgent
148163 config = lr .ChatAgentConfig (
149164 llm = llm_cfg ,
@@ -182,15 +197,15 @@ def app(
182197 """ ,
183198 )
184199
185- agent = lr . ChatAgent (config )
200+ agent = MyChatAgent (config )
186201
187202 # (4) Enable the Tool for this agent --> this auto-inserts JSON instructions
188203 # and few-shot examples into the system message
189204 agent .enable_message (PolinskyTool )
190205
191206 # (5) Create task and run it to start an interactive loop
192207 task = lr .Task (agent )
193- task .run ("ONLY say this: 'What can I help with?' say NOTHING ELSE. " )
208+ task .run ("Can you help me with some questions? " )
194209
195210
196211if __name__ == "__main__" :
0 commit comments