summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2025-03-06 13:50:12 +0100
committerYuval Adam <_@yuv.al>2025-03-06 13:50:12 +0100
commitd6fe8443a8bb6eeedaa7f8cb1f25f46491089c57 (patch)
tree41a89542f38b779428130f17ed9a32127a6df6bd
parentbadb469e09ff105a7a8418be2b1e5a1495d6d94a (diff)
Add claude llm and ask command
-rw-r--r--.gitignore3
-rw-r--r--README.md36
-rw-r--r--pyproject.toml5
-rw-r--r--strangeloop/cli.py17
-rw-r--r--strangeloop/llm.py95
-rw-r--r--uv.lock70
6 files changed, 210 insertions, 16 deletions
diff --git a/.gitignore b/.gitignore
index d3a7493..bd6ad7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
__pycache__
*.egg-info
-dist \ No newline at end of file
+dist
+.venv/ \ No newline at end of file
diff --git a/README.md b/README.md
index f717262..a7ccc1f 100644
--- a/README.md
+++ b/README.md
@@ -21,24 +21,36 @@ $ uv run strangeloop
Strangeloop provides a command-line interface with several commands:
```bash
-# Display help
-strangeloop --help
+# Run the CLI ad hoc without installation
+uvx strangeloop --help
+uvx strangeloop hello
+uvx strangeloop greet [NAME]
+uvx strangeloop info
+uvx strangeloop process FILE_PATH [--output OUTPUT_PATH]
-# Print a hello message
-strangeloop hello
+# Ask Claude Sonnet 3.7 a question
+uvx strangeloop ask "What is recursive self-improvement in AI?"
+```
-# Greet a user
+If you've installed the package:
+```bash
+# Using the installed CLI
+strangeloop --help
+strangeloop hello
strangeloop greet [NAME]
-
-# Display information about the environment
strangeloop info
strangeloop info --verbose
-
-# Process a file
strangeloop process FILE_PATH [--output OUTPUT_PATH]
+strangeloop ask "What is the meaning of life?" --max-tokens 2048 --temperature 0.8
```
-You can also run the CLI directly without installation:
-
+You can also run the CLI directly from the source:
```bash
-python main.py --help
+uvx run strangeloop --help
+```
+
+## Environment Variables
+
+The following environment variables are required:
+
+- `ANTHROPIC_API_KEY`: Your Anthropic API key for accessing Claude Sonnet 3.7
diff --git a/pyproject.toml b/pyproject.toml
index addf1ff..8ba407a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,15 +1,16 @@
[project]
name = "strangeloop"
-version = "0.0.1"
+version = "0.1.0"
description = "A recursive and self-referential AI agent framework"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"click>=8.1.8",
+ "requests>=2.32.3",
]
[project.scripts]
strangeloop = "strangeloop.cli:cli"
[tool.uv]
-package = true \ No newline at end of file
+package = true
diff --git a/strangeloop/cli.py b/strangeloop/cli.py
index 35270e5..3a20521 100644
--- a/strangeloop/cli.py
+++ b/strangeloop/cli.py
@@ -5,6 +5,7 @@ Strangeloop CLI - A recursive and self-referential AI agent framework.
import click
import sys
from pathlib import Path
+from .llm import ask_claude
@click.group()
@@ -58,5 +59,21 @@ def process(file_path, output):
click.echo(f"Results written to: {output}")
+@cli.command()
+@click.argument("question", required=True)
+@click.option("--max-tokens", "-m", default=1024, help="Maximum tokens in response")
+@click.option("--temperature", "-t", default=0.7, type=float, help="Temperature (0.0-1.0)")
+def ask(question, max_tokens, temperature):
+ """Ask Claude Sonnet 3.7 a question and get a response."""
+ try:
+ click.echo("Asking Claude Sonnet 3.7...")
+ response = ask_claude(question, max_tokens, temperature)
+ click.echo("\nResponse:")
+ click.echo(response)
+ except Exception as e:
+ click.echo(f"Error: {str(e)}", err=True)
+ sys.exit(1)
+
+
if __name__ == "__main__":
cli()
diff --git a/strangeloop/llm.py b/strangeloop/llm.py
new file mode 100644
index 0000000..900b01f
--- /dev/null
+++ b/strangeloop/llm.py
@@ -0,0 +1,95 @@
+"""
+LLM integration module for Strangeloop.
+Provides functionality to interact with Claude Sonnet 3.7.
+"""
+import os
+import requests
+import json
+from typing import Dict, Any, Optional
+
+
+class ClaudeClient:
+ """Client for interacting with Anthropic's Claude API."""
+
+ def __init__(self, api_key: Optional[str] = None, model: str = "claude-3-sonnet-20240229"):
+ """
+ Initialize the Claude client.
+
+ Args:
+ api_key: Anthropic API key. If None, will try to get from ANTHROPIC_API_KEY env var.
+ model: The Claude model to use. Defaults to Claude Sonnet 3.7.
+ """
+ self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
+ if not self.api_key:
+ raise ValueError("Anthropic API key must be provided or set as ANTHROPIC_API_KEY environment variable")
+
+ self.model = model
+ self.api_url = "https://api.anthropic.com/v1/messages"
+ self.headers = {
+ "x-api-key": self.api_key,
+ "anthropic-version": "2023-06-01",
+ "content-type": "application/json"
+ }
+
+ def ask(self, prompt: str, max_tokens: int = 1024, temperature: float = 0.7) -> Dict[str, Any]:
+ """
+ Ask Claude a question and get a response.
+
+ Args:
+ prompt: The question or prompt to send to Claude
+ max_tokens: Maximum number of tokens in the response
+ temperature: Controls randomness (0 = deterministic, 1 = creative)
+
+ Returns:
+ Dict containing the response and metadata
+ """
+ payload = {
+ "model": self.model,
+ "max_tokens": max_tokens,
+ "temperature": temperature,
+ "messages": [
+ {"role": "user", "content": prompt}
+ ]
+ }
+
+ try:
+ response = requests.post(self.api_url, headers=self.headers, json=payload)
+ response.raise_for_status()
+ return response.json()
+ except requests.exceptions.RequestException as e:
+ raise Exception(f"Error communicating with Claude API: {str(e)}")
+
+ def get_response_text(self, response: Dict[str, Any]) -> str:
+ """
+ Extract the text content from Claude's response.
+
+ Args:
+ response: The response dict from the ask method
+
+ Returns:
+ The text content of Claude's response
+ """
+ try:
+ content = response.get("content", [])
+ if content and len(content) > 0:
+ return content[0].get("text", "")
+ return ""
+ except (KeyError, IndexError, AttributeError) as e:
+ raise Exception(f"Error parsing Claude response: {str(e)}")
+
+
+def ask_claude(prompt: str, max_tokens: int = 1024, temperature: float = 0.7) -> str:
+ """
+ Convenience function to ask Claude a question and get the text response.
+
+ Args:
+ prompt: The question or prompt to send to Claude
+ max_tokens: Maximum number of tokens in the response
+ temperature: Controls randomness (0 = deterministic, 1 = creative)
+
+ Returns:
+ The text content of Claude's response
+ """
+ client = ClaudeClient()
+ response = client.ask(prompt, max_tokens, temperature)
+ return client.get_response_text(response) \ No newline at end of file
diff --git a/uv.lock b/uv.lock
index 6bdfde4..9881ff6 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,6 +2,37 @@ version = 1
requires-python = ">=3.13"
[[package]]
+name = "certifi"
+version = "2025.1.31"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
+ { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
+ { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
+ { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
+ { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
+ { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
+ { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
+ { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
+ { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
+ { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
+ { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
+ { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
+ { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
+ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
+]
+
+[[package]]
name = "click"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
@@ -23,12 +54,49 @@ wheels = [
]
[[package]]
+name = "idna"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
+]
+
+[[package]]
name = "strangeloop"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "click" },
+ { name = "requests" },
]
[package.metadata]
-requires-dist = [{ name = "click", specifier = ">=8.1.8" }]
+requires-dist = [
+ { name = "click", specifier = ">=8.1.8" },
+ { name = "requests", specifier = ">=2.32.3" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
+]