Skip to content

Add Anthropic Claude provider #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions autoload/neural/config.vim
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ let s:defaults = {
\ 'temperature': 0.2,
\ 'top_p': 1,
\ },
\ 'anthropic': {
\ 'api_key': '',
\ 'max_tokens': 1024,
\ 'model': 'claude-2',
\ 'temperature': 0.2,
\ 'top_p': 1,
\ },
\ },
\}

Expand Down
10 changes: 10 additions & 0 deletions autoload/neural/source/anthropic.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
" Author: w0rp <[email protected]>
" Description: A script describing how to use Anthropic Claude with Neural

function! neural#source#anthropic#Get() abort
return {
\ 'name': 'anthropic',
\ 'script_language': 'python',
\ 'script': neural#GetScriptDir() . '/anthropic.py',
\}
endfunction
53 changes: 51 additions & 2 deletions doc/neural.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ CONTENTS *neural-contents*
4.2 Neural Buffer ............................. |neural-buffer|
4.3 OpenAI .................................... |neural-openai|
4.4 ChatGPT ................................... |neural-chatgpt|
4.5 Highlights ................................ |neural-highlights|
4.5 Anthropic Claude .......................... |neural-anthropic|
4.6 Highlights ................................ |neural-highlights|
5. API ........................................ |neural-api|
6. Environment Variables ...................... |neural-env|
6.1 Linux + KDE ............................. |neural-env-kde|
Expand All @@ -33,6 +34,7 @@ generate text, code, and much more.
Neural supports the following tools.

1. OpenAI - https://beta.openai.com/signup/
2. Anthropic Claude - https://www.anthropic.com/

To select the tool that Neural will use, set |g:neural.selected| to the
appropriate value. OpenAI is the default data source.
Expand Down Expand Up @@ -160,6 +162,7 @@ g:neural.selected *g:neural.selected*

1. `'openai'` - OpenAI
2. `'chatgpt'` - ChatGPT
3. `'anthropic'` - Anthropic Claude


g:neural.set_default_keybinds *g:neural.set_default_keybinds*
Expand Down Expand Up @@ -419,7 +422,53 @@ g:neural.source.chatgpt.top_p *g:neural.source.chatgpt.top_p*


-------------------------------------------------------------------------------
4.5 Highlights *neural-highlights*
4.5 Anthropic Claude *neural-anthropic*

Options for configuring Anthropic Claude are listed below.


g:neural.source.anthropic.api_key *g:neural.source.anthropic.api_key*
*vim.g.neural.source.anthropic.api_key*
Type: |String|
Default: `''`

The Anthropic API key. See: https://www.anthropic.com/


g:neural.source.anthropic.max_tokens *g:neural.source.anthropic.max_tokens*
*vim.g.neural.source.anthropic.max_tokens*
Type: |Number|
Default: `1024`

The maximum number of tokens to generate in the output.


g:neural.source.anthropic.model *g:neural.source.anthropic.model*
*vim.g.neural.source.anthropic.model*
Type: |String|
Default: `'claude-2'`

The model to use for Anthropic Claude.


g:neural.source.anthropic.temperature *g:neural.source.anthropic.temperature*
*vim.g.neural.source.anthropic.temperature*
Type: |Number| or |Float|
Default: `0.2`

The Anthropic sampling temperature.


g:neural.source.anthropic.top_p *g:neural.source.anthropic.top_p*
*vim.g.neural.source.anthropic.top_p*
Type: |Number| or |Float|
Default: `1`

The Anthropic nucleus sampling between `0` and `1`.


-------------------------------------------------------------------------------
4.6 Highlights *neural-highlights*

The following highlights can be configured to change |neural|'s colors.

Expand Down
159 changes: 159 additions & 0 deletions neural_providers/anthropic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""
A Neural datasource for loading generated text via Anthropic Claude.
"""
import json
import platform
import ssl
import sys
import urllib.error
import urllib.request
from typing import Any, Dict, List, Optional, Union

API_ENDPOINT = 'https://api.anthropic.com/v1/complete'

ANTHROPIC_DATA_HEADER = 'data: '
ANTHROPIC_DONE = '[DONE]'


class Config:
"""The sanitised configuration."""

def __init__(
self,
api_key: str,
model: str,
temperature: float,
top_p: float,
max_tokens: int,
) -> None:
self.api_key = api_key
self.model = model
self.temperature = temperature
self.top_p = top_p
self.max_tokens = max_tokens


def get_claude_completion(
config: Config,
prompt: Union[str, List[Dict[str, str]]],
) -> None:
headers = {
"Content-Type": "application/json",
"x-api-key": config.api_key,
"anthropic-version": "2023-06-01",
}
data = {
"model": config.model,
"prompt": (
prompt
if isinstance(prompt, str)
else ''.join([msg.get("content", "") for msg in prompt])
),
"temperature": config.temperature,
"top_p": config.top_p,
"max_tokens_to_sample": config.max_tokens,
"stream": True,
}

req = urllib.request.Request(
API_ENDPOINT,
data=json.dumps(data).encode("utf-8"),
headers=headers,
method="POST",
)
role: Optional[str] = None

Comment on lines +64 to +65
Copy link
Preview

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable role is declared but never used in get_claude_completion. Consider removing it to reduce dead code.

Suggested change
role: Optional[str] = None

Copilot uses AI. Check for mistakes.

context = (
ssl._create_unverified_context() # type: ignore
Copy link
Preview

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Bypassing SSL verification on Darwin can expose MITM risks. Consider using a verified context or clearly documenting the trusted use case.

Suggested change
ssl._create_unverified_context() # type: ignore
ssl.create_default_context()

Copilot uses AI. Check for mistakes.

if platform.system() == "Darwin" else
None
)

with urllib.request.urlopen(req, context=context) as response:
while True:
line_bytes = response.readline()

if not line_bytes:
break

line = line_bytes.decode("utf-8", errors="replace")
line_data = (
line[len(ANTHROPIC_DATA_HEADER):-1]
if line.startswith(ANTHROPIC_DATA_HEADER) else None
)

if line_data and line_data != ANTHROPIC_DONE:
chunk = json.loads(line_data)

if "completion" in chunk:
print(chunk["completion"], end="", flush=True)

print()


def load_config(raw_config: Dict[str, Any]) -> Config:
if not isinstance(raw_config, dict): # type: ignore
raise ValueError("anthropic config is not a dictionary")

api_key = raw_config.get('api_key')
if not isinstance(api_key, str) or not api_key: # type: ignore
raise ValueError("anthropic.api_key is not defined")

model = raw_config.get('model')
if not isinstance(model, str) or not model:
raise ValueError("anthropic.model is not defined")

temperature = raw_config.get('temperature', 0.2)
if not isinstance(temperature, (int, float)):
raise ValueError("anthropic.temperature is invalid")

top_p = raw_config.get('top_p', 1)
if not isinstance(top_p, (int, float)):
raise ValueError("anthropic.top_p is invalid")

max_tokens = raw_config.get('max_tokens', 1024)
if not isinstance(max_tokens, int):
raise ValueError("anthropic.max_tokens is invalid")

return Config(
api_key=api_key,
model=model,
temperature=temperature,
top_p=top_p,
max_tokens=max_tokens,
)


def get_error_message(error: urllib.error.HTTPError) -> str:
message = error.read().decode('utf-8', errors='ignore')

try:
message = json.loads(message)['error']['message']
except Exception:
pass
Comment on lines +132 to +133
Copy link
Preview

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching all Exceptions is too broad. Narrow the exception to json.JSONDecodeError or KeyError to avoid masking unexpected errors.

Suggested change
except Exception:
pass
except json.JSONDecodeError:
pass
except KeyError:
pass

Copilot uses AI. Check for mistakes.


return message


def main() -> None:
input_data = json.loads(sys.stdin.readline())

try:
config = load_config(input_data["config"])
except ValueError as err:
sys.exit(str(err))

try:
get_claude_completion(config, input_data["prompt"])
except urllib.error.HTTPError as error:
if error.code in (400, 401):
message = get_error_message(error)
sys.exit('Neural error: Anthropic request failure: ' + message)
elif error.code == 429:
sys.exit('Neural error: Anthropic request limit reached!')
else:
raise


if __name__ == "__main__": # pragma: no cover
main() # pragma: no cover
Loading
Loading