mirror of
https://github.com/velocitatem/cvfs.git
synced 2026-05-31 16:53:38 +00:00
140 lines
4.3 KiB
Python
140 lines
4.3 KiB
Python
"""
|
|
Thin async + sync wrappers over the Anthropic SDK for quick scripting and agent
|
|
patterns. Use this when you want direct API access with streaming; for full
|
|
agentic loops with file tools use `claude-agent-sdk` (pip install claude-agent-sdk).
|
|
|
|
Usage:
|
|
from alveslib.agent import ask, stream, Agent
|
|
|
|
# One-shot
|
|
reply = ask("Summarize this data: ...")
|
|
|
|
# Streaming to stdout
|
|
stream("Write a FastAPI endpoint that ...")
|
|
|
|
# Multi-turn agent
|
|
agent = Agent(system="You are an expert Python dev.")
|
|
reply = agent.chat("Generate a Celery task that processes CSV files")
|
|
follow = agent.chat("Now add error handling and retries")
|
|
"""
|
|
|
|
import os
|
|
import asyncio
|
|
from typing import Iterator, AsyncIterator
|
|
|
|
try:
|
|
import anthropic
|
|
|
|
_client: anthropic.Anthropic | None = anthropic.Anthropic(
|
|
api_key=os.environ.get("ANTHROPIC_API_KEY")
|
|
)
|
|
_async_client: anthropic.AsyncAnthropic | None = anthropic.AsyncAnthropic(
|
|
api_key=os.environ.get("ANTHROPIC_API_KEY")
|
|
)
|
|
except ImportError:
|
|
_client = None
|
|
_async_client = None
|
|
|
|
|
|
DEFAULT_MODEL = "claude-sonnet-4-5"
|
|
|
|
|
|
def _require_client() -> "anthropic.Anthropic":
|
|
if _client is None:
|
|
raise ImportError("pip install anthropic")
|
|
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
raise RuntimeError("ANTHROPIC_API_KEY not set")
|
|
return _client
|
|
|
|
|
|
def ask(prompt: str, system: str = "", model: str = DEFAULT_MODEL) -> str:
|
|
"""One-shot blocking request; returns full text."""
|
|
client = _require_client()
|
|
msg = client.messages.create(
|
|
model=model,
|
|
max_tokens=8096,
|
|
system=system or anthropic.NOT_GIVEN,
|
|
messages=[{"role": "user", "content": prompt}],
|
|
)
|
|
return msg.content[0].text
|
|
|
|
|
|
def stream(prompt: str, system: str = "", model: str = DEFAULT_MODEL) -> Iterator[str]:
|
|
"""Streaming generator; yields text deltas. Print as they arrive."""
|
|
client = _require_client()
|
|
with client.messages.stream(
|
|
model=model,
|
|
max_tokens=8096,
|
|
system=system or anthropic.NOT_GIVEN,
|
|
messages=[{"role": "user", "content": prompt}],
|
|
) as s:
|
|
yield from s.text_stream
|
|
|
|
|
|
async def ask_async(prompt: str, system: str = "", model: str = DEFAULT_MODEL) -> str:
|
|
"""Async one-shot request."""
|
|
if _async_client is None:
|
|
raise ImportError("pip install anthropic")
|
|
msg = await _async_client.messages.create(
|
|
model=model,
|
|
max_tokens=8096,
|
|
system=system or anthropic.NOT_GIVEN,
|
|
messages=[{"role": "user", "content": prompt}],
|
|
)
|
|
return msg.content[0].text
|
|
|
|
|
|
async def stream_async(
|
|
prompt: str, system: str = "", model: str = DEFAULT_MODEL
|
|
) -> AsyncIterator[str]:
|
|
"""Async streaming generator."""
|
|
if _async_client is None:
|
|
raise ImportError("pip install anthropic")
|
|
async with _async_client.messages.stream(
|
|
model=model,
|
|
max_tokens=8096,
|
|
system=system or anthropic.NOT_GIVEN,
|
|
messages=[{"role": "user", "content": prompt}],
|
|
) as s:
|
|
async for text in s.text_stream:
|
|
yield text
|
|
|
|
|
|
class Agent:
|
|
"""Stateful multi-turn conversation agent with optional system prompt."""
|
|
|
|
def __init__(self, system: str = "", model: str = DEFAULT_MODEL):
|
|
self.system = system
|
|
self.model = model
|
|
self.history: list[dict] = []
|
|
|
|
def chat(self, prompt: str) -> str:
|
|
client = _require_client()
|
|
self.history.append({"role": "user", "content": prompt})
|
|
msg = client.messages.create(
|
|
model=self.model,
|
|
max_tokens=8096,
|
|
system=self.system or anthropic.NOT_GIVEN,
|
|
messages=self.history,
|
|
)
|
|
reply = msg.content[0].text
|
|
self.history.append({"role": "assistant", "content": reply})
|
|
return reply
|
|
|
|
async def chat_async(self, prompt: str) -> str:
|
|
if _async_client is None:
|
|
raise ImportError("pip install anthropic")
|
|
self.history.append({"role": "user", "content": prompt})
|
|
msg = await _async_client.messages.create(
|
|
model=self.model,
|
|
max_tokens=8096,
|
|
system=self.system or anthropic.NOT_GIVEN,
|
|
messages=self.history,
|
|
)
|
|
reply = msg.content[0].text
|
|
self.history.append({"role": "assistant", "content": reply})
|
|
return reply
|
|
|
|
def reset(self) -> None:
|
|
self.history.clear()
|