Skip to content

Commit

Permalink
clean
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Apr 26, 2024
1 parent b6ce1c4 commit ee751ad
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 229 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.gitattributes
_proc/
_quarto.yml
sidebar.yml
Expand Down
24 changes: 3 additions & 21 deletions claudia/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,15 @@
'doc_host': 'https://AnswerDotAI.github.io',
'git_url': 'https://github.com/AnswerDotAI/claudia',
'lib_path': 'claudia'},
'syms': { 'claudia.core': { 'claudia.core.AiMagic': ('core.html#aimagic', 'claudia/core.py'),
'claudia.core.AiMagic.__call__': ('core.html#aimagic.__call__', 'claudia/core.py'),
'claudia.core.AiMagic.__init__': ('core.html#aimagic.__init__', 'claudia/core.py'),
'claudia.core.AiMagic.cell': ('core.html#aimagic.cell', 'claudia/core.py'),
'claudia.core.AiMagic.explain': ('core.html#aimagic.explain', 'claudia/core.py'),
'claudia.core.AiMagic.fix': ('core.html#aimagic.fix', 'claudia/core.py'),
'claudia.core.AnthClient': ('core.html#anthclient', 'claudia/core.py'),
'syms': { 'claudia.core': { 'claudia.core.AnthClient': ('core.html#anthclient', 'claudia/core.py'),
'claudia.core.AnthClient.__call__': ('core.html#anthclient.__call__', 'claudia/core.py'),
'claudia.core.AnthClient.__init__': ('core.html#anthclient.__init__', 'claudia/core.py'),
'claudia.core.AnthClient.stream': ('core.html#anthclient.stream', 'claudia/core.py'),
'claudia.core._cellxml': ('core.html#_cellxml', 'claudia/core.py'),
'claudia.core._get_output': ('core.html#_get_output', 'claudia/core.py'),
'claudia.core._mk_ctx': ('core.html#_mk_ctx', 'claudia/core.py'),
'claudia.core._mk_dialog': ('core.html#_mk_dialog', 'claudia/core.py'),
'claudia.core._mk_prompt': ('core.html#_mk_prompt', 'claudia/core.py'),
'claudia.core._mk_sysp': ('core.html#_mk_sysp', 'claudia/core.py'),
'claudia.core._show_dialog': ('core.html#_show_dialog', 'claudia/core.py'),
'claudia.core.contents': ('core.html#contents', 'claudia/core.py'),
'claudia.core.create_magic': ('core.html#create_magic', 'claudia/core.py'),
'claudia.core.first_match': ('core.html#first_match', 'claudia/core.py'),
'claudia.core.get_cells': ('core.html#get_cells', 'claudia/core.py'),
'claudia.core.hl_md': ('core.html#hl_md', 'claudia/core.py'),
'claudia.core.json_to_xml': ('core.html#json_to_xml', 'claudia/core.py'),
'claudia.core.last_match': ('core.html#last_match', 'claudia/core.py'),
'claudia.core.mk_msg': ('core.html#mk_msg', 'claudia/core.py'),
'claudia.core.mk_msgs': ('core.html#mk_msgs', 'claudia/core.py'),
'claudia.core.replace_req': ('core.html#replace_req', 'claudia/core.py'),
'claudia.core.set_next_cell': ('core.html#set_next_cell', 'claudia/core.py'),
'claudia.core.t': ('core.html#t', 'claudia/core.py'),
'claudia.core.to_xml': ('core.html#to_xml', 'claudia/core.py')}}}
'claudia.core.to_xml': ('core.html#to_xml', 'claudia/core.py'),
'claudia.core.xt': ('core.html#xt', 'claudia/core.py')}}}
220 changes: 12 additions & 208 deletions claudia/core.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,35 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../00_core.ipynb.

# %% auto 0
__all__ = ['models', 'g', 'tags', 'set_next_cell', 'mk_msg', 'mk_msgs', 'contents', 'AnthClient', 'hl_md', 'to_xml', 't',
'json_to_xml', 'first_match', 'last_match', 'get_cells', 'AiMagic', 'create_magic', 'replace_req']
__all__ = ['models', 'g', 'tags', 'mk_msg', 'mk_msgs', 'contents', 'AnthClient', 'to_xml', 'xt', 'json_to_xml', 'first_match',
'last_match']

# %% ../00_core.ipynb 5
import xml.etree.ElementTree as ET, json

from anthropic import Anthropic
from IPython.display import Markdown,Javascript,clear_output
from io import BytesIO
from html import unescape

from fastcore.utils import *

# %% ../00_core.ipynb 6
models = 'claude-3-opus-20240229','claude-3-sonnet-20240229','claude-3-haiku-20240307'

# %% ../00_core.ipynb 9
def set_next_cell(ip, text, code=True, replace=False, execute=False):
"Create or replace an nb cell underneath the active cell containing `text`"
if not code: execute=True
ip.payload_manager.write_payload(dict(
source='set_next_input',
replace=replace, execute=execute,
text=text, ctype='code' if code else 'markdown'))

# %% ../00_core.ipynb 11
def mk_msg(content, role='user', **kw):
"Helper to create a `dict` appropriate for a Claude message"
return dict(role=role, content=content, **kw)

# %% ../00_core.ipynb 12
# %% ../00_core.ipynb 10
def mk_msgs(msgs, **kw):
"Helper to set 'assistant' role on alternate messages"
return [mk_msg(o, ('user','assistant')[i%2], **kw) if isinstance(o,str) else o
for i,o in enumerate(msgs)]

# %% ../00_core.ipynb 13
# %% ../00_core.ipynb 11
def contents(r):
"Help to get the contents from Claude response `r`"
return r.content[0].text.strip()

# %% ../00_core.ipynb 14
# %% ../00_core.ipynb 12
class AnthClient:
def __init__(self, model, cli=None):
"Basic Anthropic messages client"
Expand All @@ -57,23 +44,7 @@ def stream(self, msgs, sp='', temp=0, maxtok=4096, stop=None):
system=sp, temperature=temp, stop_sequences=stop) as stream:
yield from stream.text_stream

# %% ../00_core.ipynb 16
def _mk_sysp(pre, has_nb):
res = 'You are a helpful assistant with deep expertise in many topics, being used inside a Jupyter Notebook environment'
if not pre: return res + '. You provide concise yet complete answers, with no summary or restatement of the question background.'
if pre[0]=='-': return res + ". Provide concise step by step instructions to complete the task as a markdown bulleted list. Don't provide much detail about each step -- the user will be going through each step in a notebook one at a time, so they will ask for more detail when they get there."
lang = pre[3:]
res = res + f', and are a skilled {lang} coder. You provide, using a markdown fenced block with no additional explanation, {lang} code that fully completes the task. Your code will be run in an existing notebook.'
if has_nb:
res += "\nThe user has just run the cells shown in `nb_cells` in the notebook you are using, so don't repeat that context."
return res

# %% ../00_core.ipynb 17
def hl_md(s, lang='xml'):
"Syntax highlight `s` using `lang`"
return Markdown(f'```{lang}\n{s}\n```')

# %% ../00_core.ipynb 18
# %% ../00_core.ipynb 14
def to_xml(node, hl=False):
"Convert `node` to an XML string"
def mk_el(tag, cs, attrs):
Expand All @@ -87,18 +58,18 @@ def mk_el(tag, cs, attrs):
res = ET.tostring(root, encoding='unicode')
return hl_md(res) if hl else res

# %% ../00_core.ipynb 19
def t(tag, c=None, **kw):
# %% ../00_core.ipynb 15
def xt(tag, c=None, **kw):
"Helper to create appropriate data structure for `to_xml`"
kw = {k.lstrip('_'):str(v) for k,v in kw.items()}
return tag,c,kw

# %% ../00_core.ipynb 20
# %% ../00_core.ipynb 16
g = globals()
tags = 'div','img','h1','h2','h3','h4','h5','p','hr','span','html'
for o in tags: g[o] = partial(t, o)

# %% ../00_core.ipynb 23
# %% ../00_core.ipynb 19
def json_to_xml(d, rnm):
root = ET.Element(rnm)
def build_xml(data, parent):
Expand All @@ -111,179 +82,12 @@ def build_xml(data, parent):
ET.indent(root)
return ET.tostring(root, encoding='unicode')

# %% ../00_core.ipynb 24
def _get_output(o):
ot = o['output_type']
if ot in ('stream','execute_result','display_data'):
return t('output', o['text'] if ot=='stream' else o['data'], type=ot)
elif o['output_type']=='error':
return t('error', o['tb'], evalue=o['evalue'])
raise Exception(o)

def _cellxml(c):
"Cell `c` converted to XML"
elems = [t('source', c['source'])]
outs = c.get('outputs', [])
ol = [_get_output(o) for o in outs]
if ol: elems.append(t('outputs', ol))
return t('cell', elems, type=c['cell_type'])

# %% ../00_core.ipynb 25
def _mk_ctx(vs, cells, ns=None):
"Context for Claude using variables `vs` and nb `cells`, with variables from namespace `ns`"
if not vs and not cells: return ''
r = []
if vs:
if not ns: ns=globals()
elems = [t(o, ns[o], type=type(ns[o]).__name__) for o in vs]
r.append(t('variables', elems))
if cells:
elems = [_cellxml(c) for c in cells]
r.append(t('nb_cells', elems))
res = to_xml(t('context', r))
info = 'If this XML context contains entities, they should be decoded.'
if '&' in res:
r.insert(0, t('info', info))
res = to_xml(t('context', r))
return res+'\n' if res else ''

# %% ../00_core.ipynb 30
# %% ../00_core.ipynb 20
def first_match(lst, f, default=None):
"First element of `lst` matching predicate `f`, or `default` if none"
return next((i for i,o in enumerate(lst) if f(o)), default)

# %% ../00_core.ipynb 31
#| export
def last_match(lst, f, default=None):
"Last element of `lst` matching predicate `f`, or `default` if none"
return next((i for i in range(len(lst)-1, -1, -1) if f(lst[i])), default)

# %% ../00_core.ipynb 47
def get_cells(nbm, offset=1):
cells = nbm['cells'][:nbm['idx'] + offset]
lm = last_match(cells, lambda o: o.get('source','').startswith('%ai reset'))
if lm: cells = cells[lm+1:]
return [o for o in cells if not re.match(r'%ai +skip*$', o.get('source',''), flags=re.MULTILINE)]

# %% ../00_core.ipynb 51
_pp = '''{context_goes_here}<instructions>
{instructions_go_here}
</instructions>
<task>
{prompt_goes_here}
</task>'''

_code_pp = '''Write code I can run to complete the `task` below. You are not expected to access the internet or run code -- please provide code that I will run in my notebook.
Write code using expert-level concise code with no comments, and using minimal vertical space (including using the ternary `if` op as appropriate).'''
_prose_pp = 'Complete the `task` below. Answer concisely and with no summary or background unless asked for specifically -- I will ask for additional details or examples if I need them.'

# %% ../00_core.ipynb 52
def _mk_prompt(aic, rep, cells, expand=True, ns=None):
"Prompt for AI cell `aic` and optional reply `rep`, with nb cell context `cells`, and optionally $`variable`s expanded"
if ns is None: ns = get_ipython()
magic,*prompt = aic['source'].split('\n')
prompt = '\n'.join(prompt).strip()
cmd = magic.split()[0]
inst = _prose_pp if cmd in ('%%ai','%%aio') else _code_pp
vars = [re.sub(r'\$`(\w+)`', r'\1', o) for o in re.findall(r'\$`\w+`', prompt)] if expand else []
prompt = re.sub(r'\$(`\w+`)', r'\1', prompt)
ctx = _mk_ctx(vars, cells, ns)
fullp = _pp.format(prompt_goes_here=prompt, context_goes_here=ctx, instructions_go_here=inst)
res = [mk_msg(fullp)]
if rep:
src = rep['source'].strip()
if rep['cell_type']=='code': src = f'```\n{src}\n```'
res.append(mk_msg(src, role='assistant'))
return res

# %% ../00_core.ipynb 55
def _mk_dialog(cells, ns=None):
"Split `cells` into groups based on ai magics, and create Claude dialog messages"
res = []
while True:
m = first_match(cells, lambda o: o.get('source','').startswith('%%ai'), 0)
aic = cells[m]
rep = cells[m+1] if m<len(cells)-1 else None
res += _mk_prompt(aic, rep, cells[:m], ns=ns)
cells = cells[m+1 if rep else m:]
if len(cells)<2: break
return res

# %% ../00_core.ipynb 57
def _show_dialog(dialog):
for o in dialog: print('- ', o['role'], ':\n', o['content'],'\n----', sep='')

# %% ../00_core.ipynb 60
class AiMagic:
def __init__(self, model, shell=None):
"Backend functionality for `create_magic`, using Claude `model`"
self.c = AnthClient(model)
self.usage,self.shell = [],shell or get_ipython()

def __call__(self, x): return self.cell(cell=x)

def cell(self, line='', cell='', pre='', temp=0.5):
if line=='0' or not cell: return
is_code = pre.startswith('```')
cells = get_cells(self.shell.user_ns['nbmeta'])
if len(cell.split())==1:
meth = getattr(self, cell.strip(), None)
if meth: meth(cells)
chat = _mk_dialog(cells, ns=self.shell.user_ns)
sp = _mk_sysp(pre, '<nb_cells' in chat[-1]['content'])
if pre: chat.append(mk_msg(pre.strip(), role='assistant'))

cts = pre
if pre.startswith('`'): cts = ''.join(pre.splitlines(True)[1:])
display(Markdown(cts))
clear_output(wait=True)
try:
for chunk in self.c.stream(chat, sp=sp, stop=['```\n'] if is_code else None, temp=temp):
cts += chunk
display(Markdown(cts))
clear_output(wait=True)
except KeyboardInterrupt: pass
display('Done') # (I need this or the next clear_output is ignored :shrug:)
clear_output() # Remove once fully done
cts = cts.strip() # Remove superflouoususs line ending
if line=='-t': _show_dialog(chat)
set_next_cell(self.shell, cts, code=is_code)

# %% ../00_core.ipynb 61
def create_magic(model=None, sysp=None, nm='ai', shell=None):
"Create magic named `nm` using `model` and sys prompt `sysp`"
if not model: model = models[-1]
nm = str(nm)
if not shell: shell = get_ipython()
r = AiMagic(model, shell)

js = '''require(['base/js/namespace', 'notebook/js/codecell'], function(j, c) {
c.CodeCell.options_default.highlight_modes['magic_markdown'] = {'reg':['^%%ai']} ;
j.notebook.get_cells().forEach(function(cell){
if (cell.cell_type === 'code'){ cell.auto_highlight(); }
});
});'''
display(Javascript(js))
clear_output()

def f(line, cell=None, pre=''): return r.cell(line, cell, pre)
shell.register_magic_function(partial(f, pre=''), 'line_cell', nm)
shell.register_magic_function(partial(f, pre='```python'), 'line_cell', nm+'p')
shell.register_magic_function(partial(f, pre='```\n%%bash\n'), 'line_cell', nm+'s')
shell.register_magic_function(partial(f, pre='```\n%%javascript\n'), 'line_cell', nm+'j')
shell.register_magic_function(partial(f, pre='- '), 'line_cell', nm+'o')

# %% ../00_core.ipynb 63
def replace_req(cells, replace):
cell = cells[-1]
st,*_ = cell['source'].splitlines(True)
cell['source'] = '\n'.join([st,replace])

# %% ../00_core.ipynb 65
@patch
def explain(self:AiMagic, cells): replace_req(cells, "Explain clearly and concisely what's happening here.")

# %% ../00_core.ipynb 66
@patch
def fix(self:AiMagic, cells): replace_req(cells, "Provide code to fix the most recent error in the notebook.")

0 comments on commit ee751ad

Please sign in to comment.