Skip to content

Commit 938e7c2

Browse files
committed
Updated to include NewsAPI
1 parent c8386a5 commit 938e7c2

File tree

11 files changed

+632
-0
lines changed

11 files changed

+632
-0
lines changed

cmd/agent/main.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
client "github.com/mutablelogic/go-client"
1313
llm "github.com/mutablelogic/go-llm"
1414
agent "github.com/mutablelogic/go-llm/pkg/agent"
15+
"github.com/mutablelogic/go-llm/pkg/newsapi"
16+
"github.com/mutablelogic/go-llm/pkg/tool"
1517
)
1618

1719
////////////////////////////////////////////////////////////////////////////////
@@ -26,6 +28,9 @@ type Globals struct {
2628
Ollama `embed:"" help:"Ollama configuration"`
2729
Anthropic `embed:"" help:"Anthropic configuration"`
2830

31+
// Tools
32+
NewsAPI `embed:"" help:"NewsAPI configuration"`
33+
2934
// Context
3035
ctx context.Context
3136
agent llm.Agent
@@ -40,6 +45,10 @@ type Anthropic struct {
4045
AnthropicKey string `env:"ANTHROPIC_API_KEY" help:"Anthropic API Key"`
4146
}
4247

48+
type NewsAPI struct {
49+
NewsKey string `env:"NEWSAPI_KEY" help:"News API Key"`
50+
}
51+
4352
type CLI struct {
4453
Globals
4554

@@ -93,6 +102,20 @@ func main() {
93102
opts = append(opts, agent.WithAnthropic(cli.AnthropicKey, clientopts...))
94103
}
95104

105+
// Make a toolkit
106+
toolkit := tool.NewToolKit()
107+
opts = append(opts, agent.WithToolKit(toolkit))
108+
109+
// NewsAPI
110+
if cli.NewsKey != "" {
111+
if client, err := newsapi.New(cli.NewsKey, clientopts...); err != nil {
112+
cmd.FatalIfErrorf(err)
113+
} else if err := client.RegisterWithToolKit(toolkit); err != nil {
114+
cmd.FatalIfErrorf(err)
115+
}
116+
}
117+
118+
// Create the agent
96119
agent, err := agent.New(opts...)
97120
cmd.FatalIfErrorf(err)
98121
cli.Globals.agent = agent

pkg/newsapi/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# NewsAPI Client
2+
3+
This package provides a client for the NewsAPI API, which is used to interact with the NewsAPI service.
4+
5+
References:
6+
7+
- API https://newsapi.org/docs
8+
- Package https://pkg.go.dev/github.com/mutablelogic/go-llm/pkg/newsapi

pkg/newsapi/agent.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package newsapi
2+
3+
import (
4+
"context"
5+
6+
// Packages
7+
llm "github.com/mutablelogic/go-llm"
8+
)
9+
10+
///////////////////////////////////////////////////////////////////////////////
11+
// TYPES
12+
13+
type headlines struct {
14+
*Client `json:"-"`
15+
}
16+
17+
var _ llm.Tool = (*headlines)(nil)
18+
19+
///////////////////////////////////////////////////////////////////////////////
20+
// HEADLINES
21+
22+
func (headlines) Name() string {
23+
return "current_headlines"
24+
}
25+
26+
func (headlines) Description() string {
27+
return "Return the current news headlines"
28+
}
29+
30+
func (headlines *headlines) Run(ctx context.Context) (any, error) {
31+
response, err := headlines.Headlines(OptCategory("general"), OptLimit(5))
32+
if err != nil {
33+
return nil, err
34+
}
35+
return map[string]any{
36+
"type": "text",
37+
"headlines": response,
38+
}, nil
39+
}
40+
41+
/*
42+
// Return all the agent tools for the weatherapi
43+
func (c *Client) Tools() []agent.Tool {
44+
return []agent.Tool{
45+
&tool{
46+
name: "current_headlines",
47+
description: "Return the current news headlines",
48+
run: c.agentCurrentHeadlines,
49+
}, &tool{
50+
name: "current_headlines_country",
51+
description: "Return the current news headlines for a country",
52+
run: c.agentCountryHeadlines,
53+
params: []agent.ToolParameter{
54+
{
55+
Name: "countrycode",
56+
Description: "The two-letter country code to return headlines for",
57+
Required: true,
58+
},
59+
},
60+
}, &tool{
61+
name: "current_headlines_category",
62+
description: "Return the current news headlines for a business, entertainment, health, science, sports or technology",
63+
run: c.agentCategoryHeadlines,
64+
params: []agent.ToolParameter{
65+
{
66+
Name: "category",
67+
Description: "business, entertainment, health, science, sports, technology",
68+
Required: true,
69+
},
70+
},
71+
}, &tool{
72+
name: "search_news",
73+
description: "Return the news headlines with a search query",
74+
run: c.agentSearchNews,
75+
params: []agent.ToolParameter{
76+
{
77+
Name: "query",
78+
Description: "A phrase used to search for news headlines",
79+
Required: true,
80+
},
81+
},
82+
},
83+
}
84+
}
85+
86+
///////////////////////////////////////////////////////////////////////////////
87+
// PRIVATE METHODS - TOOL
88+
89+
func (*tool) Provider() string {
90+
return "newsapi"
91+
}
92+
93+
func (t *tool) Name() string {
94+
return t.name
95+
}
96+
97+
func (t *tool) Description() string {
98+
return t.description
99+
}
100+
101+
func (t *tool) Params() []agent.ToolParameter {
102+
return t.params
103+
}
104+
105+
func (t *tool) Run(ctx context.Context, call *agent.ToolCall) (*agent.ToolResult, error) {
106+
return t.run(ctx, call)
107+
}
108+
109+
///////////////////////////////////////////////////////////////////////////////
110+
// PRIVATE METHODS - TOOL
111+
112+
// Return the current general headlines
113+
func (c *Client) agentCurrentHeadlines(_ context.Context, call *agent.ToolCall) (*agent.ToolResult, error) {
114+
response, err := c.Headlines(OptCategory("general"), OptLimit(5))
115+
if err != nil {
116+
return nil, err
117+
}
118+
return &agent.ToolResult{
119+
Id: call.Id,
120+
Result: map[string]any{
121+
"type": "text",
122+
"headlines": response,
123+
},
124+
}, nil
125+
}
126+
127+
// Return the headlines for a specific country
128+
func (c *Client) agentCountryHeadlines(_ context.Context, call *agent.ToolCall) (*agent.ToolResult, error) {
129+
country, err := call.String("countrycode")
130+
if err != nil {
131+
return nil, err
132+
}
133+
country = strings.ToLower(country)
134+
response, err := c.Headlines(OptCountry(country), OptLimit(5))
135+
if err != nil {
136+
return nil, err
137+
}
138+
return &agent.ToolResult{
139+
Id: call.Id,
140+
Result: map[string]any{
141+
"type": "text",
142+
"country": country,
143+
"headlines": response,
144+
},
145+
}, nil
146+
}
147+
148+
// Return the headlines for a specific category
149+
func (c *Client) agentCategoryHeadlines(_ context.Context, call *agent.ToolCall) (*agent.ToolResult, error) {
150+
category, err := call.String("category")
151+
if err != nil {
152+
return nil, err
153+
}
154+
category = strings.ToLower(category)
155+
response, err := c.Headlines(OptCategory(category), OptLimit(5))
156+
if err != nil {
157+
return nil, err
158+
}
159+
return &agent.ToolResult{
160+
Id: call.Id,
161+
Result: map[string]any{
162+
"type": "text",
163+
"category": category,
164+
"headlines": response,
165+
},
166+
}, nil
167+
}
168+
169+
// Return the headlines for a specific query
170+
func (c *Client) agentSearchNews(_ context.Context, call *agent.ToolCall) (*agent.ToolResult, error) {
171+
query, err := call.String("query")
172+
if err != nil {
173+
return nil, err
174+
}
175+
response, err := c.Articles(OptQuery(query), OptLimit(5))
176+
if err != nil {
177+
return nil, err
178+
}
179+
return &agent.ToolResult{
180+
Id: call.Id,
181+
Result: map[string]any{
182+
"type": "text",
183+
"query": query,
184+
"headlines": response,
185+
},
186+
}, nil
187+
}
188+
*/

pkg/newsapi/articles.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package newsapi
2+
3+
import (
4+
"time"
5+
6+
// Packages
7+
"github.com/mutablelogic/go-client"
8+
9+
// Namespace imports
10+
. "github.com/djthorpe/go-errors"
11+
)
12+
13+
///////////////////////////////////////////////////////////////////////////////
14+
// TYPES
15+
16+
type Article struct {
17+
Source Source `json:"source"`
18+
Title string `json:"title"`
19+
Author string `json:"author,omitempty"`
20+
Description string `json:"description,omitempty"`
21+
Url string `json:"url,omitempty"`
22+
ImageUrl string `json:"urlToImage,omitempty"`
23+
PublishedAt time.Time `json:"publishedAt,omitempty"`
24+
Content string `json:"content,omitempty"`
25+
}
26+
27+
type respArticles struct {
28+
Status string `json:"status"`
29+
Code string `json:"code,omitempty"`
30+
Message string `json:"message,omitempty"`
31+
TotalResults int `json:"totalResults"`
32+
Articles []Article `json:"articles"`
33+
}
34+
35+
///////////////////////////////////////////////////////////////////////////////
36+
// PUBLIC METHODS
37+
38+
// Returns headlines
39+
func (c *Client) Headlines(opt ...Opt) ([]Article, error) {
40+
var response respArticles
41+
var query opts
42+
43+
// Add options
44+
for _, opt := range opt {
45+
if err := opt(&query); err != nil {
46+
return nil, err
47+
}
48+
}
49+
50+
// Request -> Response
51+
if err := c.Do(nil, &response, client.OptPath("top-headlines"), client.OptQuery(query.Values())); err != nil {
52+
return nil, err
53+
} else if response.Status != "ok" {
54+
return nil, ErrBadParameter.Withf("%s: %s", response.Code, response.Message)
55+
}
56+
57+
// Return success
58+
return response.Articles, nil
59+
}
60+
61+
// Returns articles
62+
func (c *Client) Articles(opt ...Opt) ([]Article, error) {
63+
var response respArticles
64+
var query opts
65+
66+
// Add options
67+
for _, opt := range opt {
68+
if err := opt(&query); err != nil {
69+
return nil, err
70+
}
71+
}
72+
73+
// Request -> Response
74+
if err := c.Do(nil, &response, client.OptPath("everything"), client.OptQuery(query.Values())); err != nil {
75+
return nil, err
76+
} else if response.Status != "ok" {
77+
return nil, ErrBadParameter.Withf("%s: %s", response.Code, response.Message)
78+
}
79+
80+
// Return success
81+
return response.Articles, nil
82+
}

pkg/newsapi/articles_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package newsapi_test
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"testing"
7+
8+
// Packages
9+
opts "github.com/mutablelogic/go-client"
10+
newsapi "github.com/mutablelogic/go-llm/pkg/newsapi"
11+
assert "github.com/stretchr/testify/assert"
12+
)
13+
14+
func Test_articles_001(t *testing.T) {
15+
assert := assert.New(t)
16+
client, err := newsapi.New(GetApiKey(t), opts.OptTrace(os.Stderr, true))
17+
assert.NoError(err)
18+
19+
articles, err := client.Headlines(newsapi.OptQuery("google"))
20+
assert.NoError(err)
21+
assert.NotNil(articles)
22+
23+
body, _ := json.MarshalIndent(articles, "", " ")
24+
t.Log(string(body))
25+
}
26+
27+
func Test_articles_002(t *testing.T) {
28+
assert := assert.New(t)
29+
client, err := newsapi.New(GetApiKey(t), opts.OptTrace(os.Stderr, true))
30+
assert.NoError(err)
31+
32+
articles, err := client.Articles(newsapi.OptQuery("google"), newsapi.OptLimit(1))
33+
assert.NoError(err)
34+
assert.NotNil(articles)
35+
36+
body, _ := json.MarshalIndent(articles, "", " ")
37+
t.Log(string(body))
38+
}

0 commit comments

Comments
 (0)