Refactor search functionality and remove 'think' strategy
Reorganized search logic into a dedicated `search.go` file for better modularity. Removed the 'think' functionality and related options (`MaxThinks`) to simplify the codebase. Enhanced the search function and updated tool functions to reflect the streamlined approach.
This commit is contained in:
parent
a5a669f9a6
commit
bc56213d5a
2
go.mod
2
go.mod
@ -8,7 +8,7 @@ replace github.com/rocketlaunchr/google-search => github.com/chrisjoyce911/googl
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.stevedudenhoeffer.com/steve/go-extractor v0.0.0-20250315044602-7c0e44a22f2c
|
gitea.stevedudenhoeffer.com/steve/go-extractor v0.0.0-20250315044602-7c0e44a22f2c
|
||||||
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250317023858-7f5e34e437a7
|
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250317041832-2737a5b2be93
|
||||||
github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d
|
github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d
|
||||||
github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275
|
github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
|
@ -4,13 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/Edw590/go-wolfram"
|
"github.com/Edw590/go-wolfram"
|
||||||
"go.starlark.net/lib/math"
|
"go.starlark.net/lib/math"
|
||||||
"go.starlark.net/starlark"
|
"go.starlark.net/starlark"
|
||||||
"go.starlark.net/syntax"
|
"go.starlark.net/syntax"
|
||||||
"log/slog"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/cache"
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/cache"
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor"
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor"
|
||||||
@ -49,11 +49,6 @@ type Options struct {
|
|||||||
// search up to 5 possible times to find an answer.
|
// search up to 5 possible times to find an answer.
|
||||||
MaxSearches int
|
MaxSearches int
|
||||||
|
|
||||||
// MaxThinks is the maximum number of times to think about a question. A "Think" is different than a search in that
|
|
||||||
// the LLM just breaks the question down into smaller parts and tries to answer them. This is useful for complex
|
|
||||||
// questions that are hard to answer since LLMs are better at answering smaller questions.
|
|
||||||
MaxThinks int
|
|
||||||
|
|
||||||
// MaxTries is the absolute maximum number of pages to try to get an answer from. For instance, if MaxSearches is 5 and
|
// MaxTries is the absolute maximum number of pages to try to get an answer from. For instance, if MaxSearches is 5 and
|
||||||
// 5 pages are tried and no answers are found, the function will return ErrMaxTries.
|
// 5 pages are tried and no answers are found, the function will return ErrMaxTries.
|
||||||
MaxTries int
|
MaxTries int
|
||||||
@ -78,8 +73,7 @@ type Options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var DefaultOptions = Options{
|
var DefaultOptions = Options{
|
||||||
MaxSearches: 5,
|
MaxSearches: 10,
|
||||||
MaxThinks: 10,
|
|
||||||
MaxTries: 5,
|
MaxTries: 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,131 +185,23 @@ func doesTextAnswerQuestion(ctx *gollm.Context, q Question, text string) (string
|
|||||||
return req.Toolbox.Execute(ctx, res.Choices[0].Calls[0])
|
return req.Toolbox.Execute(ctx, res.Choices[0].Calls[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func functionSearch(ctx *gollm.Context, q Question, searchTerm string) (string, error) {
|
|
||||||
slog.Info("searching", "search", searchTerm, "question", q)
|
|
||||||
res, err := q.Search.Search(ctx, searchTerm)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) == 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// first pass try to see if any provide the result without needing archive
|
|
||||||
for _, r := range res {
|
|
||||||
trimmed := strings.TrimSpace(r.URL)
|
|
||||||
if trimmed == "" {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("extracting article", "url", trimmed)
|
|
||||||
u, err := url.Parse(trimmed)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
a, err := extractArticle(ctx, q.Cache, u)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("extracted article", "url", a.URL, "title", a.Title, "body", a.Body)
|
|
||||||
|
|
||||||
if a.Title != "" && a.Body != "" {
|
|
||||||
answer, err := doesTextAnswerQuestion(ctx, q, a.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("error checking if text answers question", "question", q.Question, "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if answer != "" {
|
|
||||||
return answer, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func functionThink(ctx *gollm.Context, q Question) (string, error) {
|
|
||||||
fnAnswer := gollm.NewFunction(
|
|
||||||
"answer",
|
|
||||||
"Answer the question.",
|
|
||||||
func(ctx *gollm.Context, args struct {
|
|
||||||
Answer string `description:"the answer to the question"`
|
|
||||||
}) (string, error) {
|
|
||||||
return args.Answer, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
var temp float32 = 0.8
|
|
||||||
req := gollm.Request{
|
|
||||||
Messages: []gollm.Message{
|
|
||||||
{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: "Answer the given question as accurately and concisely as possible using the answer function.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Role: gollm.RoleUser,
|
|
||||||
Text: q.Question,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Toolbox: gollm.NewToolBox(fnAnswer),
|
|
||||||
Temperature: &temp,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := q.Model.ChatComplete(ctx, req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res.Choices) == 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res.Choices[0].Calls) == 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return req.Toolbox.Execute(ctx, res.Choices[0].Calls[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Options) Answer(ctx context.Context, q Question) (string, error) {
|
func (o Options) Answer(ctx context.Context, q Question) (string, error) {
|
||||||
var answer string
|
var answer string
|
||||||
|
|
||||||
fnSearch := gollm.NewFunction(
|
fnSearch := gollm.NewFunction(
|
||||||
"search",
|
"search",
|
||||||
"Search the web for an answer to a question. You can call this function up to "+fmt.Sprint(o.MaxSearches)+" times.",
|
"Search the web for an answer to a question. You can call this function up to "+fmt.Sprint(o.MaxSearches)+` times. The result will be JSON in the format of {"url": "https://example.com", "answer": "the answer to the question"}. If a previous call to search produced no results, do not re-search with just reworded search terms, try a different approach.`,
|
||||||
func(ctx *gollm.Context, args struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
SearchQuery string `description:"what to search the web for for this question"`
|
Query string `description:"search the web with this, such as: 'capital of the united states site:wikipedia.org'"`
|
||||||
Question string `description:"what question(s) you are trying to answer with this search"`
|
Question string `description:"when reading the results, what question(s) are you trying to answer?"`
|
||||||
}) (string, error) {
|
}) (string, error) {
|
||||||
q2 := q
|
q2 := q
|
||||||
q2.Question = args.Question
|
q2.Question = args.Question
|
||||||
|
|
||||||
if o.MaxThinks > 0 {
|
if o.MaxSearches > 0 {
|
||||||
o.MaxSearches = o.MaxSearches - 1
|
o.MaxSearches = o.MaxSearches - 1
|
||||||
}
|
}
|
||||||
return functionSearch(ctx, q2, args.SearchQuery)
|
return functionSearch(ctx, q2, args.Query)
|
||||||
})
|
|
||||||
|
|
||||||
fnThink := gollm.NewFunction(
|
|
||||||
"think",
|
|
||||||
"Think about a question. This is useful for breaking down complex questions into smaller parts that are easier to answer.",
|
|
||||||
func(ctx *gollm.Context, args struct {
|
|
||||||
Question string `json:"question" description:"the question to think about"`
|
|
||||||
}) (string, error) {
|
|
||||||
q2 := q
|
|
||||||
q2.Question = args.Question
|
|
||||||
|
|
||||||
if o.MaxThinks > 0 {
|
|
||||||
o.MaxThinks = o.MaxThinks - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return functionThink(ctx, q2)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
fnAnswer := gollm.NewFunction(
|
fnAnswer := gollm.NewFunction(
|
||||||
@ -361,8 +247,19 @@ func (o Options) Answer(ctx context.Context, q Question) (string, error) {
|
|||||||
return v.String(), nil
|
return v.String(), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
var funcs = []*gollm.Function{fnAnswer, fnCalculate}
|
fnGiveUp := gollm.NewFunction(
|
||||||
|
"give_up",
|
||||||
|
"Indicate that the system has given up on finding an answer.",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Reason string `description:"the reason the system is giving up (e.g.: 'no results found')"`
|
||||||
|
}) (string, error) {
|
||||||
|
answer = "given up: " + args.Reason
|
||||||
|
return "given up", nil
|
||||||
|
})
|
||||||
|
|
||||||
|
var baseFuncs = []*gollm.Function{fnAnswer, fnCalculate, fnGiveUp}
|
||||||
|
|
||||||
|
var funcs = baseFuncs
|
||||||
if fnWolfram != nil {
|
if fnWolfram != nil {
|
||||||
funcs = append(funcs, fnWolfram)
|
funcs = append(funcs, fnWolfram)
|
||||||
}
|
}
|
||||||
@ -371,10 +268,6 @@ func (o Options) Answer(ctx context.Context, q Question) (string, error) {
|
|||||||
funcs = append(funcs, fnSearch)
|
funcs = append(funcs, fnSearch)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.MaxThinks > 0 {
|
|
||||||
funcs = append(funcs, fnThink)
|
|
||||||
}
|
|
||||||
|
|
||||||
var temp float32 = 0.8
|
var temp float32 = 0.8
|
||||||
|
|
||||||
var messages []gollm.Message
|
var messages []gollm.Message
|
||||||
@ -430,14 +323,15 @@ func (o Options) Answer(ctx context.Context, q Question) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(res.Choices) == 0 {
|
|
||||||
return nil, fmt.Errorf("no response choices provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res.Choices) > o.MaxSearches {
|
if len(res.Choices) > o.MaxSearches {
|
||||||
res.Choices = res.Choices[:o.MaxSearches]
|
res.Choices = res.Choices[:o.MaxSearches]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(res.Choices) == 0 {
|
||||||
|
return nil, fmt.Errorf("no response choices provided")
|
||||||
|
}
|
||||||
|
|
||||||
var answers []gollm.ToolCallResponse
|
var answers []gollm.ToolCallResponse
|
||||||
|
|
||||||
choice := res.Choices[0]
|
choice := res.Choices[0]
|
||||||
@ -490,6 +384,19 @@ func (o Options) Answer(ctx context.Context, q Question) (string, error) {
|
|||||||
maxTries := o.MaxTries
|
maxTries := o.MaxTries
|
||||||
|
|
||||||
for i := 0; i < maxTries; i++ {
|
for i := 0; i < maxTries; i++ {
|
||||||
|
// rework this run's functions incase MaxSearches etc. have changed
|
||||||
|
var funcs2 = baseFuncs
|
||||||
|
|
||||||
|
if fnWolfram != nil {
|
||||||
|
funcs2 = append(funcs2, fnWolfram)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.MaxSearches > 0 {
|
||||||
|
funcs2 = append(funcs2, fnSearch)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Toolbox = gollm.NewToolBox(funcs2...)
|
||||||
|
|
||||||
newReq, err := runAnswer(o, req)
|
newReq, err := runAnswer(o, req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
82
pkg/answer/search.go
Normal file
82
pkg/answer/search.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package answer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type searchResults struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Answer string `json:"answer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s searchResults) String() (string, error) {
|
||||||
|
b, err := json.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalSearch(ctx *gollm.Context, q Question, searchTerm string) (searchResults, error) {
|
||||||
|
slog.Info("searching", "search", searchTerm, "question", q)
|
||||||
|
results, err := q.Search.Search(ctx, searchTerm)
|
||||||
|
if err != nil {
|
||||||
|
return searchResults{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) == 0 {
|
||||||
|
return searchResults{Url: "not-found", Answer: "no search results found"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// first pass try to see if any provide the result without needing archive
|
||||||
|
for _, r := range results {
|
||||||
|
trimmed := strings.TrimSpace(r.URL)
|
||||||
|
if trimmed == "" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("extracting article", "url", trimmed)
|
||||||
|
u, err := url.Parse(trimmed)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := extractArticle(ctx, q.Cache, u)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("extracted article", "url", a.URL, "title", a.Title, "body", a.Body)
|
||||||
|
|
||||||
|
if a.Title != "" && a.Body != "" {
|
||||||
|
answer, err := doesTextAnswerQuestion(ctx, q, a.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error checking if text answers question", "question", q.Question, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if answer != "" {
|
||||||
|
return searchResults{Url: u.String(), Answer: answer}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchResults{Url: "not-found", Answer: "no searched results answered"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func functionSearch(ctx *gollm.Context, q Question, searchTerm string) (string, error) {
|
||||||
|
res, err := internalSearch(ctx, q, searchTerm)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.String()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user