diff --git a/.gitignore b/.gitignore index af0f9d8..17f21d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.gitattributes _proc/ _quarto.yml sidebar.yml diff --git a/claudia/_modidx.py b/claudia/_modidx.py index 7dbf97d..2f3c572 100644 --- a/claudia/_modidx.py +++ b/claudia/_modidx.py @@ -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')}}} diff --git a/claudia/core.py b/claudia/core.py index 1e2ce8b..5099270 100644 --- a/claudia/core.py +++ b/claudia/core.py @@ -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" @@ -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): @@ -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): @@ -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_go_here} - - - -{prompt_goes_here} -''' - -_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