diff --git a/assets/css/index.css b/assets/css/index.css index 7cd6a3db8a..74e4768b98 100644 --- a/assets/css/index.css +++ b/assets/css/index.css @@ -1297,7 +1297,7 @@ a[href*="#no-click"], img[src*="#no-click"] { } .suggestion-chips { - @apply flex flex-wrap gap-2 mt-3; + @apply grid grid-cols-2 gap-2 mt-3; } .suggestion-chip { @@ -1368,7 +1368,7 @@ a[href*="#no-click"], img[src*="#no-click"] { } .suggestion-chips { - @apply flex-col space-y-2; + @apply grid-cols-1; } .suggestion-chip { diff --git a/content/develop/ai/agent-builder/_index.md b/content/develop/ai/agent-builder/_index.md index 3239c2fa03..62ba5826fe 100644 --- a/content/develop/ai/agent-builder/_index.md +++ b/content/develop/ai/agent-builder/_index.md @@ -30,11 +30,12 @@ Redis powers these capabilities with fast, reliable data storage and retrieval t ## What you can build -Choose from three types of intelligent agents: +Choose from four types of intelligent agents: - **Recommendation engines**: Personalized product and content recommendations - **Conversational assistants**: Chatbots with memory and context awareness - **Knowledge assistants**: RAG agents that ingest documents, answer questions with citations, and use semantic caching +- **Redis Iris conversational assistants**: Conversational agents backed by managed [Redis Iris Agent Memory]({{< relref "/develop/ai/context-engine/agent-memory" >}}) — session and long-term memory with no vector index to build The agent builder will generate complete, working code examples for your chosen agent type. diff --git a/content/develop/ai/agent-builder/agent-concepts.md b/content/develop/ai/agent-builder/agent-concepts.md index 408c54b497..0d0ee83e41 100644 --- a/content/develop/ai/agent-builder/agent-concepts.md +++ b/content/develop/ai/agent-builder/agent-concepts.md @@ -79,6 +79,7 @@ Redis is the **ideal foundation** for AI agents because it excels at the three t - **Short-term**: Conversation context and session state - **Long-term**: User preferences and learned patterns - Flexible data structures (Hashes, Lists, Streams, JSON) for different memory types +- **Managed option**: The [Redis Iris Context Engine]({{< relref "/develop/ai/context-engine/agent-memory" >}}) provides short-term (session) and long-term memory as a managed service — with semantic long-term search — so you don't have to build the vector index and storage yourself - [Explore Redis data structures →](/develop/data-types/) ## Types of agents you can build @@ -365,6 +366,7 @@ Ready to build your AI agent with Redis? - [Redis quick start guide]({{< relref "/develop/get-started" >}}) for setting up Redis **Learn more:** +- [Redis Iris Context Engine — Agent Memory]({{< relref "/develop/ai/context-engine/agent-memory" >}}) for managed session and long-term agent memory - [Redis Vector Search documentation]({{< relref "develop/ai/search-and-query/vectors" >}}) - [RedisVL Python library]({{< relref "develop/clients/redis-vl" >}}) for vector operations and AI workflows - [Redis data structures guide](/develop/data-types/) diff --git a/layouts/shortcodes/agent-builder.html b/layouts/shortcodes/agent-builder.html index 0fd72ee6c6..3518699339 100644 --- a/layouts/shortcodes/agent-builder.html +++ b/layouts/shortcodes/agent-builder.html @@ -25,6 +25,7 @@

Build Your AI Agent🛍️ Recommendation Engine + diff --git a/static/code/agent-templates/javascript/iris_agent.js b/static/code/agent-templates/javascript/iris_agent.js new file mode 100644 index 0000000000..69ffa9c23b --- /dev/null +++ b/static/code/agent-templates/javascript/iris_agent.js @@ -0,0 +1,202 @@ +/* + * Redis Iris Conversational Assistant (Agent Memory) + * + * A conversational agent whose memory is fully managed by the Redis Iris + * Context Engine on Redis Cloud. Instead of building your own vector index, + * embeddings, and session store, the agent calls the managed, store-scoped + * Agent Memory REST API directly: + * + * Features: + * - Session memory: every user and assistant turn is stored as a session event + * - Long-term memory: the service extracts and promotes important facts; + * the agent searches them semantically each turn + * - Session summaries: older turns are compacted into a summary the agent + * folds back into context, so long conversations don't lose their history + * - No embeddings, vector index, or Redis schema to manage + * + * Each turn the agent: + * 1. Searches long-term memory for facts relevant to the new message + * 2. Loads the session (recent events + compacted summary) for short-term context + * 3. Calls the LLM with that memory injected into the system prompt + * 4. Writes the user and assistant messages back as session events + * (long-term facts are extracted and promoted automatically) + * + * To run this code: + * Install dependencies: + * npm install openai dotenv + * (Node.js 18+ is required for the built-in fetch used to call the API.) + * + * Set environment variables (Agent Memory — from the Redis Cloud console): + * AGENT_MEMORY_URL=your_agent_memory_base_url + * STORE_ID=your_store_id + * AGENT_MEMORY_API_KEY=your_agent_memory_api_key + * + * Set environment variables (LLM): + * LLM_API_KEY=your_api_key_here + * LLM_API_BASE_URL=your_base_url (optional - default: ${CONFIG.models[formData.llmModel].baseUrl}) + * LLM_MODEL=your_model (optional - default: ${CONFIG.models[formData.llmModel].defaultModel}) + * + * Note: this template uses the OpenAI SDK with a configurable base URL, so you + * can point it at any OpenAI-compatible chat provider. Agent memory is handled + * entirely by the managed Agent Memory service — see + * https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/ + * + * To create an Agent Memory service and get the values above, follow the + * Redis Cloud Agent Memory quickstart in the documentation. + * + * Run: + * node iris_agent.js + */ + +'use strict'; + +require('dotenv').config(); +const OpenAI = require('openai'); +const readline = require('readline'); +const crypto = require('crypto'); + +// How many long-term memories to inject as relevant background each turn. +const MAX_LONG_TERM_RESULTS = 5; +// How many recent session events to load for short-term context each turn. +const MAX_SESSION_EVENTS = 12; + +class ${AgentClassName} { + constructor(sessionId, actorId = 'user') { + // Managed Agent Memory service (Redis Cloud). The service owns the + // vector index, embeddings, and storage; we call its store-scoped + // REST API directly with fetch. + this.baseUrl = (process.env.AGENT_MEMORY_URL || '').replace(/\/$/, ''); + this.storeId = process.env.STORE_ID; + this.apiKey = process.env.AGENT_MEMORY_API_KEY; + + // Chat LLM. Uses the OpenAI SDK with a configurable base URL so any + // OpenAI-compatible provider works. + this.llm = new OpenAI({ + apiKey: process.env.LLM_API_KEY, + baseURL: process.env.LLM_API_BASE_URL || '${CONFIG.models[formData.llmModel].baseUrl}', + }); + this.model = process.env.LLM_MODEL || '${CONFIG.models[formData.llmModel].defaultModel}'; + + // A session groups the events of one conversation. Long-term memory is + // shared across all of a user's sessions. + this.sessionId = sessionId || `session-${crypto.randomBytes(6).toString('hex')}`; + this.actorId = actorId; + } + + // Call the store-scoped Agent Memory API. Returns parsed JSON, or null on + // 404 (e.g. a session that doesn't exist yet). + async memoryRequest(method, path, body) { + const res = await fetch(`${this.baseUrl}/v1/stores/${this.storeId}${path}`, { + method, + headers: { + Authorization: `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }); + if (res.status === 404) return null; + if (!res.ok) { + throw new Error(`Agent Memory ${method} ${path} -> ${res.status}: ${await res.text()}`); + } + return res.json(); + } + + // Semantic search over long-term memory for facts relevant to the query. + async relevantMemories(query) { + try { + const results = await this.memoryRequest('POST', '/long-term-memory/search', { text: query }); + const items = (results && results.items) || []; + return items.slice(0, MAX_LONG_TERM_RESULTS).map(item => item.text); + } catch (err) { + console.error(`[memory] long-term search unavailable: ${err.message}`); + return []; + } + } + + // Load the session: recent events for short-term context, plus the + // compacted summary of older turns the service has already summarized. + async loadSession() { + try { + const session = await this.memoryRequest('GET', `/session-memory/${this.sessionId}`); + if (!session) return { turns: [], summary: '' }; + const events = (session.events || []).slice(-MAX_SESSION_EVENTS); + const turns = events.map(event => ({ + role: String(event.role).toUpperCase() === 'ASSISTANT' ? 'assistant' : 'user', + content: (event.content || []).map(part => part.text || '').join(' '), + })); + return { turns, summary: (session.summary && session.summary.text) || '' }; + } catch (err) { + console.error(`[memory] session load unavailable: ${err.message}`); + return { turns: [], summary: '' }; + } + } + + // Persist one turn as a session event. Long-term promotion is automatic. + async recordEvent(role, text) { + await this.memoryRequest('POST', '/session-memory/events', { + sessionId: this.sessionId, + actorId: this.actorId, + role, + content: [{ text }], + createdAt: new Date().toISOString(), + }); + } + + async ask(userInput) { + // 1. Relevant long-term facts and 2. this session's recent turns + summary. + const facts = await this.relevantMemories(userInput); + const { turns, summary } = await this.loadSession(); + + let systemPrompt = + 'You are a helpful assistant with persistent memory. ' + + 'Use the following remembered facts about the user when relevant. ' + + 'If nothing is relevant, answer normally.\n\n' + + (facts.length + ? 'Relevant memories:\n' + facts.map(f => `- ${f}`).join('\n') + : 'Relevant memories: (none yet)'); + if (summary) { + systemPrompt += `\n\nSummary of earlier conversation:\n${summary}`; + } + + const messages = [ + { role: 'system', content: systemPrompt }, + ...turns, + { role: 'user', content: userInput }, + ]; + + // 3. Call the LLM. + const response = await this.llm.chat.completions.create({ model: this.model, messages }); + const answer = response.choices[0].message.content; + + // 4. Write both turns back as session events. + await this.recordEvent('USER', userInput); + await this.recordEvent('ASSISTANT', answer); + + return answer; + } +} + +async function main() { + const agent = new ${AgentClassName}(); + console.log('Redis Iris Conversational Assistant — type "exit" to quit.'); + console.log(`Session: ${agent.sessionId}\n`); + + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + const prompt = () => new Promise(resolve => rl.question('You: ', resolve)); + + for (;;) { + const userInput = (await prompt()).trim(); + if (!userInput || ['exit', 'quit'].includes(userInput.toLowerCase())) break; + console.log(`Agent: ${await agent.ask(userInput)}\n`); + } + rl.close(); +} + +if (require.main === module) { + main().catch(err => { + console.error('Fatal error:', err); + process.exit(1); + }); +} + +module.exports = ${AgentClassName}; diff --git a/static/code/agent-templates/python/iris_agent.py b/static/code/agent-templates/python/iris_agent.py new file mode 100644 index 0000000000..93034323c6 --- /dev/null +++ b/static/code/agent-templates/python/iris_agent.py @@ -0,0 +1,181 @@ +''' +Redis Iris Conversational Assistant (Agent Memory) + +A conversational agent whose memory is fully managed by the Redis Iris +Context Engine. Instead of building your own vector index, embeddings, and +session store, the agent calls the managed Agent Memory service: + +Features: +- Session memory: every user and assistant turn is stored as a session event +- Long-term memory: the service automatically promotes important facts from + session events; the agent searches them semantically each turn +- Cross-session recall: relevant facts follow the user across conversations +- No embeddings, vector index, or Redis schema to manage — the service does it + +Each turn the agent: + 1. Searches long-term memory for facts relevant to the new message + 2. Loads recent session events for short-term conversational context + 3. Calls the LLM with that memory injected into the system prompt + 4. Writes the user and assistant messages back as session events + (long-term facts are extracted and promoted automatically) + +To run this code: + Install dependencies: + pip install redis-agent-memory openai + + Set environment variables (Agent Memory — from the Redis Cloud console): + export AGENT_MEMORY_URL=your_agent_memory_base_url + export STORE_ID=your_store_id + export AGENT_MEMORY_API_KEY=your_agent_memory_api_key + + Set environment variables (LLM): + export LLM_API_KEY=your_api_key_here + export LLM_API_BASE_URL=your_${formData.llmModel.toLowerCase()}_api_base_url + (optional - default: ${CONFIG.models[formData.llmModel].baseUrl}) + export LLM_MODEL=your_${formData.llmModel.toLowerCase()}_model + (optional - default: ${CONFIG.models[formData.llmModel].defaultModel}) + + Note: this template uses the OpenAI SDK with a configurable base URL, so you + can point it at any OpenAI-compatible chat provider. Agent memory is handled + entirely by the managed Agent Memory service — see + https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/ + + To create an Agent Memory service and get the values above, follow the + Redis Cloud Agent Memory quickstart in the documentation. +''' + +import os +import uuid +from datetime import datetime, timezone + +import openai +from redis_agent_memory import AgentMemory, models + +# How many recent session events to load for short-term context each turn. +MAX_SESSION_EVENTS = 12 +# How many long-term memories to inject as relevant background each turn. +MAX_LONG_TERM_RESULTS = 5 + + +class ${AgentClassName}: + def __init__(self, session_id=None, actor_id='user'): + # Managed Agent Memory client. The service owns the vector index, + # embeddings, and storage — this client just talks to its REST API. + self.memory = AgentMemory( + os.environ['AGENT_MEMORY_URL'], + store_id=os.environ['STORE_ID'], + api_key=os.environ['AGENT_MEMORY_API_KEY'], + ) + + # Chat LLM. Uses the OpenAI SDK with a configurable base URL so any + # OpenAI-compatible provider works. + self.llm = openai.OpenAI( + api_key=os.environ['LLM_API_KEY'], + base_url=os.getenv('LLM_API_BASE_URL', '${CONFIG.models[formData.llmModel].baseUrl}'), + ) + self.model = os.getenv('LLM_MODEL', '${CONFIG.models[formData.llmModel].defaultModel}') + + # A session groups the events of one conversation. Reuse the same + # session_id to continue a conversation; long-term memory is shared + # across all of a user's sessions. + self.session_id = session_id or f'session-{uuid.uuid4().hex[:12]}' + self.actor_id = actor_id + + def _relevant_memories(self, query): + '''Semantic search over long-term memory for facts relevant to the query.''' + try: + results = self.memory.search_long_term_memory(request={'text': query}) + except Exception as e: + print(f'[memory] long-term search unavailable: {e}') + return [] + + # Matching records come back in `.items`; each record exposes its `.text`. + items = getattr(results, 'items', []) or [] + return [item.text for item in items[:MAX_LONG_TERM_RESULTS]] + + def _load_session(self): + '''Load the session: recent events for short-term context, plus the + compacted summary of older turns the service has already summarized.''' + try: + session = self.memory.get_session_memory(session_id=self.session_id) + except Exception: + return [], '' + + events = getattr(session, 'events', []) or [] + turns = [] + for event in events[-MAX_SESSION_EVENTS:]: + role = getattr(event, 'role', 'USER') + content = getattr(event, 'content', []) or [] + # Content parts are Content model objects (or dicts); read either. + text = ' '.join( + getattr(part, 'text', None) or (part.get('text', '') if isinstance(part, dict) else '') + for part in content + ) + turns.append({ + 'role': 'assistant' if str(role).upper().endswith('ASSISTANT') else 'user', + 'content': text, + }) + + # After summarization, older events are dropped from `events` and the + # compacted history is returned separately in `summary`. + summary_obj = getattr(session, 'summary', None) + summary = getattr(summary_obj, 'text', '') if summary_obj else '' + return turns, summary + + def _record(self, role, text): + '''Persist one turn as a session event. Long-term promotion is automatic.''' + self.memory.add_session_event( + actor_id=self.actor_id, + role=role, + content=[{'text': text}], + created_at=datetime.now(timezone.utc), + session_id=self.session_id, + ) + + def ask(self, user_input): + # 1. Pull relevant long-term facts and 2. this session's recent turns + summary. + facts = self._relevant_memories(user_input) + recent, summary = self._load_session() + + system_prompt = ( + 'You are a helpful assistant with persistent memory. ' + 'Use the following remembered facts about the user when relevant. ' + 'If nothing is relevant, answer normally.\n\n' + + ('Relevant memories:\n' + '\n'.join(f'- {f}' for f in facts) + if facts else 'Relevant memories: (none yet)') + ) + if summary: + system_prompt += f'\n\nSummary of earlier conversation:\n{summary}' + + messages = [{'role': 'system', 'content': system_prompt}] + messages.extend(recent) + messages.append({'role': 'user', 'content': user_input}) + + # 3. Call the LLM. + response = self.llm.chat.completions.create(model=self.model, messages=messages) + answer = response.choices[0].message.content + + # 4. Write both turns back to session memory. + self._record(models.MessageRole.USER, user_input) + self._record(models.MessageRole.ASSISTANT, answer) + + return answer + + +def main(): + agent = ${AgentClassName}() + print('Redis Iris Conversational Assistant — type "exit" to quit.') + print(f'Session: {agent.session_id}\n') + while True: + try: + user_input = input('You: ').strip() + except (EOFError, KeyboardInterrupt): + print() + break + if not user_input or user_input.lower() in ('exit', 'quit'): + break + print(f'Agent: {agent.ask(user_input)}\n') + + +if __name__ == '__main__': + main() diff --git a/static/js/agent-builder.js b/static/js/agent-builder.js index 36a326c341..4676ab40ab 100644 --- a/static/js/agent-builder.js +++ b/static/js/agent-builder.js @@ -26,6 +26,12 @@ description: "A RAG agent that ingests documents, uses Redis-native hybrid retrieval (text pre-filter + vector search), semantic caching, and session memory to answer questions with citations.", features: ["Document ingestion with chunking", "Hybrid vector + full-text search", "Semantic caching", "Citations"], keywords: ["rag", "knowledge", "documents", "search", "retrieval", "qa", "question answering", "citations", "hybrid"] + }, + iris: { + name: "Redis Iris Conversational Assistant", + description: "A conversational assistant whose memory is managed by Redis Iris Agent Memory. It stores session events and semantically searches long-term memory through the managed service — no vector index or embeddings to build.", + features: ["Managed Agent Memory service", "Session (short-term) memory", "Semantic long-term memory search", "Cross-session recall"], + keywords: ["iris", "context engine", "agent memory"] } }, languages: { @@ -293,7 +299,7 @@ switch (conversationState.step) { case 'agent-type': { - const agentIcons = { recommendation: '🛍️', conversational: '💬', rag: '🔍' }; + const agentIcons = { recommendation: '🛍️', conversational: '💬', rag: '🔍', iris: '🧠' }; suggestions = Object.entries(CONFIG.agentTypes).map(([key, config]) => ({ value: key, label: config.name, @@ -400,7 +406,8 @@ const defaultNames = { recommendation: 'RecommendationEngine', conversational: 'ConversationalAgent', - rag: 'KnowledgeAssistant' + rag: 'KnowledgeAssistant', + iris: 'IrisConversationalAssistant' }; conversationState.selections.agentName = defaultNames[selectedType] || 'RedisAgent'; @@ -418,7 +425,8 @@ addMessage("I didn't understand that. Please choose one of the agent types:", 'bot', [ { value: 'recommendation', label: '🛍️ Recommendation Engine' }, { value: 'conversational', label: '💬 Conversational Assistant' }, - { value: 'rag', label: '🔍 Knowledge Assistant' } + { value: 'rag', label: '🔍 Knowledge Assistant' }, + { value: 'iris', label: '🧠 Redis Iris Conversational Assistant' } ]); } }