Skip to content

Commit

Permalink
Merge pull request #2 from AnswerDotAI/anthropic-pdf-support
Browse files Browse the repository at this point in the history
add pdf support for Claude
  • Loading branch information
comhar authored Nov 4, 2024
2 parents 6ac1a86 + 148e8bd commit fa3a567
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 6 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,38 @@ This generates the expected cache block below
}
```

#### PDF chats

*msglm* offers PDF
[support](https://docs.anthropic.com/en/docs/build-with-claude/pdf-support)
for Anthropic. Just like an image chat all you need to do is pass the
raw pdf bytes in a list with your question to *mk_msg* and it will
generate the correct format as shown in the example below.

``` python
import httpx
from msglm import mk_msg_anthropic as mk_msg
from anthropic import Anthropic

client = Anthropic(default_headers={'anthropic-beta': 'pdfs-2024-09-25'})

url = "https://assets.anthropic.com/m/1cd9d098ac3e6467/original/Claude-3-Model-Card-October-Addendum.pdf"
pdf = httpx.get(url).content

r = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[mk_msg([pdf, "Which model has the highest human preference win rates across each use-case?"])]
)
print(r.content[0].text)
```

Note: this feature is currently in beta so you’ll need to:

- use the Anthropic beta client
(e.g. `anthropic.Anthropic(default_headers={'anthropic-beta': 'pdfs-2024-09-25'})`)
- use the `claude-3-5-sonnet-20241022` model

### Summary

We hope *msglm* will make your life a little easier when chatting to
Expand Down
5 changes: 5 additions & 0 deletions msglm/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@
'msglm.core.AnthropicMsg.find_block': ('core.html#anthropicmsg.find_block', 'msglm/core.py'),
'msglm.core.AnthropicMsg.img_msg': ('core.html#anthropicmsg.img_msg', 'msglm/core.py'),
'msglm.core.AnthropicMsg.is_sdk_obj': ('core.html#anthropicmsg.is_sdk_obj', 'msglm/core.py'),
'msglm.core.AnthropicMsg.pdf_msg': ('core.html#anthropicmsg.pdf_msg', 'msglm/core.py'),
'msglm.core.Msg': ('core.html#msg', 'msglm/core.py'),
'msglm.core.Msg.__call__': ('core.html#msg.__call__', 'msglm/core.py'),
'msglm.core.Msg.find_block': ('core.html#msg.find_block', 'msglm/core.py'),
'msglm.core.Msg.img_msg': ('core.html#msg.img_msg', 'msglm/core.py'),
'msglm.core.Msg.is_sdk_obj': ('core.html#msg.is_sdk_obj', 'msglm/core.py'),
'msglm.core.Msg.mk_content': ('core.html#msg.mk_content', 'msglm/core.py'),
'msglm.core.Msg.pdf_msg': ('core.html#msg.pdf_msg', 'msglm/core.py'),
'msglm.core.Msg.text_msg': ('core.html#msg.text_msg', 'msglm/core.py'),
'msglm.core.OpenAiMsg': ('core.html#openaimsg', 'msglm/core.py'),
'msglm.core.OpenAiMsg.find_block': ('core.html#openaimsg.find_block', 'msglm/core.py'),
'msglm.core.OpenAiMsg.img_msg': ('core.html#openaimsg.img_msg', 'msglm/core.py'),
'msglm.core.OpenAiMsg.is_sdk_obj': ('core.html#openaimsg.is_sdk_obj', 'msglm/core.py'),
'msglm.core._add_cache_control': ('core.html#_add_cache_control', 'msglm/core.py'),
'msglm.core._is_img': ('core.html#_is_img', 'msglm/core.py'),
'msglm.core._is_pdf': ('core.html#_is_pdf', 'msglm/core.py'),
'msglm.core._mk_img': ('core.html#_mk_img', 'msglm/core.py'),
'msglm.core._mk_pdf': ('core.html#_mk_pdf', 'msglm/core.py'),
'msglm.core.mk_msg': ('core.html#mk_msg', 'msglm/core.py'),
'msglm.core.mk_msg_anthropic': ('core.html#mk_msg_anthropic', 'msglm/core.py'),
'msglm.core.mk_msgs': ('core.html#mk_msgs', 'msglm/core.py'),
Expand Down
23 changes: 22 additions & 1 deletion msglm/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ def _mk_img(data:bytes)->tuple:
mtype = mimetypes.types_map["."+imghdr.what(None, h=data)]
return img, mtype

# %% ../nbs/00_core.ipynb
def _is_img(data): return isinstance(data, bytes) and bool(imghdr.what(None, data))

# %% ../nbs/00_core.ipynb
def _is_pdf(data): return isinstance(data, bytes) and data.startswith(b'%PDF-')

# %% ../nbs/00_core.ipynb
def _mk_pdf(data:bytes)->str:
"Convert pdf bytes to a base64 encoded pdf"
return base64.standard_b64encode(data).decode("utf-8")

# %% ../nbs/00_core.ipynb
def mk_msg(content:Union[list,str], role:str="user", *args, api:str="openai", **kw)->dict:
"Create an OpenAI/Anthropic compatible message."
Expand Down Expand Up @@ -64,10 +75,15 @@ def img_msg(self, *args, **kw)->dict:
"Convert bytes to an image message"
raise NotImplemented

def pdf_msg(self, *args, **kw)->dict:
"Convert bytes to a pdf message"
raise NotImplemented

def mk_content(self, content:[str, bytes], text_only:bool=False) -> dict:
"Create the appropriate data structure based the content type."
if isinstance(content, str): return self.text_msg(content, text_only=text_only)
if isinstance(content, bytes): return self.img_msg(content)
if _is_img(content): return self.img_msg(content)
if _is_pdf(content): return self.pdf_msg(content)
return content

# %% ../nbs/00_core.ipynb
Expand All @@ -79,6 +95,11 @@ def img_msg(self, data: bytes) -> dict:
r = {"type": "base64", "media_type": mtype, "data":img}
return {"type": "image", "source": r}

def pdf_msg(self, data: bytes) -> dict:
"Convert `data` to a pdf message"
r = {"type": "base64", "media_type": "application/pdf", "data":_mk_pdf(data)}
return {"type": "document", "source": r}

def is_sdk_obj(self, r)-> bool:
"Check if `r` is an SDK object."
return isinstance(r, abc.Mapping)
Expand Down
Loading

0 comments on commit fa3a567

Please sign in to comment.