Skip to content

Commit 7f347b8

Browse files
committed
Merge branch 'release/v1.1.7'
2 parents e7a9b54 + a6b40fe commit 7f347b8

37 files changed

Lines changed: 2808 additions & 29 deletions

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ go get -u github.com/bububa/atomic-agents
4747
Atomic Agents with the following main components:
4848

4949
1. `agents/`: The core Atomic Agents library
50-
2. `components/`: The Atomic Agents components contains `Message`, `Memory`, `SystemPromptGenerator`, `SystemPromptContextProvider` utilities
50+
2. `components/`: The Atomic Agents components
51+
52+
- `message`: Defines the Message structure for input/output
53+
- `memory`: Defines a in memory Memory Store
54+
- `systemprompt`: Contains SystemPrompt `Generator` and `ContextProvider`
55+
- `embedder`: Defines the embedder interface, contains several `Provider` including `OpenAI`, `Gemini`, `VoyageAI`, `HuggingFace`, `Cohere` implementations
56+
- `vectordb`: Defines a vectordb interface, contains several `Provider`s including `Memory`, `Chromem`, `Milvus`
57+
- `document` Defines a `Document` interface use for RAG, implemented `File`, `Http` document types. Provide a `Parser` interface which transform document content into specific string with `PDFParser` and `HTML to markdown` parsers implementations.
58+
5159
3. `schema/`: Defines the Input/Output schema structures and interfaces
5260
4. `examples/`: Example projects showcasing Atomic Agents usage
5361
5. `tools/`: A collection of tools that can be used with Atomic Agents

components/document/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package document contains Document structs and Parsers prepare for RAG
2+
package document

components/document/document.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package document
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
)
7+
8+
var ErrReading = errors.New("document is reading")
9+
10+
type ReadStatus = int32
11+
12+
const (
13+
Unread ReadStatus = iota
14+
Reading
15+
ReadCompleted
16+
)
17+
18+
type ReadableDocument interface {
19+
ReadAll() error
20+
Read() (chan<- []byte, error)
21+
}
22+
23+
type ClosableDocument interface {
24+
Close() error
25+
}
26+
27+
// Document is a document container with metadata
28+
type Document struct {
29+
buffer *bytes.Buffer
30+
Meta map[string]string
31+
}
32+
33+
func (d *Document) Reader() *bytes.Reader {
34+
return bytes.NewReader(d.buffer.Bytes())
35+
}

components/document/file.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package document
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"os"
8+
"strconv"
9+
10+
"go.uber.org/atomic"
11+
)
12+
13+
type File struct {
14+
status *atomic.Int32
15+
fp *os.File
16+
Document
17+
}
18+
19+
var (
20+
_ ReadableDocument = (*File)(nil)
21+
_ ClosableDocument = (*File)(nil)
22+
)
23+
24+
func NewFile(fname string) (*File, error) {
25+
fp, err := os.Open(fname)
26+
if err != nil {
27+
return nil, err
28+
}
29+
fileInfo, err := fp.Stat()
30+
if err != nil {
31+
return nil, err
32+
}
33+
if fileInfo.IsDir() {
34+
return nil, errors.New("FileDocument could not be a directory")
35+
}
36+
return &File{
37+
status: atomic.NewInt32(Unread),
38+
fp: fp,
39+
Document: Document{
40+
buffer: new(bytes.Buffer),
41+
Meta: map[string]string{
42+
"filename": fileInfo.Name(),
43+
"modtime": strconv.FormatInt(fileInfo.ModTime().Unix(), 10),
44+
},
45+
},
46+
}, nil
47+
}
48+
49+
func (d *File) ReadStatus() ReadStatus {
50+
return d.status.Load()
51+
}
52+
53+
func (d *File) ReadAll() error {
54+
if d.ReadStatus() == Reading {
55+
return ErrReading
56+
} else if d.ReadStatus() == ReadCompleted {
57+
return nil
58+
}
59+
if _, err := io.Copy(d.buffer, d.fp); err != nil {
60+
d.status.Store(Unread)
61+
return err
62+
}
63+
d.status.Store(ReadCompleted)
64+
return nil
65+
}
66+
67+
func (d *File) Read() (chan<- []byte, error) {
68+
ch := make(chan<- []byte)
69+
if d.ReadStatus() == Reading {
70+
return nil, ErrReading
71+
} else if d.ReadStatus() == ReadCompleted {
72+
go func() {
73+
defer close(ch)
74+
d.status.Store(Reading)
75+
reader := bytes.NewReader(d.buffer.Bytes())
76+
tmp := make([]byte, 1024)
77+
for {
78+
n, err := reader.Read(tmp)
79+
if err != nil {
80+
d.status.Store(ReadCompleted)
81+
return
82+
}
83+
bs := make([]byte, n)
84+
copy(bs, tmp[:n])
85+
ch <- bs
86+
}
87+
}()
88+
return ch, nil
89+
}
90+
go func() {
91+
defer close(ch)
92+
d.status.Store(Reading)
93+
tmp := make([]byte, 1024)
94+
for {
95+
n, err := d.fp.Read(tmp)
96+
if err != nil {
97+
if errors.Is(err, io.EOF) {
98+
d.status.Store(ReadCompleted)
99+
} else {
100+
d.buffer.Reset()
101+
d.status.Store(Unread)
102+
}
103+
return
104+
}
105+
d.buffer.Write(tmp[:n])
106+
}
107+
}()
108+
return ch, nil
109+
}
110+
111+
func (d *File) Close() error {
112+
return d.fp.Close()
113+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package document
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"io"
7+
8+
htmltomarkdown "github.com/JohannesKaufmann/html-to-markdown/v2"
9+
"github.com/JohannesKaufmann/html-to-markdown/v2/converter"
10+
)
11+
12+
// HTML2MDParser is a parser which parse html content to markdown
13+
type HTML2MDParser struct {
14+
opts []converter.ConvertOptionFunc
15+
}
16+
17+
var _ Parser = (*PDFParser)(nil)
18+
19+
func NewHTML2MDParser(opts ...converter.ConvertOptionFunc) *HTML2MDParser {
20+
return &HTML2MDParser{
21+
opts: opts,
22+
}
23+
}
24+
25+
// Parse try to parse a html content from a bytes.Reader into a markdown content then write to an io.Writer
26+
func (h *HTML2MDParser) Parse(ctx context.Context, reader *bytes.Reader, writer io.Writer) error {
27+
bs, err := htmltomarkdown.ConvertReader(reader, h.opts...)
28+
if err != nil {
29+
return err
30+
}
31+
_, err = writer.Write(bs)
32+
return err
33+
}

components/document/http.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package document
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"net/http"
8+
9+
"go.uber.org/atomic"
10+
)
11+
12+
type Http struct {
13+
status *atomic.Int32
14+
client *http.Client
15+
httpReq *http.Request
16+
Document
17+
}
18+
19+
var (
20+
_ ReadableDocument = (*File)(nil)
21+
_ ClosableDocument = (*File)(nil)
22+
)
23+
24+
type HttpConfig struct {
25+
client *http.Client
26+
link string
27+
method string
28+
payload io.Reader
29+
}
30+
31+
type HttpOption func(*HttpConfig)
32+
33+
func WithHttpMethod(method string) HttpOption {
34+
return func(h *HttpConfig) {
35+
h.method = method
36+
}
37+
}
38+
39+
func WithHttpURL(link string) HttpOption {
40+
return func(h *HttpConfig) {
41+
h.link = link
42+
}
43+
}
44+
45+
func WithPayload(payload io.Reader) HttpOption {
46+
return func(h *HttpConfig) {
47+
h.payload = payload
48+
}
49+
}
50+
51+
func WithHttpClient(client *http.Client) HttpOption {
52+
return func(h *HttpConfig) {
53+
h.client = client
54+
}
55+
}
56+
57+
func NewHttp(opts ...HttpOption) (*Http, error) {
58+
var cfg HttpConfig
59+
for _, opt := range opts {
60+
opt(&cfg)
61+
}
62+
if cfg.client == nil {
63+
cfg.client = http.DefaultClient
64+
}
65+
httpReq, err := http.NewRequest(cfg.method, cfg.link, cfg.payload)
66+
if err != nil {
67+
return nil, err
68+
}
69+
return &Http{
70+
status: atomic.NewInt32(Unread),
71+
client: cfg.client,
72+
httpReq: httpReq,
73+
Document: Document{
74+
buffer: new(bytes.Buffer),
75+
Meta: map[string]string{
76+
"url": cfg.link,
77+
"method": cfg.method,
78+
},
79+
},
80+
}, nil
81+
}
82+
83+
func (h *Http) ReadStatus() ReadStatus {
84+
return h.status.Load()
85+
}
86+
87+
func (h *Http) ReadAll() error {
88+
if h.ReadStatus() == Reading {
89+
return ErrReading
90+
} else if h.ReadStatus() == ReadCompleted {
91+
return nil
92+
}
93+
httpResp, err := h.client.Do(h.httpReq)
94+
if err != nil {
95+
h.status.Store(Unread)
96+
return err
97+
}
98+
defer httpResp.Body.Close()
99+
if _, err = io.Copy(h.buffer, httpResp.Body); err != nil {
100+
h.status.Store(Unread)
101+
}
102+
h.status.Store(ReadCompleted)
103+
return nil
104+
}
105+
106+
func (h *Http) Read() (chan<- []byte, error) {
107+
ch := make(chan<- []byte)
108+
if h.ReadStatus() == Reading {
109+
return nil, ErrReading
110+
} else if h.ReadStatus() == ReadCompleted {
111+
go func() {
112+
defer close(ch)
113+
h.status.Store(Reading)
114+
reader := bytes.NewReader(h.buffer.Bytes())
115+
tmp := make([]byte, 1024)
116+
for {
117+
n, err := reader.Read(tmp)
118+
if err != nil {
119+
h.status.Store(ReadCompleted)
120+
return
121+
}
122+
bs := make([]byte, n)
123+
copy(bs, tmp[:n])
124+
ch <- bs
125+
}
126+
}()
127+
return ch, nil
128+
}
129+
go func() {
130+
defer close(ch)
131+
h.status.Store(Reading)
132+
httpResp, err := h.client.Do(h.httpReq)
133+
if err != nil {
134+
h.status.Store(Unread)
135+
return
136+
}
137+
defer httpResp.Body.Close()
138+
tmp := make([]byte, 1024)
139+
for {
140+
n, err := httpResp.Body.Read(tmp)
141+
if err != nil {
142+
if errors.Is(err, io.EOF) {
143+
h.status.Store(ReadCompleted)
144+
} else {
145+
h.buffer.Reset()
146+
h.status.Store(Unread)
147+
}
148+
return
149+
}
150+
h.buffer.Write(tmp[:n])
151+
}
152+
}()
153+
return ch, nil
154+
}

components/document/parser.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package document
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"io"
7+
)
8+
9+
type Parser interface {
10+
Parse(context.Context, *bytes.Reader, io.Writer) error
11+
}

0 commit comments

Comments
 (0)