Skip to content

Commit 9b5d67a

Browse files
committed
feat: Add interactive chat CLI with RAG system and configurable model URL
1 parent 4b54c85 commit 9b5d67a

4 files changed

Lines changed: 409 additions & 2 deletions

File tree

clarifai/cli/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,49 @@ clarifai config edit
4747
clarifai context env
4848
```
4949

50+
### Logout from the current context
51+
```bash
52+
clarifai logout
53+
```
54+
55+
This clears the PAT from the current context, logging you out. You can log back in anytime with `clarifai login`.
56+
57+
## Chat Command
58+
59+
Start an interactive chat session with AI assistance for CLI questions:
60+
61+
```bash
62+
$ clarifai chat
63+
```
64+
65+
### Configuration
66+
67+
You can customize the chat model URL in your config file. The chat command uses a default model URL but you can override it per context:
68+
69+
```yaml
70+
# ~/.clarifai/config.yaml
71+
contexts:
72+
default:
73+
CLARIFAI_PAT: your_pat_here
74+
chat_model_url: https://clarifai.com/openai/chat-completion/models/gpt-oss-120b
75+
production:
76+
CLARIFAI_PAT: prod_pat_here
77+
chat_model_url: https://clarifai.com/your-org/your-app/models/your-model
78+
```
79+
80+
If `chat_model_url` is not specified in a context, it defaults to: `https://clarifai.com/openai/chat-completion/models/gpt-oss-120b`
81+
82+
### Usage Example
83+
84+
```bash
85+
$ clarifai login
86+
$ clarifai chat
87+
> What's the login command?
88+
Assistant: The login command is `clarifai login`...
89+
> exit
90+
Goodbye!
91+
```
92+
5093
## Compute Orchestration
5194

5295
Quick example for deploying a `visual-classifier` model

clarifai/cli/base.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
from clarifai.utils.logging import logger
1313

1414

15-
@click.group(cls=AliasedGroup)
15+
@click.group(cls=AliasedGroup, invoke_without_command=True)
1616
@click.version_option(version=__version__)
1717
@click.option('--config', default=DEFAULT_CONFIG)
1818
@click.pass_context
1919
def cli(ctx, config):
20-
"""Clarifai CLI"""
20+
"""Clarifai CLI - Chat is the default command.
21+
22+
Run `clarifai` to start the chat interface, or use subcommands like `clarifai config`, `clarifai login`, etc.
23+
"""
2124
ctx.ensure_object(dict)
2225
if os.path.exists(config):
2326
cfg = Config.from_yaml(filename=config)
@@ -123,6 +126,26 @@ def login(ctx, api_url, user_id):
123126
logger.info(f"Login successful for user '{user_id}' in context '{context_name}'")
124127

125128

129+
@cli.command()
130+
@click.pass_context
131+
def logout(ctx):
132+
"""Logout from the current context by clearing the PAT."""
133+
current_context_name = ctx.obj.current_context
134+
current_context = ctx.obj.current
135+
136+
if current_context.name == '_empty_' or not current_context.pat:
137+
click.secho("You are not currently logged in.", fg='yellow')
138+
return
139+
140+
# Clear PAT from current context
141+
current_context['env']['CLARIFAI_PAT'] = ''
142+
ctx.obj.to_yaml()
143+
144+
click.secho(f"✅ Successfully logged out from '{current_context_name}'.", fg='green')
145+
click.echo("Your PAT has been cleared from this context.")
146+
logger.info(f"Logout successful from context '{current_context_name}'")
147+
148+
126149
def pat_display(pat):
127150
return pat[:5] + "****"
128151

@@ -314,5 +337,17 @@ def run(ctx, script, context=None):
314337
load_command_modules()
315338

316339

340+
@cli.result_callback()
341+
@click.pass_context
342+
def default_command(ctx, *args, **kwargs):
343+
"""If no subcommand is provided, run chat as the default."""
344+
if ctx.invoked_subcommand is None:
345+
# Import here to avoid circular imports
346+
from clarifai.cli.chat import chat
347+
348+
# Invoke the chat command
349+
ctx.invoke(chat)
350+
351+
317352
def main():
318353
cli()

clarifai/cli/chat.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""Chat command for Clarifai CLI."""
2+
3+
import os
4+
import sys
5+
6+
import click
7+
8+
import clarifai
9+
from clarifai.cli.base import cli
10+
from clarifai.cli.rag import ClarifaiCodeRAG, build_system_prompt_with_rag
11+
from clarifai.client.model import Model
12+
from clarifai.utils.cli import validate_context
13+
from clarifai.utils.logging import logger
14+
15+
# Default model URL for GPT-OSS-120B chat completion
16+
DEFAULT_CHAT_MODEL_URL = "https://clarifai.com/openai/chat-completion/models/gpt-oss-120b"
17+
18+
19+
@cli.command()
20+
@click.pass_context
21+
def chat(ctx):
22+
"""Start an interactive chat session using the Clarifai chat model.
23+
24+
You must be logged in first. Uses the current context's credentials.
25+
26+
Example:
27+
$ clarifai login
28+
$ clarifai chat
29+
"""
30+
# Validate that user is authenticated
31+
validate_context(ctx)
32+
33+
# Get the current context
34+
current_context = ctx.obj.current
35+
36+
if current_context.name == '_empty_' or not current_context.pat:
37+
click.secho("Error: No valid authentication found. Please login first.", fg='red')
38+
click.echo("Run: clarifai login")
39+
sys.exit(1)
40+
41+
# Get chat model URL from config or use default
42+
chat_model_url = current_context.get('chat_model_url', DEFAULT_CHAT_MODEL_URL)
43+
44+
# Try to initialize the model
45+
try:
46+
click.secho("Initializing chat model...", fg='cyan')
47+
48+
# Initialize model with the current context's PAT
49+
model = Model(
50+
url=chat_model_url,
51+
pat=current_context.pat,
52+
)
53+
54+
click.secho("Connected! Type 'exit' or 'quit' to leave.\n", fg='green')
55+
56+
# Initialize RAG system for CLI documentation
57+
try:
58+
# Find the clarifai-python root
59+
clarifai_root = os.path.dirname(os.path.dirname(clarifai.__file__))
60+
rag = ClarifaiCodeRAG(clarifai_root)
61+
click.secho("(CLI reference system ready)\n", fg='blue')
62+
except Exception as e:
63+
click.secho(f"(Warning: Could not load CLI reference system: {e})\n", fg='yellow')
64+
rag = None
65+
66+
# Interactive chat loop
67+
conversation_history = []
68+
69+
while True:
70+
try:
71+
# Get user input
72+
user_input = click.prompt(
73+
click.style("You", fg='cyan'), show_default=False, default=''
74+
)
75+
76+
if not user_input:
77+
continue
78+
79+
# Check for exit commands
80+
if user_input.lower() in ('exit', 'quit', 'bye'):
81+
click.secho("Goodbye!", fg='yellow')
82+
break
83+
84+
# Add to conversation history
85+
conversation_history.append({'role': 'user', 'message': user_input})
86+
87+
click.secho("Thinking...", fg='yellow')
88+
89+
# Build conversation context for follow-up questions
90+
conversation_context = ""
91+
if len(conversation_history) > 2: # Include last exchange if available
92+
conversation_context = "\n## Previous Context:\n"
93+
# Include last 2 exchanges (4 messages max) for context
94+
for msg in conversation_history[-4:-1]:
95+
role = "User" if msg['role'] == 'user' else "Assistant"
96+
conversation_context += (
97+
f"{role}: {msg['message'][:200]}\n" # Limit to 200 chars per msg
98+
)
99+
100+
# Build enhanced prompt with RAG context
101+
system_prompt = None
102+
if rag:
103+
system_prompt = build_system_prompt_with_rag(rag, user_input)
104+
else:
105+
system_prompt = """You are a Clarifai CLI expert. Help users with CLI commands, options, and troubleshooting.
106+
For non-CLI questions, direct users to: https://docs.clarifai.com
107+
108+
IMPORTANT: Keep responses CONCISE and FOCUSED. Use bullet points when appropriate. Maximum 300 words."""
109+
110+
# Create enhanced input with system prompt, history, and question
111+
enhanced_input = f"{system_prompt}{conversation_context}\n\nCurrent User Question: {user_input}\n\nRespond concisely (max 300 words)."
112+
113+
# Send to model with inference parameters to limit response
114+
try:
115+
response = model.predict_by_bytes(
116+
input_bytes=enhanced_input.encode('utf-8'),
117+
input_type='text',
118+
inference_params={
119+
'max_tokens': '500' # Limit output tokens for concise responses
120+
},
121+
)
122+
123+
# Extract the text response
124+
if response and hasattr(response, 'outputs') and len(response.outputs) > 0:
125+
output = response.outputs[0]
126+
if hasattr(output, 'data') and hasattr(output.data, 'text'):
127+
assistant_message = output.data.text.raw
128+
129+
# Add to conversation history
130+
conversation_history.append(
131+
{'role': 'assistant', 'message': assistant_message}
132+
)
133+
134+
# Display response
135+
click.secho(f"Assistant: {assistant_message}", fg='green')
136+
else:
137+
click.secho("No text response received", fg='red')
138+
else:
139+
click.secho("Invalid response format", fg='red')
140+
141+
except Exception as e:
142+
click.secho(f"Error calling model: {str(e)}", fg='red')
143+
logger.exception("Chat model prediction error")
144+
145+
click.echo() # Add spacing between exchanges
146+
147+
except KeyboardInterrupt:
148+
click.echo()
149+
click.secho("Chat interrupted. Goodbye!", fg='yellow')
150+
break
151+
except Exception as e:
152+
click.secho(f"Error: {str(e)}", fg='red')
153+
logger.exception("Chat error")
154+
except ImportError as e:
155+
click.secho(f"Error: Failed to import Clarifai SDK. {str(e)}", fg='red')
156+
sys.exit(1)
157+
except Exception as e:
158+
click.secho(f"Error initializing chat model: {str(e)}", fg='red')
159+
logger.exception("Chat initialization error")
160+
sys.exit(1)

0 commit comments

Comments
 (0)