Skip to content

Commit f1d346d

Browse files
authored
Update Cloud LLM brick docs (#27)
1 parent bb62885 commit f1d346d

File tree

2 files changed

+160
-51
lines changed

2 files changed

+160
-51
lines changed
Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,109 @@
1-
# Cloud LLM brick
1+
# Cloud LLM Brick
22

3-
This directory contains the implementation of the Cloud LLM brick, which provides an interface to interact with cloud-based Large Language Models (LLMs) through their REST API.
3+
The Cloud LLM Brick provides a seamless interface to interact with cloud-based Large Language Models (LLMs) such as OpenAI's GPT, Anthropic's Claude, and Google's Gemini. It abstracts the complexity of REST APIs, enabling you to send prompts, receive responses, and maintain conversational context within your Arduino projects.
44

55
## Overview
66

7-
The Cloud LLM brick allows users to send prompts to a specified LLM service and receive generated responses.
8-
It can be configured to work with a curated set of LLM providers that offer RESTful APIs, notably: ChatGPT, Claude and Gemini.
7+
This Brick acts as a gateway to powerful AI models hosted in the cloud. It is designed to handle the nuances of network communication, authentication, and session management. Whether you need a simple one-off answer or a continuous conversation with memory, the Cloud LLM Brick provides a unified API for different providers.
8+
9+
## Features
10+
11+
- **Multi-Provider Support**: Compatible with major LLM providers including Anthropic (Claude), OpenAI (GPT), and Google (Gemini).
12+
- **Conversational Memory**: Built-in support for windowed history, allowing the AI to remember context from previous exchanges.
13+
- **Streaming Responses**: Receive text chunks in real-time as they are generated, ideal for responsive user interfaces.
14+
- **Configurable Behavior**: Customize system prompts, temperature (creativity), and request timeouts.
15+
- **Simple API**: Unified `chat` and `chat_stream` methods regardless of the underlying model provider.
916

1017
## Prerequisites
1118

12-
Before using the Cloud LLM brick, ensure you have the following:
13-
- An account with a cloud-based LLM service (e.g., OpenAI, Cohere, etc.).
14-
- API access credentials (API key or token) for the LLM service.
15-
- Network connectivity to access the LLM service endpoint.
19+
- **Internet Connection**: The board must be connected to the internet to reach the LLM provider's API.
20+
- **API Key**: A valid API key for the chosen service (e.g., OpenAI API Key, Anthropic API Key).
21+
- **Python Dependencies**: The Brick relies on LangChain integration packages (`langchain-anthropic`, `langchain-openai`, `langchain-google-genai`).
1622

17-
## Features
23+
## Code Example and Usage
24+
25+
### Basic Conversation
1826

19-
- Send prompts to a cloud-based LLM service.
20-
- Receive and process responses from the LLM.
21-
- Supports both one-shot requests and memory for follow-up questions and answers.
22-
- Supports a curated set of LLM providers.
27+
This example initializes the Brick with an OpenAI model and performs a simple chat interaction.
2328

24-
## Code example and usage
25-
Here is a basic example of how to use the Cloud LLM brick:
29+
**Note:** The API key is not hardcoded. It is retrieved automatically from the **Brick Configuration** in App Lab.
2630

2731
```python
28-
from arduino.app_bricks.cloud_llm import CloudLLM
32+
import os
33+
from arduino.app_bricks.cloud_llm import CloudLLM, CloudModel
2934
from arduino.app_utils import App
3035

31-
llm = CloudLLM(api_key="your_api_key_here")
36+
# Initialize the Brick (API key is loaded from configuration)
37+
llm = CloudLLM(
38+
model=CloudModel.OPENAI_GPT,
39+
system_prompt="You are a helpful assistant for an IoT device."
40+
)
3241

33-
App.start_bricks()
42+
def simple_chat():
43+
# Send a prompt and print the response
44+
response = llm.chat("What is the capital of Italy?")
45+
print(f"AI: {response}")
3446

35-
response = llm.chat("What is the capital of France?")
36-
print(response)
47+
# Run the application
48+
App.run(simple_chat)
49+
```
50+
51+
### Streaming with Memory
52+
53+
This example demonstrates how to enable conversational memory and process the response as a stream of tokens.
54+
55+
```python
56+
from arduino.app_bricks.cloud_llm import CloudLLM, CloudModel
57+
from arduino.app_utils import App
3758

38-
App.stop_bricks()
59+
# Initialize with memory enabled (keeps last 10 messages)
60+
# API Key is retrieved automatically from Brick Configuration
61+
llm = CloudLLM(
62+
model=CloudModel.ANTHROPIC_CLAUDE
63+
).with_memory(max_messages=10)
64+
65+
def chat_loop():
66+
while True:
67+
user_input = input("You: ")
68+
if user_input.lower() in ["exit", "quit"]:
69+
break
70+
71+
print("AI: ", end="", flush=True)
72+
73+
# Stream the response token by token
74+
for token in llm.chat_stream(user_input):
75+
print(token, end="", flush=True)
76+
print() # Newline after response
77+
78+
App.run(chat_loop)
3979
```
80+
81+
## Configuration
82+
83+
The Brick is initialized with the following parameters:
84+
85+
| Parameter | Type | Default | Description |
86+
| :-------------- | :-------------------- | :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
87+
| `api_key` | `str` | `os.getenv("API_KEY")` | The authentication key for the LLM provider. **Recommended:** Set this via the **Brick Configuration** menu in App Lab instead of code. |
88+
| `model` | `str` \| `CloudModel` | `CloudModel.ANTHROPIC_CLAUDE` | The specific model to use. Accepts a `CloudModel` enum or its string value. |
89+
| `system_prompt` | `str` | `""` | A base instruction that defines the AI's behavior and persona. |
90+
| `temperature` | `float` | `0.7` | Controls randomness. `0.0` is deterministic, `1.0` is creative. |
91+
| `timeout` | `int` | `30` | Maximum time (in seconds) to wait for a response. |
92+
93+
### Supported Models
94+
95+
You can select a model using the `CloudModel` enum or by passing the corresponding raw string identifier.
96+
97+
| Enum Constant | Raw String ID | Provider Documentation |
98+
| :---------------------------- | :------------------------- | :-------------------------------------------------------------------------- |
99+
| `CloudModel.ANTHROPIC_CLAUDE` | `claude-3-7-sonnet-latest` | [Anthropic Models](https://docs.anthropic.com/en/docs/about-claude/models) |
100+
| `CloudModel.OPENAI_GPT` | `gpt-4o-mini` | [OpenAI Models](https://platform.openai.com/docs/models) |
101+
| `CloudModel.GOOGLE_GEMINI` | `gemini-2.5-flash` | [Google Gemini Models](https://ai.google.dev/gemini-api/docs/models/gemini) |
102+
103+
## Methods
104+
105+
- **`chat(message)`**: Sends a message and returns the complete response string. Blocks until generation is finished.
106+
- **`chat_stream(message)`**: Returns a generator yielding response tokens as they arrive.
107+
- **`stop_stream()`**: Interrupts an active streaming generation.
108+
- **`with_memory(max_messages)`**: Enables history tracking. `max_messages` defines the context window size.
109+
- **`clear_memory()`**: Resets the conversation history.

src/arduino/app_bricks/cloud_llm/cloud_llm.py

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ class AlreadyGenerating(Exception):
3131

3232
@brick
3333
class CloudLLM:
34-
"""A simplified, opinionated wrapper for common LangChain conversational patterns.
34+
"""A Brick for interacting with cloud-based Large Language Models (LLMs).
3535
36-
This class provides a single interface to manage stateless chat and chat with memory.
36+
This class wraps LangChain functionality to provide a simplified, unified interface
37+
for chatting with models like Claude, GPT, and Gemini. It supports both synchronous
38+
'one-shot' responses and streaming output, with optional conversational memory.
3739
"""
3840

3941
def __init__(
@@ -44,18 +46,24 @@ def __init__(
4446
temperature: Optional[float] = 0.7,
4547
timeout: int = 30,
4648
):
47-
"""Initializes the CloudLLM brick with the given configuration.
49+
"""Initializes the CloudLLM brick with the specified provider and configuration.
4850
4951
Args:
50-
api_key: The API key for the LLM service.
51-
model: The model identifier as per LangChain specification (e.g., "anthropic:claude-3-sonnet-20240229")
52-
or by using a CloudModels enum (e.g. CloudModels.OPENAI_GPT). Defaults to CloudModel.ANTHROPIC_CLAUDE.
53-
system_prompt: The global system-level instruction for the AI.
54-
temperature: The sampling temperature for response generation. Defaults to 0.7.
55-
timeout: The maximum time to wait for a response from the LLM service, in seconds. Defaults to 30 seconds.
52+
api_key (str): The API access key for the target LLM service. Defaults to the
53+
'API_KEY' environment variable.
54+
model (Union[str, CloudModel]): The model identifier. Accepts a `CloudModel`
55+
enum member (e.g., `CloudModel.OPENAI_GPT`) or its corresponding raw string
56+
value (e.g., `'gpt-4o-mini'`). Defaults to `CloudModel.ANTHROPIC_CLAUDE`.
57+
system_prompt (str): A system-level instruction that defines the AI's persona
58+
and constraints (e.g., "You are a helpful assistant"). Defaults to empty.
59+
temperature (Optional[float]): The sampling temperature between 0.0 and 1.0.
60+
Higher values make output more random/creative; lower values make it more
61+
deterministic. Defaults to 0.7.
62+
timeout (int): The maximum duration in seconds to wait for a response before
63+
timing out. Defaults to 30.
5664
5765
Raises:
58-
ValueError: If the API key is missing.
66+
ValueError: If `api_key` is not provided (empty string).
5967
"""
6068
if api_key == "":
6169
raise ValueError("API key is required to initialize CloudLLM brick.")
@@ -99,34 +107,34 @@ def __init__(
99107
def with_memory(self, max_messages: int = DEFAULT_MEMORY) -> "CloudLLM":
100108
"""Enables conversational memory for this instance.
101109
102-
This allows the chatbot to remember previous user and AI messages.
103-
Calling this modifies the instance to be stateful.
110+
Configures the Brick to retain a window of previous messages, allowing the
111+
AI to maintain context across multiple interactions.
104112
105113
Args:
106-
max_messages: The total number of past messages (user + AI) to
107-
keep in the conversation window. Set to 0 to disable memory.
114+
max_messages (int): The maximum number of messages (user + AI) to keep
115+
in history. Older messages are discarded. Set to 0 to disable memory.
116+
Defaults to 10.
108117
109118
Returns:
110-
self: The current CloudLLM instance for method chaining.
119+
CloudLLM: The current instance, allowing for method chaining.
111120
"""
112121
self._max_messages = max_messages
113122

114123
return self
115124

116125
def chat(self, message: str) -> str:
117-
"""Sends a single message to the AI and gets a complete response synchronously.
126+
"""Sends a message to the AI and blocks until the complete response is received.
118127
119-
This is the primary way to interact. It automatically handles memory
120-
based on how the instance was configured.
128+
This method automatically manages conversation history if memory is enabled.
121129
122130
Args:
123-
message: The user's message.
131+
message (str): The input text prompt from the user.
124132
125133
Returns:
126-
The AI's complete response as a string.
134+
str: The complete text response generated by the AI.
127135
128136
Raises:
129-
RuntimeError: If the chat model is not initialized or if text generation fails.
137+
RuntimeError: If the internal chain is not initialized or if the API request fails.
130138
"""
131139
if self._chain is None:
132140
raise RuntimeError("CloudLLM brick is not started. Please call start() before generating text.")
@@ -137,19 +145,20 @@ def chat(self, message: str) -> str:
137145
raise RuntimeError(f"Response generation failed: {e}")
138146

139147
def chat_stream(self, message: str) -> Iterator[str]:
140-
"""Sends a single message to the AI and streams the response as a synchronous generator.
148+
"""Sends a message to the AI and yields response tokens as they are generated.
141149
142-
Use this to get tokens as they are generated, perfect for a streaming UI.
150+
This allows for processing or displaying the response in real-time (streaming).
151+
The generation can be interrupted by calling `stop_stream()`.
143152
144153
Args:
145-
message: The user's message.
154+
message (str): The input text prompt from the user.
146155
147156
Yields:
148-
str: Chunks of the AI's response as they become available.
157+
str: Chunks of text (tokens) from the AI response.
149158
150159
Raises:
151-
RuntimeError: If the chat model is not initialized or if text generation fails.
152-
AlreadyGenerating: If the chat model is already streaming a response.
160+
RuntimeError: If the internal chain is not initialized or if the API request fails.
161+
AlreadyGenerating: If a streaming session is already active.
153162
"""
154163
if self._chain is None:
155164
raise RuntimeError("CloudLLM brick is not started. Please call start() before generating text.")
@@ -168,18 +177,33 @@ def chat_stream(self, message: str) -> Iterator[str]:
168177
self._keep_streaming.clear()
169178

170179
def stop_stream(self) -> None:
171-
"""Signals the LLM to stop generating a response."""
180+
"""Signals the active streaming generation to stop.
181+
182+
This sets an internal flag that causes the `chat_stream` iterator to break
183+
early. It has no effect if no stream is currently running.
184+
"""
172185
self._keep_streaming.clear()
173186

174187
def clear_memory(self) -> None:
175-
"""Clears the conversational memory.
188+
"""Clears the conversational memory history.
176189
177-
This only has an effect if with_memory() has been called.
190+
Resets the stored context. This is useful for starting a new conversation
191+
topic without previous context interfering. Only applies if memory is enabled.
178192
"""
179193
if self._history:
180194
self._history.clear()
181195

182196
def _get_session_history(self, session_id: str) -> WindowedChatMessageHistory:
197+
"""Retrieves or creates the chat history for a given session.
198+
199+
Internal callback used by LangChain's `RunnableWithMessageHistory`.
200+
201+
Args:
202+
session_id (str): The unique identifier for the session.
203+
204+
Returns:
205+
WindowedChatMessageHistory: The history object managing the message window.
206+
"""
183207
if self._max_messages == 0:
184208
self._history = InMemoryChatMessageHistory()
185209
if self._history is None:
@@ -188,6 +212,21 @@ def _get_session_history(self, session_id: str) -> WindowedChatMessageHistory:
188212

189213

190214
def model_factory(model_name: CloudModel, **kwargs) -> BaseChatModel:
215+
"""Factory function to instantiate the specific LangChain chat model.
216+
217+
This function maps the supported `CloudModel` enum values to their respective
218+
LangChain implementations.
219+
220+
Args:
221+
model_name (CloudModel): The enum or string identifier for the model.
222+
**kwargs: Additional arguments passed to the model constructor (e.g., api_key, temperature).
223+
224+
Returns:
225+
BaseChatModel: An instance of a LangChain chat model wrapper.
226+
227+
Raises:
228+
ValueError: If `model_name` does not match one of the supported `CloudModel` options.
229+
"""
191230
if model_name == CloudModel.ANTHROPIC_CLAUDE:
192231
from langchain_anthropic import ChatAnthropic
193232

0 commit comments

Comments
 (0)