├── docs
└── rag.md
├── pkg
├── README.md
├── .DS_Store
├── strings
│ └── strings.go
├── structx
│ ├── set.go
│ ├── generic.go
│ └── map.go
├── browser
│ ├── dom
│ │ ├── process_dom_test.go
│ │ └── utils_test.go
│ └── utils.go
└── adb
│ └── manager.go
├── tool
├── adb
│ ├── element.go
│ └── registry.go
├── event.go
├── nil.go
├── search
│ └── engine
│ │ ├── bing.go
│ │ └── engine.go
├── crawl
│ ├── crawler.go
│ ├── tool.go
│ ├── jina.go
│ └── colly.go
├── datetime
│ └── tool.go
├── weather
│ └── weather.go
├── config.go
├── file
│ ├── create_file.go
│ └── write_file.go
├── user
│ └── inquiry.go
├── factory.go
├── browser
│ ├── dom
│ │ ├── process_dom_test.go
│ │ └── utils_test.go
│ └── controller
│ │ └── utils.go
├── entity.go
├── mobile
│ └── controller
│ │ └── action.go
├── think
│ └── think.go
└── wikipedia
│ └── wikipedia.go
├── example
├── memory
│ └── main.go
├── bookkeeper
│ ├── web
│ │ ├── src
│ │ │ ├── vite-env.d.ts
│ │ │ ├── main.tsx
│ │ │ ├── App.css
│ │ │ └── index.css
│ │ ├── tsconfig.json
│ │ ├── .gitignore
│ │ ├── index.html
│ │ ├── vite.config.ts
│ │ ├── tsconfig.node.json
│ │ ├── tsconfig.app.json
│ │ ├── eslint.config.js
│ │ ├── package.json
│ │ ├── public
│ │ │ └── vite.svg
│ │ └── README.md
│ ├── config
│ │ ├── config.yaml
│ │ └── config.go
│ ├── models
│ │ ├── user.go
│ │ └── transaction.go
│ ├── database
│ │ └── database.go
│ ├── api
│ │ ├── chat_handler.go
│ │ └── analysis.go
│ ├── readme
│ └── services
│ │ ├── chat_service.go
│ │ └── image_service.go
├── deepdoc
│ ├── 企业级 SaaS 行业增长白皮书.pdf
│ └── main.go
├── mcp
│ └── mcp_config
│ │ └── servers.json
├── llm
│ ├── moonshot
│ │ └── moonshot.go
│ ├── ollama
│ │ └── ollama.go
│ ├── zhipu
│ │ └── zhipu.go
│ ├── vision
│ │ └── main.go
│ ├── doubao
│ │ └── doubao.go
│ ├── claude
│ │ └── claude.go
│ ├── deepseek
│ │ └── deepseek.go
│ ├── asure-openai
│ │ └── openai.go
│ ├── openai-c
│ │ └── openai-c.go
│ └── qwen
│ │ └── qwen.go
├── agent
│ ├── browser
│ │ └── main.go
│ ├── memory
│ │ └── agent.go
│ └── finance
│ │ └── finance.go
├── rag
│ ├── pgvector
│ │ └── main.go
│ ├── main.go
│ └── milvus
│ │ └── main.go
└── storage
│ └── main.go
├── program
├── completion.go
├── action.go
├── cot.go
├── option.go
├── program.go
├── prompt.go
└── response.go
├── speech
├── tts
│ ├── minmax
│ │ └── basic.go
│ ├── tts.go
│ ├── cache.go
│ ├── tencent
│ │ ├── stream.go
│ │ └── options.go
│ └── aliyun
│ │ ├── utils.go
│ │ ├── handler.go
│ │ └── client.go
├── outbound.go
├── result.go
├── asr.go
├── outbound
│ ├── outbound.go
│ ├── file.go
│ ├── ratelimit.go
│ └── websocket.go
├── entities.go
├── asr
│ ├── tencent
│ │ ├── option.go
│ │ ├── listener.go
│ │ └── tencent.go
│ └── mock.go
├── tts.go
└── encoding.go
├── embedding
├── hunyuan_embedder.go
├── embedding.go
├── string_embedder_test.go
├── string_embedder.go
├── openai_embedder.go
└── onnx_embedder.go
├── rag
├── deepdoc
│ ├── .DS_Store
│ ├── document.go
│ ├── mdx.go
│ ├── extractor
│ │ ├── unknown.go
│ │ ├── extractor.go
│ │ └── document.go
│ ├── txtx.go
│ ├── json.go
│ ├── csv.go
│ ├── docx.go
│ ├── pptx.go
│ ├── xlsx.go
│ ├── chunk
│ │ └── chunk_test.go
│ ├── doc.go
│ └── pdf.go
├── retirval.go
├── rag.go
└── indexer.go
├── workflow
├── engine.go
├── edge.go
├── node
│ ├── vdb.go
│ ├── start.go
│ ├── expr.go
│ ├── node.go
│ ├── factory.go
│ ├── tool.go
│ ├── llm.go
│ └── end.go
├── workflow.go
├── definition.go
└── node.go
├── vdb
├── distance.go
├── option.go
└── vdb.go
├── agent
├── contexts.go
├── response.go
└── memory.go
├── prompt
├── templates
│ ├── simple.json
│ ├── intent.go
│ ├── meta.go
│ └── example.go
├── prompt.go
├── readme
├── format.go
└── simple_format.go
├── llm
├── cache_ranker.go
├── logger.go
├── cache_metrics.go
├── qwen
│ └── llm.go
├── config.go
├── openai-c
│ └── llm.go
├── doubao
│ └── doubao.go
├── anthropic
│ └── claude.go
├── deepseek
│ └── llm.go
├── cache_query.go
├── cache.go
├── mock.go
├── ollama
│ └── llm.go
├── cache_redis.go
└── hook.go
├── memory
├── memory.go
└── memory_item.go
├── vision
├── options.go
├── siliconflow
│ └── vision.go
├── hook.go
└── vision.go
├── .gitignore
├── mcp_config
└── servers.json
├── storage
├── session.go
├── storage.go
├── postgres_store.go
└── json_store.go
└── LICENSE
/docs/rag.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pkg/README.md:
--------------------------------------------------------------------------------
1 | # utils for struct
--------------------------------------------------------------------------------
/tool/adb/element.go:
--------------------------------------------------------------------------------
1 | package adb
2 |
--------------------------------------------------------------------------------
/example/memory/main.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
--------------------------------------------------------------------------------
/program/completion.go:
--------------------------------------------------------------------------------
1 | package program
2 |
--------------------------------------------------------------------------------
/speech/tts/minmax/basic.go:
--------------------------------------------------------------------------------
1 | package minmax
2 |
--------------------------------------------------------------------------------
/embedding/hunyuan_embedder.go:
--------------------------------------------------------------------------------
1 | package embedding
2 |
--------------------------------------------------------------------------------
/pkg/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/showntop/llmack/HEAD/pkg/.DS_Store
--------------------------------------------------------------------------------
/example/bookkeeper/web/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/rag/deepdoc/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/showntop/llmack/HEAD/rag/deepdoc/.DS_Store
--------------------------------------------------------------------------------
/example/deepdoc/企业级 SaaS 行业增长白皮书.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/showntop/llmack/HEAD/example/deepdoc/企业级 SaaS 行业增长白皮书.pdf
--------------------------------------------------------------------------------
/tool/event.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | // Event 事件
4 | type Event struct {
5 | Name string
6 | Type string
7 | Data any
8 | }
9 |
--------------------------------------------------------------------------------
/rag/deepdoc/document.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | // Document ...
4 | type Document struct {
5 | ID string
6 | Name string
7 | }
8 |
--------------------------------------------------------------------------------
/program/action.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | type Action struct {
4 | Name string
5 | Description string
6 | Parameters map[string]any
7 | }
8 |
--------------------------------------------------------------------------------
/workflow/engine.go:
--------------------------------------------------------------------------------
1 | package workflow
2 |
3 | // Executor ...
4 | type Executor interface {
5 | // TODO
6 | }
7 |
8 | func xxx() {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/speech/outbound.go:
--------------------------------------------------------------------------------
1 | package speech
2 |
3 | // Outbound ...
4 | type Outbound interface {
5 | Write([]byte) error
6 | Reset() error
7 | Close() error
8 | }
9 |
--------------------------------------------------------------------------------
/vdb/distance.go:
--------------------------------------------------------------------------------
1 | package vdb
2 |
3 | type Distance string
4 |
5 | const (
6 | DistanceL2 Distance = "l2"
7 | DistanceCosine Distance = "cosine"
8 | )
9 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/example/bookkeeper/config/config.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8080
3 |
4 | database:
5 | host: localhost
6 | port: 5432
7 | user: postgres_admin
8 | password: xxxx
9 | dbname: bookkeeper
10 |
--------------------------------------------------------------------------------
/embedding/embedding.go:
--------------------------------------------------------------------------------
1 | package embedding
2 |
3 | import "context"
4 |
5 | // Embedder ...
6 | type Embedder interface {
7 | Embed(ctx context.Context, text string) ([]float32, error)
8 | Dimension() int
9 | }
10 |
--------------------------------------------------------------------------------
/speech/result.go:
--------------------------------------------------------------------------------
1 | package speech
2 |
3 | import "github.com/showntop/llmack/llm"
4 |
5 | // AudioChunk ...
6 | type AudioChunk struct {
7 | Audio string
8 | Text string
9 | ToolCalls []llm.ToolCall
10 | }
11 |
--------------------------------------------------------------------------------
/speech/asr.go:
--------------------------------------------------------------------------------
1 | package speech
2 |
3 | // ASR ...
4 | type ASR interface {
5 | Recognize(buffer []byte) (string, error)
6 | Input(buffer []byte) error // 流入音频数据, async recognize
7 | Close() error // 流入音频数据, async recognize
8 | }
9 |
--------------------------------------------------------------------------------
/agent/contexts.go:
--------------------------------------------------------------------------------
1 | package agent
2 |
3 | import "time"
4 |
5 | type Memory struct {
6 | ID string `json:"id"`
7 | Content string `json:"content"`
8 | CreatedAt time.Time `json:"created_at"`
9 | UpdatedAt time.Time `json:"updated_at"`
10 | }
11 |
--------------------------------------------------------------------------------
/prompt/templates/simple.json:
--------------------------------------------------------------------------------
1 | {
2 | "context_prompt": "用户在与一个客观的助手对话。助手会尊重找到的材料,给出全面专业的解释,但不会过度演绎。同时回答中不会暴露引用的材料:\n\n```\n{{#context#}}\n```\n",
3 | "system_prompt_orders": [
4 | "context_prompt",
5 | "pre_prompt"
6 | ],
7 | "query_prompt": "{{#query#}}",
8 | "stops": null
9 | }
--------------------------------------------------------------------------------
/example/bookkeeper/web/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import './index.css'
4 | import App from './App.tsx'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/llm/cache_ranker.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | // Scorer 打分
4 | type Scorer interface {
5 | Eval(string) float64
6 | }
7 |
8 | // ExactMatchScorer 精确匹配排序
9 | type ExactMatchScorer struct {
10 | }
11 |
12 | // Eval 精确匹配排序
13 | func (r *ExactMatchScorer) Eval(query string) float64 {
14 | return 1.0
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/strings/strings.go:
--------------------------------------------------------------------------------
1 | package strings
2 |
3 | import "strings"
4 |
5 | // TrimSpecial trim tab \n \r \t 等特殊字符
6 | func TrimSpecial(s string) string {
7 | s = strings.ReplaceAll(s, "\n", " ")
8 | s = strings.ReplaceAll(s, "\r", " ")
9 | s = strings.ReplaceAll(s, "\t", " ")
10 | return s
11 | }
12 |
--------------------------------------------------------------------------------
/tool/nil.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | )
7 |
8 | var NilTool = &Tool{
9 | Name: "nil",
10 | Description: "nil tool",
11 | Kind: "code",
12 | invoke: func(ctx context.Context, args string) (string, error) {
13 | return "", fmt.Errorf("not implement")
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/memory/memory.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // Extra ...
8 | type Extra any
9 |
10 | type Memory interface {
11 | Get(context.Context, string) ([]*MemoryItem, error)
12 | Add(context.Context, string, *MemoryItem) error
13 | FetchHistories(context.Context, string) ([]*MemoryItem, error)
14 | }
15 |
--------------------------------------------------------------------------------
/rag/deepdoc/mdx.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | // MdxDocument ...
4 | type MdxDocument struct {
5 | }
6 |
7 | // Mdx ...
8 | func Mdx() *MdxDocument {
9 | return &MdxDocument{}
10 | }
11 |
12 | // Extract ...
13 | func (d *MdxDocument) Extract(filename string, binary []byte) ([]string, error) {
14 | return []string{string(binary)}, nil
15 | }
16 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/vision/options.go:
--------------------------------------------------------------------------------
1 | package vision
2 |
3 | // InvokeOption is a function that configures a InvokeOptions.
4 | type InvokeOption func(*InvokeOptions)
5 |
6 | // InvokeOptions ...
7 | type InvokeOptions struct {
8 | ApiKey string
9 | }
10 |
11 | // WithApiKey ...
12 | func WithApiKey(apiKey string) InvokeOption {
13 | return func(o *InvokeOptions) {
14 | o.ApiKey = apiKey
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/rag/deepdoc/extractor/unknown.go:
--------------------------------------------------------------------------------
1 | package extractor
2 |
3 | // NewUnknownExtractor ...
4 | func NewUnknownExtractor() *UnknownExtractor {
5 |
6 | return &UnknownExtractor{}
7 | }
8 |
9 | // UnknownExtractor ...
10 | type UnknownExtractor struct {
11 | }
12 |
13 | // Extract ...
14 | func (d *UnknownExtractor) Extract(m *Meta, binary []byte) ([]string, error) {
15 | return nil, nil
16 | }
17 |
--------------------------------------------------------------------------------
/workflow/edge.go:
--------------------------------------------------------------------------------
1 | package workflow
2 |
3 | // Edge TODO
4 | type Edge struct {
5 | ID string `json:"id"` // 线段id
6 | Name string `json:"name,omitempty"`
7 | Source string `json:"source"`
8 | Target string `json:"target"`
9 | ExpressName string `json:"expr_name,omitempty"`
10 | Express string `json:"expr_content,omitempty"`
11 | // Metadata meta `json:"metadata"`
12 | }
13 |
--------------------------------------------------------------------------------
/tool/search/engine/bing.go:
--------------------------------------------------------------------------------
1 | package engine
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // NewBing bing search
8 | func NewBing(apiKey string) Searcher {
9 | return &Serper{apiKey: apiKey}
10 | }
11 |
12 | // Bing search by big
13 | type Bing struct {
14 | apiKey string
15 | }
16 |
17 | // Search 使用serper搜索
18 | func (s *Bing) Search(ctx context.Context, query string) ([]*Result, error) {
19 | return nil, nil
20 | }
21 |
--------------------------------------------------------------------------------
/rag/deepdoc/txtx.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | // TxtxDocument ...
8 | type TxtxDocument struct {
9 | }
10 |
11 | // Txtx ...
12 | func Txtx() *TxtxDocument {
13 | return &TxtxDocument{}
14 | }
15 |
16 | // Extract ...
17 | func (d *TxtxDocument) Extract(filename string, binary []byte) ([]string, error) {
18 | sections := strings.Split(string(binary), "\n")
19 | return sections, nil
20 | }
21 |
--------------------------------------------------------------------------------
/tool/crawl/crawler.go:
--------------------------------------------------------------------------------
1 | package crawl
2 |
3 | import "context"
4 |
5 | // Result 爬取结果
6 | type Result struct {
7 | Link string
8 | Title string
9 | Content string
10 | }
11 |
12 | // Crawler 爬虫接口
13 | type Crawler interface {
14 | Crawl(ctx context.Context, url string) (*Result, error)
15 | }
16 |
17 | var Crawlers = make(map[string]Crawler)
18 |
19 | func Register(name string, c Crawler) {
20 | Crawlers[name] = c
21 | }
22 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/bookkeeper/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "gorm.io/gorm"
7 | )
8 |
9 | type User struct {
10 | gorm.Model
11 | Username string `gorm:"uniqueIndex;not null" json:"username"`
12 | Password string `gorm:"not null" json:"-"`
13 | Email string `gorm:"uniqueIndex;not null" json:"email"`
14 | LastLoginAt time.Time `json:"last_login_at"`
15 | IsActive bool `gorm:"default:true" json:"is_active"`
16 | }
17 |
--------------------------------------------------------------------------------
/memory/memory_item.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import "time"
4 |
5 | type MemoryItem struct {
6 | ID int64
7 | SessionID string
8 | Content string
9 | Topics []string
10 | Extra *Extra
11 | CreatedAt time.Time
12 | UpdatedAt time.Time
13 | }
14 |
15 | func NewMemoryItem(sessionID string, content string, extra *Extra) *MemoryItem {
16 | return &MemoryItem{
17 | SessionID: sessionID,
18 | Content: content,
19 | Extra: extra,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/speech/tts/tts.go:
--------------------------------------------------------------------------------
1 | package tts
2 |
3 | import "context"
4 |
5 | // TTS is the struct for text to speech
6 | type TTS struct {
7 | }
8 |
9 | // newTTS creates a new TTS
10 | func newTTS() *TTS {
11 | return &TTS{}
12 | }
13 |
14 | // Speak speaks the given text
15 | func (t *TTS) Speak(text string) error {
16 | return nil
17 | }
18 |
19 | func (t *TTS) getCached() {
20 | data, _ := Cache(nil).Get(context.Background(), "")
21 | if len(data) <= 0 {
22 | return
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/workflow/node/vdb.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/workflow"
7 | )
8 |
9 | // VDBNode ...
10 | type VDBNode struct {
11 | // Node
12 | }
13 |
14 | // NewVDBNode 创建VDBNode
15 | func NewVDBNode(n *workflow.Node) *VDBNode {
16 | return &VDBNode{
17 | // Node: *n,
18 | }
19 | }
20 |
21 | // Execute 执行VDBNode
22 | func (n *VDBNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) {
23 |
24 | return nil, nil
25 | }
26 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig({
6 | server: {
7 | host: '0.0.0.0',
8 | port: 3000,
9 | proxy: {
10 | '/api': {
11 | target: 'http://localhost:8080',
12 | changeOrigin: true,
13 | // rewrite: (path) => path.replace(/^\/api/, ''),
14 | },
15 | },
16 | },
17 |
18 | plugins: [react()],
19 | })
20 |
--------------------------------------------------------------------------------
/tool/datetime/tool.go:
--------------------------------------------------------------------------------
1 | package datetime
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/showntop/llmack/tool"
8 | )
9 |
10 | const GetTime = "GetTime"
11 |
12 | func init() {
13 | t := tool.New(
14 | tool.WithName(GetTime),
15 | tool.WithKind("code"),
16 | tool.WithDescription("获取当前时间"),
17 | tool.WithFunction(func(ctx context.Context, args string) (string, error) {
18 | return time.Now().Format("2006-01-02 15:04:05"), nil
19 | }),
20 | )
21 | tool.Register(t)
22 | }
23 |
--------------------------------------------------------------------------------
/tool/search/engine/engine.go:
--------------------------------------------------------------------------------
1 | package engine
2 |
3 | import "context"
4 |
5 | // Searcher 搜索器
6 | type Searcher interface {
7 | Search(ctx context.Context, query string) ([]*Result, error)
8 | }
9 |
10 | // Result 搜索结果
11 | type Result struct {
12 | Time string `json:"time"`
13 | Link string `json:"link"`
14 | Image string `json:"image"`
15 | Video string `json:"id"` // video id
16 | Title string `json:"title"`
17 | Snippet string `json:"snippet"`
18 | Content string `json:"content"`
19 | }
20 |
--------------------------------------------------------------------------------
/prompt/templates/intent.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | var IntentInstruction = `
4 | You are given an instruction along description of task labelled as [Task Description]. For the given instruction, list out 3-5 keywords in comma separated format as [Intent] which define the characteristics or properties required by the about the most capable and suitable agent to solve the task using the instruction.
5 |
6 |
7 | [Task Description]: {{task_description}}
8 | [Instruction]: {{instruction}}
9 |
10 |
11 | [Intent]:
12 | `
13 |
--------------------------------------------------------------------------------
/program/cot.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | // cot chain of thought
4 | type cot struct {
5 | *predictor
6 | }
7 |
8 | // COT ...
9 | func COT() *predictor {
10 | prefix := "Reasoning: Let's think step by step in order to"
11 | desc := "${reasoning}"
12 |
13 | p := &cot{}
14 | p.predictor = Predictor()
15 | p.predictor.WithOutputField("reasoning", desc, prefix)
16 | return p.predictor
17 | }
18 |
19 | func (p *cot) WithRationaleField(infos ...any) *predictor {
20 | p.predictor.WithOutputField(infos...)
21 | return p.predictor
22 | }
23 |
--------------------------------------------------------------------------------
/vdb/option.go:
--------------------------------------------------------------------------------
1 | package vdb
2 |
3 | // SearchOption 搜索选项
4 | type SearchOption func(*SearchOptions)
5 |
6 | // SearchOptions 搜索配置
7 | type SearchOptions struct {
8 | TopK int // 返回结果数量限制
9 | Threshold float64 // 相似度阈值
10 | }
11 |
12 | // WithTopk 设置返回结果数量
13 | func WithTopk(topk int) SearchOption {
14 | return func(o *SearchOptions) {
15 | o.TopK = topk
16 | }
17 | }
18 |
19 | // WithThreshold 设置相似度阈值
20 | func WithThreshold(threshold float64) SearchOption {
21 | return func(o *SearchOptions) {
22 | o.Threshold = threshold
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/rag/deepdoc/json.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "github.com/jeremywohl/flatten"
5 | )
6 |
7 | // JsonDocument ...
8 | type JsonDocument struct {
9 | }
10 |
11 | // Json ...
12 | //
13 | // nolint:ignore
14 | func Json() *JsonDocument {
15 | return &JsonDocument{}
16 | }
17 |
18 | // Extract ...
19 | func (d *JsonDocument) Extract(filename string, binary []byte) ([]string, error) {
20 |
21 | flat, err := flatten.FlattenString(string(binary), "", flatten.DotStyle)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | return []string{flat}, nil
27 | }
28 |
--------------------------------------------------------------------------------
/workflow/node/start.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/workflow"
7 | )
8 |
9 | type startNode struct {
10 | executeable
11 | Identifier
12 | }
13 |
14 | // StartNode initializes new start node with start definition
15 | func StartNode(n *workflow.Node) *startNode {
16 | return &startNode{
17 | Identifier: Identifier{
18 | id: n.ID,
19 | },
20 | }
21 | }
22 |
23 | // Exec executes start node
24 | func (f startNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) {
25 | return nil, nil
26 | }
27 |
--------------------------------------------------------------------------------
/tool/weather/weather.go:
--------------------------------------------------------------------------------
1 | package weather
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/tool"
7 | )
8 |
9 | const QueryWeather = "QueryWeather"
10 |
11 | func init() {
12 | t := tool.New(
13 | tool.WithName(QueryWeather),
14 | tool.WithKind("code"),
15 | tool.WithDescription("查询天气"),
16 | tool.WithParameters(tool.Parameter{
17 | Name: "city", Type: tool.String, Required: true, LLMDescrition: "城市", Default: "北京",
18 | }),
19 | tool.WithFunction(func(ctx context.Context, args string) (string, error) {
20 | return "北京晴朗,北风三级,2-15 摄氏度,空气质量优。", nil
21 | }),
22 | )
23 | tool.Register(t)
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 | go.work.sum
23 |
24 | # env file
25 | .env
26 |
27 | # tmp
28 | tmp/
29 |
--------------------------------------------------------------------------------
/prompt/templates/meta.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | // MetaInstruction is the instruction for generating prompts with meta prompts.
4 | var MetaInstruction = `
5 | You are given a task description and a prompt instruction and different styles known as meta prompts:
6 | [Task Description]: {{task_description}}
7 | [Meta Prompt]: {{meta_prompts}}
8 | Now you need to generate {{num_variations}} variations of following Instruction adaptively mixing meta prompt while keeping similar semantic meaning.
9 | Make sure to wrap each generated prompt with and
10 | [Prompt Instruction]: {{prompt_instruction}}
11 | [Generated Prompts]:
12 | `
13 |
--------------------------------------------------------------------------------
/speech/outbound/outbound.go:
--------------------------------------------------------------------------------
1 | package outbound
2 |
3 | type handle func([]byte) error
4 |
5 | // BaseOutbound ...
6 | type BaseOutbound struct {
7 | q chan []byte
8 | sampleRate int
9 | audioEncoding int
10 | }
11 |
12 | // NewBaseOutbound ...
13 | func NewBaseOutbound() *BaseOutbound {
14 | return &BaseOutbound{
15 | sampleRate: 16000,
16 | audioEncoding: 1,
17 | q: make(chan []byte, 0),
18 | }
19 | }
20 |
21 | // Write ...
22 | func (o *BaseOutbound) Write(data []byte) error {
23 | o.q <- data
24 | return nil
25 | }
26 |
27 | // Reset ...
28 | func (o *BaseOutbound) Reset() error {
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/rag/deepdoc/csv.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "bytes"
5 | "encoding/csv"
6 | )
7 |
8 | // CsvDocument ...
9 | type CsvDocument struct {
10 | }
11 |
12 | // Csv ...
13 | func Csv() *CsvDocument {
14 | return &CsvDocument{}
15 | }
16 |
17 | // Extract ...
18 | func (d *CsvDocument) Extract(filename string, binary []byte) ([]string, error) {
19 | // safe 校验
20 | // read csv values using csv.Reader
21 | csvReader := csv.NewReader(bytes.NewReader(binary))
22 | csvReader.Comma = ';'
23 | csvReader.LazyQuotes = true
24 | data, err := csvReader.ReadAll()
25 | if err != nil {
26 | return nil, err
27 | }
28 |
29 | return data[0], nil
30 | }
31 |
--------------------------------------------------------------------------------
/speech/entities.go:
--------------------------------------------------------------------------------
1 | package speech
2 |
3 | // TranscriptionPayload ...
4 | type TranscriptionPayload struct {
5 | Text string
6 | Final bool
7 | }
8 |
9 | // TurnPayload ...
10 | type TurnPayload struct {
11 | TurnID string
12 | Transcription *TranscriptionPayload
13 | }
14 |
15 | // AgentPayload ...
16 | type AgentPayload struct {
17 | TurnID string
18 | Transcription TranscriptionPayload
19 | }
20 |
21 | // ResponsePayload ...
22 | type ResponsePayload struct {
23 | Text string
24 | Chunk []byte
25 | First bool
26 | Final bool
27 | }
28 |
29 | // TTSResultPayload ...
30 | type TTSResultPayload struct {
31 | Text string
32 | Stream chan []byte
33 | }
34 |
--------------------------------------------------------------------------------
/tool/config.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import (
4 | "strings"
5 | "sync"
6 |
7 | "github.com/showntop/flatmap"
8 | )
9 |
10 | type config struct {
11 | config *flatmap.Map
12 | once sync.Once
13 | }
14 |
15 | var DefaultConfig config
16 |
17 | func WithConfig(c map[string]any) error {
18 | var err error
19 | DefaultConfig.once.Do(func() {
20 | var mmm *flatmap.Map
21 | mmm, err = flatmap.Flatten(c, flatmap.DefaultTokenizer)
22 | DefaultConfig.config = mmm
23 | })
24 | return err
25 | }
26 |
27 | func (c *config) GetString(fields ...string) string {
28 | x, _ := c.config.Get(strings.Join(fields, flatmap.DefaultTokenizer.Separator())).(string)
29 | return x
30 | }
31 |
--------------------------------------------------------------------------------
/mcp_config/servers.json:
--------------------------------------------------------------------------------
1 | {
2 | "servers": {
3 | "filesystem": {
4 | "args": [
5 | "-y",
6 | "@modelcontextprotocol/server-filesystem",
7 | "/tmp"
8 | ],
9 | "command": "npx",
10 | "description": "文件系统访问服务器",
11 | "enabled": true,
12 | "transport": "stdio"
13 | },
14 | "github": {
15 | "args": [
16 | "-y",
17 | "@modelcontextprotocol/server-github"
18 | ],
19 | "command": "npx",
20 | "description": "GitHub仓库访问服务器",
21 | "enabled": false,
22 | "env": {
23 | "GITHUB_PERSONAL_ACCESS_TOKEN": "your-token-here"
24 | },
25 | "transport": "stdio"
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/example/mcp/mcp_config/servers.json:
--------------------------------------------------------------------------------
1 | {
2 | "servers": {
3 | "filesystem": {
4 | "args": [
5 | "-y",
6 | "@modelcontextprotocol/server-filesystem",
7 | "/tmp"
8 | ],
9 | "command": "npx",
10 | "description": "文件系统访问服务器",
11 | "enabled": true,
12 | "transport": "stdio"
13 | },
14 | "github": {
15 | "args": [
16 | "-y",
17 | "@modelcontextprotocol/server-github"
18 | ],
19 | "command": "npx",
20 | "description": "GitHub仓库访问服务器",
21 | "enabled": false,
22 | "env": {
23 | "GITHUB_PERSONAL_ACCESS_TOKEN": "your-token-here"
24 | },
25 | "transport": "stdio"
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/llm/logger.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import "context"
4 |
5 | // NoneLogger ...
6 | type NoneLogger struct {
7 | }
8 |
9 | // ErrorContextf ...
10 | func (w *NoneLogger) ErrorContextf(ctx context.Context, format string, args ...interface{}) {
11 | // 在这里实现你的逻辑
12 | }
13 |
14 | // InfoContextf ...
15 | func (w *NoneLogger) InfoContextf(ctx context.Context, format string, args ...interface{}) {
16 | // 在这里实现你的逻辑
17 | }
18 |
19 | // WarnContextf ...
20 | func (w *NoneLogger) WarnContextf(ctx context.Context, format string, args ...interface{}) {
21 | // 在这里实现你的逻辑
22 | }
23 |
24 | // DebugContextf ...
25 | func (w *NoneLogger) DebugContextf(ctx context.Context, format string, args ...interface{}) {
26 | // 在这里实现你的逻辑
27 | }
28 |
--------------------------------------------------------------------------------
/rag/deepdoc/docx.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "bytes"
5 |
6 | "baliance.com/gooxml/document"
7 | )
8 |
9 | // DocxDocument ...
10 | type DocxDocument struct {
11 | }
12 |
13 | // Docx ...
14 | func Docx() *DocxDocument {
15 | return &DocxDocument{}
16 | }
17 |
18 | // Extract ...
19 | func (d *DocxDocument) Extract(filename string, binary []byte) ([]string, error) {
20 | doc, err := document.Read(bytes.NewReader(binary), int64(len(binary)))
21 | if err != nil {
22 | return nil, err
23 | }
24 |
25 | sections := []string{}
26 | for _, para := range doc.Paragraphs() {
27 | //run为每个段落相同格式的文字组成的片段
28 | for _, run := range para.Runs() {
29 | sections = append(sections, run.Text())
30 | }
31 | }
32 | return sections, nil
33 | }
34 |
--------------------------------------------------------------------------------
/speech/outbound/file.go:
--------------------------------------------------------------------------------
1 | package outbound
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/showntop/llmack/speech"
8 | )
9 |
10 | // FileOutBound for test ...
11 | type FileOutBound struct {
12 | ff *os.File
13 | }
14 |
15 | // NewFileOutBound ...
16 | func NewFileOutBound() speech.Outbound {
17 | f, _ := os.Create("out.wav")
18 | return &FileOutBound{ff: f}
19 |
20 | }
21 |
22 | // Write ...
23 | func (o *FileOutBound) Write(msg []byte) error {
24 | fmt.Println("file outbound write", len(msg))
25 | _, err := o.ff.Write(msg)
26 | return err
27 | }
28 |
29 | // Reset ...
30 | func (o *FileOutBound) Reset() error {
31 | return nil
32 | }
33 |
34 | // Close ...
35 | func (o *FileOutBound) Close() error {
36 | return o.ff.Close()
37 | }
38 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "verbatimModuleSyntax": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "erasableSyntaxOnly": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noUncheckedSideEffectImports": true
23 | },
24 | "include": ["vite.config.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "verbatimModuleSyntax": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "erasableSyntaxOnly": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUncheckedSideEffectImports": true
25 | },
26 | "include": ["src"]
27 | }
28 |
--------------------------------------------------------------------------------
/prompt/prompt.go:
--------------------------------------------------------------------------------
1 | // Package prompt handles prompt representation and templating
2 | package prompt
3 |
4 | // Prompt 提示词
5 | type Prompt struct {
6 | Version string
7 | InitialInstruction string // 初始指令,用于生成prompt
8 | Description string // 简短描述任务
9 | Inputs map[string]any // 输入
10 | Outputs any // 输出
11 | Metadata map[string]any // 元数据
12 | }
13 |
14 | // New creates a new prompt
15 | func New() *Prompt {
16 | p := &Prompt{
17 | Inputs: make(map[string]any),
18 | Metadata: make(map[string]any),
19 | }
20 | return p
21 | }
22 |
23 | // Render renders the prompt as string
24 | func (p *Prompt) Render(inputs map[string]any) string {
25 | xxx, err := Render(p.InitialInstruction, inputs)
26 | if err != nil {
27 | panic(err)
28 | }
29 | return xxx
30 | }
31 |
--------------------------------------------------------------------------------
/workflow/workflow.go:
--------------------------------------------------------------------------------
1 | package workflow
2 |
3 | // Result ...
4 | type Result struct {
5 | Outputs map[string]any
6 | }
7 |
8 | // Workflow ...
9 | type Workflow struct {
10 | ID int64
11 | Name string
12 | Description string
13 | Key string
14 | Version int64
15 | Nodes []Node
16 | Edges []Edge
17 | // Metadata metadata
18 | }
19 |
20 | // Parameters ...
21 | type Parameters map[string]Parameter
22 |
23 | // Parameter ...
24 | type Parameter struct {
25 | Name string `json:"name"` // 参数名称
26 | Value string `json:"value"` // 参数值
27 | Type string `json:"type"` // 参数类型
28 | }
29 |
30 | // Nodes ...
31 | type Nodes []Node
32 |
33 | // Event ...
34 | type Event struct {
35 | Name string `json:"name"` // 事件名称
36 | Data any `json:"data"` // 事件值
37 | Type string `json:"type"` // 事件类型
38 | }
39 |
--------------------------------------------------------------------------------
/rag/retirval.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/log"
7 | "github.com/showntop/llmack/vdb"
8 | )
9 |
10 | // Retrival ...
11 | type Retrival struct {
12 | vdb vdb.VDB
13 | scalarDB ScalarDB // object
14 | }
15 |
16 | // NewRetrival ...
17 | func NewRetrival(name string, config any) (*Retrival, error) {
18 | // new vdb
19 | vdb, err := vdb.NewVDB(name, config)
20 | if err != nil {
21 | return nil, err
22 | }
23 |
24 | return &Retrival{vdb: vdb}, nil
25 | }
26 |
27 | // Retrieve TODO: 实现检索逻辑
28 | func (r *Retrival) Retrieve(ctx context.Context, query string, opts *SearchOptions) ([]*vdb.Document, error) {
29 | log.InfoContextf(ctx, "Retrieve knowledge: %v query: %s options: %+v", opts.LibraryID, query, opts)
30 |
31 | // return r.vdb.Search(ctx, query, opts)
32 | return nil, nil
33 | }
34 |
--------------------------------------------------------------------------------
/example/llm/moonshot/moonshot.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 |
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/llm/moonshot"
12 | )
13 |
14 | func init() {
15 | godotenv.Load()
16 | }
17 |
18 | func main() {
19 | ctx := context.Background()
20 | llm.WithSingleConfig(map[string]any{
21 | "api_key": os.Getenv("moonshot_api_key"),
22 | })
23 |
24 | resp, err := llm.NewInstance(moonshot.Name).Invoke(ctx, []llm.Message{
25 | llm.NewUserTextMessage("你好"),
26 | }, llm.WithModel("moonshot-v1-8k"))
27 | if err != nil {
28 | panic(err)
29 | }
30 | // stream := resp.Stream()
31 | // for v := stream.Next(); v != nil; v = stream.Next() {
32 | // fmt.Println(string(v.Delta.Message.Content().Data))
33 | // }
34 | fmt.Println(resp.Result())
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/rag/rag.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/vdb"
7 | )
8 |
9 | // ScalarDB ...
10 | type ScalarDB interface {
11 | Fetch(context.Context, string, *SearchOptions) (*vdb.Document, error)
12 | }
13 |
14 | // Options ...
15 | type SearchOptions struct {
16 | LibraryID int64 `json:"id"`
17 | Kind string `json:"kind"`
18 | IndexID int32 `json:"index_id"`
19 | TopK int `json:"top_k"`
20 | ScoreThreshold float64 `json:"score_threshold"`
21 | }
22 |
23 | type SearchOption func(*SearchOptions)
24 |
25 | func WithTopK(topK int) SearchOption {
26 | return func(o *SearchOptions) {
27 | o.TopK = topK
28 | }
29 | }
30 |
31 | func WithScoreThreshold(scoreThreshold float64) SearchOption {
32 | return func(o *SearchOptions) {
33 | o.ScoreThreshold = scoreThreshold
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 | import tseslint from 'typescript-eslint'
6 |
7 | export default tseslint.config(
8 | { ignores: ['dist'] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ['**/*.{ts,tsx}'],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | 'react-hooks': reactHooks,
18 | 'react-refresh': reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | 'react-refresh/only-export-components': [
23 | 'warn',
24 | { allowConstantExport: true },
25 | ],
26 | },
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/example/bookkeeper/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | )
6 |
7 | type Config struct {
8 | Server ServerConfig
9 | Database DatabaseConfig
10 | LLM LLMConfig
11 | }
12 |
13 | type ServerConfig struct {
14 | Port string
15 | }
16 |
17 | type DatabaseConfig struct {
18 | Host string
19 | Port int
20 | User string
21 | Password string
22 | DBName string
23 | }
24 |
25 | type LLMConfig struct {
26 | Provider string
27 | Model string
28 | }
29 |
30 | func LoadConfig() (*Config, error) {
31 | viper.SetConfigName("config")
32 | viper.SetConfigType("yaml")
33 | viper.AddConfigPath("./config")
34 |
35 | if err := viper.ReadInConfig(); err != nil {
36 | return nil, err
37 | }
38 |
39 | var config Config
40 | if err := viper.Unmarshal(&config); err != nil {
41 | return nil, err
42 | }
43 |
44 | return &config, nil
45 | }
46 |
--------------------------------------------------------------------------------
/example/bookkeeper/models/transaction.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "gorm.io/gorm"
7 | )
8 |
9 | type Transaction struct {
10 | gorm.Model
11 | Amount float64 `json:"amount"`
12 | Category string `json:"category"`
13 | Description string `json:"description"`
14 | Date time.Time `json:"date"`
15 | ImageURL string `json:"image_url"`
16 | UserID uint `json:"user_id"`
17 | }
18 |
19 | type Category struct {
20 | gorm.Model
21 | Name string `json:"name"`
22 | Description string `json:"description"`
23 | Type string `json:"type"` // income or expense
24 | }
25 |
26 | type Budget struct {
27 | gorm.Model
28 | Category string `json:"category"`
29 | Amount float64 `json:"amount"`
30 | StartDate time.Time `json:"start_date"`
31 | EndDate time.Time `json:"end_date"`
32 | UserID uint `json:"user_id"`
33 | }
34 |
--------------------------------------------------------------------------------
/example/llm/ollama/ollama.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/showntop/llmack/llm"
8 | _ "github.com/showntop/llmack/llm/ollama"
9 | )
10 |
11 | func main() {
12 | ctx := context.Background()
13 |
14 | // llm.WithConfigs(map[string]any{
15 | // "ollama": map[string]any{
16 | // "endpoint": "http://127.0.0.1:4000",
17 | // },
18 | // })
19 |
20 | llm.WithSingleConfig(map[string]any{
21 | "base_url": "http://127.0.0.1:11434",
22 | })
23 |
24 | resp, err := llm.NewInstance("ollama").Invoke(ctx, []llm.Message{
25 | llm.NewUserTextMessage("你好"),
26 | }, llm.WithModel("llama3.1"), llm.WithStream(true))
27 | if err != nil {
28 | panic(err)
29 | }
30 | // stream := resp.Stream()
31 | // for v := stream.Next(); v != nil; v = stream.Next() {
32 | // fmt.Println(string(v.Delta.Message.Content().Data))
33 | // }
34 | fmt.Println(resp.Result())
35 | }
36 |
--------------------------------------------------------------------------------
/llm/cache_metrics.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import (
4 | "sync/atomic"
5 | "time"
6 | )
7 |
8 | // Metrics 存储缓存的统计信息
9 | type Metrics struct {
10 | hits uint64
11 | misses uint64
12 | adds uint64
13 | evicts uint64
14 | lastAccess time.Time
15 | }
16 |
17 | func (m *Metrics) recordHit() { atomic.AddUint64(&m.hits, 1) }
18 | func (m *Metrics) recordMiss() { atomic.AddUint64(&m.misses, 1) }
19 | func (m *Metrics) recordAdd() { atomic.AddUint64(&m.adds, 1) }
20 | func (m *Metrics) recordEvict() { atomic.AddUint64(&m.evicts, 1) }
21 |
22 | // GetStats 返回当前统计信息
23 | func (m *Metrics) GetStats() map[string]interface{} {
24 | return map[string]interface{}{
25 | "hits": atomic.LoadUint64(&m.hits),
26 | "misses": atomic.LoadUint64(&m.misses),
27 | "adds": atomic.LoadUint64(&m.adds),
28 | "evicts": atomic.LoadUint64(&m.evicts),
29 | "last_access": m.lastAccess,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/example/llm/zhipu/zhipu.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 |
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/llm/zhipu"
12 | )
13 |
14 | func init() {
15 | godotenv.Load()
16 | }
17 |
18 | func main() {
19 | ctx := context.Background()
20 | llm.WithSingleConfig(map[string]any{
21 | "api_key": os.Getenv("zhipu_api_key"),
22 | })
23 |
24 | resp, err := llm.NewInstance(zhipu.Name).Invoke(ctx,
25 | []llm.Message{
26 | // llm.UserTextPromptMessage("你好"),
27 | llm.NewUserMultipartMessage(
28 | llm.MultipartContentImageURL("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"),
29 | llm.MultipartContentText("这是一张关于猫的照片吗"),
30 | ),
31 | },
32 |
33 | llm.WithModel("glm-4v-plus"))
34 | if err != nil {
35 | panic(err)
36 | }
37 | fmt.Println(resp.Result())
38 | }
39 |
--------------------------------------------------------------------------------
/speech/outbound/ratelimit.go:
--------------------------------------------------------------------------------
1 | package outbound
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // RateLimitOutbound ...
8 | type RateLimitOutbound struct {
9 | *BaseOutbound
10 | handle func([]byte) error
11 | }
12 |
13 | // NewRateLimitOutbound ...
14 | func NewRateLimitOutbound() *RateLimitOutbound {
15 | out := &RateLimitOutbound{}
16 | out.BaseOutbound = NewBaseOutbound()
17 | go out.Loop()
18 | return out
19 | }
20 |
21 | // Loop ...
22 | func (o *RateLimitOutbound) Loop() error {
23 | for chunk := range o.q {
24 | startTime := time.Now()
25 | // 置换 chunk 状态
26 | chunkSizePerDuration := float64(o.sampleRate) * 2.00 / float64(time.Second)
27 | durations := time.Duration(float64(len(chunk)) / chunkSizePerDuration)
28 | o.handle(chunk)
29 | endTime := time.Now()
30 |
31 | x := durations - endTime.Sub(startTime) - 10*time.Millisecond
32 | if x > 0 {
33 | time.Sleep(x)
34 | }
35 |
36 | }
37 | return nil
38 | }
39 |
--------------------------------------------------------------------------------
/tool/file/create_file.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "os"
7 |
8 | "github.com/showntop/llmack/tool"
9 | )
10 |
11 | const CreateFile = "CreateFile"
12 |
13 | func init() {
14 | t := tool.New(
15 | tool.WithName(CreateFile),
16 | tool.WithKind("code"),
17 | tool.WithDescription("创建文件"),
18 | tool.WithParameters(tool.Parameter{
19 | Name: "path",
20 | LLMDescrition: "文件路径",
21 | Type: tool.String,
22 | Required: true,
23 | }),
24 | tool.WithFunction(func(ctx context.Context, args string) (string, error) {
25 | var params struct {
26 | Path string `json:"path"`
27 | }
28 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
29 | return "", err
30 | }
31 | _, err := os.Create(params.Path)
32 | if err != nil {
33 | return "", err
34 | }
35 | return "文件创建成功", nil
36 | }),
37 | )
38 | tool.Register(t)
39 | }
40 |
--------------------------------------------------------------------------------
/example/bookkeeper/database/database.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/bookkeeper-ai/bookkeeper/config"
8 | "github.com/bookkeeper-ai/bookkeeper/models"
9 | "gorm.io/driver/postgres"
10 | "gorm.io/gorm"
11 | )
12 |
13 | var DB *gorm.DB
14 |
15 | func InitDB(cfg *config.Config) error {
16 | dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
17 | cfg.Database.Host,
18 | cfg.Database.Port,
19 | cfg.Database.User,
20 | cfg.Database.Password,
21 | cfg.Database.DBName,
22 | )
23 |
24 | db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
25 | if err != nil {
26 | return err
27 | }
28 |
29 | DB = db
30 |
31 | // 自动迁移数据库表
32 | err = DB.AutoMigrate(&models.Transaction{}, &models.Category{}, &models.Budget{}, &models.User{})
33 | if err != nil {
34 | return err
35 | }
36 |
37 | log.Println("Database connected successfully")
38 | return nil
39 | }
40 |
--------------------------------------------------------------------------------
/speech/asr/tencent/option.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import "github.com/showntop/tencentcloud-speech-sdk-go/asr"
4 |
5 | // Option ...
6 | type Option func(*Options)
7 |
8 | // Options ...
9 | type Options struct {
10 | AppID string
11 | SecretID string
12 | SecretKey string
13 |
14 | listener asr.SpeechRecognitionListener
15 | }
16 |
17 | // WithAppID ...
18 | func WithAppID(appID string) Option {
19 | return func(o *Options) {
20 | o.AppID = appID
21 | }
22 | }
23 |
24 | // WithSecretID ...
25 | func WithSecretID(secretID string) Option {
26 | return func(o *Options) {
27 | o.SecretID = secretID
28 | }
29 | }
30 |
31 | // WithSecretKey ...
32 | func WithSecretKey(secretKey string) Option {
33 | return func(o *Options) {
34 | o.SecretKey = secretKey
35 | }
36 | }
37 |
38 | // WithListener ...
39 | func WithListener(l asr.SpeechRecognitionListener) Option {
40 | return func(o *Options) {
41 | o.listener = l
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/structx/set.go:
--------------------------------------------------------------------------------
1 | package structx
2 |
3 | type set[T comparable] struct {
4 | keys map[T]int
5 | values []any
6 | }
7 |
8 | // Set ...
9 | func Set[T comparable]() *set[T] {
10 | return &set[T]{
11 | keys: make(map[T]int),
12 | values: make([]any, 0),
13 | }
14 | }
15 |
16 | func (s *set[T]) Set(key T, value any) {
17 | if x, ok := s.keys[key]; ok {
18 | s.values[x] = value
19 | return
20 | }
21 | s.keys[key] = len(s.values)
22 | s.values = append(s.values, value)
23 | }
24 |
25 | func (s *set[T]) Get(key T) any {
26 |
27 | x, ok := s.keys[key]
28 | if !ok {
29 | return nil
30 | }
31 | return s.values[x]
32 | }
33 |
34 | func (s *set[T]) Del(key T, value any) {
35 |
36 | }
37 |
38 | func (s *set[T]) Exist(key T) bool {
39 | _, ok := s.keys[key]
40 | return ok
41 | }
42 |
43 | func (s *set[T]) Values() []any {
44 | return s.values
45 | }
46 |
47 | func (s *set[T]) SortBy(field string) []any {
48 | return s.values
49 | }
50 |
--------------------------------------------------------------------------------
/speech/tts/cache.go:
--------------------------------------------------------------------------------
1 | package tts
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | "github.com/redis/go-redis/v9"
8 | )
9 |
10 | var onceCache sync.Once
11 |
12 | // redisCache is the struct for the redis
13 | type redisCache struct {
14 | client *redis.Client
15 | }
16 |
17 | var defaultCache *redisCache
18 |
19 | // Cache creates a new redis cache
20 | func Cache(cli *redis.Client) *redisCache {
21 | onceCache.Do(func() {
22 | cli := redis.NewClient(&redis.Options{
23 | Addr: "localhost:6379",
24 | Password: "", // no password set
25 | DB: 0, // use default DB
26 | })
27 | defaultCache = &redisCache{client: cli}
28 | })
29 | return defaultCache
30 | }
31 |
32 | func (c *redisCache) Get(ctx context.Context, key string) ([]byte, error) {
33 | return c.client.Get(ctx, key).Bytes()
34 | }
35 |
36 | func (c *redisCache) Set(ctx context.Context, key string, raw []byte) error {
37 | return c.client.Set(ctx, key, raw, 0).Err()
38 | }
39 |
--------------------------------------------------------------------------------
/program/option.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/llm"
7 | )
8 |
9 | type option func(*predictor)
10 |
11 | func WithAdapter(adapter Adapter) option {
12 | return func(p *predictor) {
13 | p.adapter = adapter
14 | }
15 | }
16 |
17 | func WithLLM(provider string, model string, opts ...llm.Option) option {
18 | return func(p *predictor) {
19 | opts = append(opts, llm.WithDefaultModel(model))
20 | p.model = llm.NewInstance(provider, opts...)
21 | }
22 | }
23 |
24 | func WithLLMInstance(model *llm.Instance) option {
25 | return func(p *predictor) {
26 | p.model = model
27 | }
28 | }
29 |
30 | func WithMaxIterationNum(num int) option {
31 | return func(p *predictor) {
32 | p.maxIterationNum = num
33 | }
34 | }
35 |
36 | func WithResetMessages(resetMessages func(ctx context.Context, messages []llm.Message) []llm.Message) option {
37 | return func(p *predictor) {
38 | p.resetMessages = resetMessages
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/speech/tts/tencent/stream.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import (
4 | "sync"
5 |
6 | mtts "github.com/showntop/llmack/speech/tts"
7 | "github.com/showntop/tencentcloud-speech-sdk-go/tts"
8 | )
9 |
10 | // StreamTTS ...
11 | type StreamTTS struct {
12 | sync.Mutex
13 |
14 | mtts.TTS
15 | options Options
16 | client *tts.SpeechWsv2Synthesizer
17 | sessionID string
18 | buffer chan []byte
19 | }
20 |
21 | func NewStreamTTS(opts ...Option) *StreamTTS {
22 | return &StreamTTS{}
23 | }
24 |
25 | func (s *StreamTTS) Complete() error {
26 | return nil
27 | }
28 |
29 | func (s *StreamTTS) Prepare() error {
30 | return nil
31 | }
32 |
33 | func (s *StreamTTS) Input(text string) error {
34 | return nil
35 | }
36 |
37 | func (s *StreamTTS) Synthesize() error {
38 | return nil
39 | }
40 |
41 | func (s *StreamTTS) Terminate() error {
42 | return nil
43 | }
44 |
45 | // StreamResult() chan []byte
46 | func (s *StreamTTS) StreamResult() chan []byte {
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/workflow/node/expr.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/expr-lang/expr"
8 |
9 | "github.com/showntop/llmack/workflow"
10 | wf "github.com/showntop/llmack/workflow"
11 | )
12 |
13 | // exprNode TODO
14 | type exprNode struct {
15 | executeable
16 | Identifier
17 |
18 | Node *wf.Node
19 | }
20 |
21 | // ExprNode 创建expr node
22 | func ExprNode(n *workflow.Node) *exprNode {
23 | return &exprNode{
24 | Node: n,
25 | }
26 | }
27 |
28 | // Execute 执行JSON节点,单次执行
29 | func (n *exprNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) {
30 |
31 | expression, _ := n.Node.Metadata["expr"].(string)
32 | if expression == "" {
33 | return nil, nil
34 | }
35 |
36 | program, err := expr.Compile(expression)
37 | if err != nil {
38 | return nil, errors.Join(err)
39 | }
40 | result, err := expr.Run(program, r.Inputs)
41 | if err != nil {
42 | return nil, errors.Join(err)
43 | }
44 |
45 | return result, nil
46 | }
47 |
--------------------------------------------------------------------------------
/tool/user/inquiry.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | "github.com/showntop/llmack/tool"
9 | )
10 |
11 | const Inquery = "Inquery"
12 |
13 | func init() {
14 | t := tool.New(
15 | tool.WithName(Inquery),
16 | tool.WithKind("code"),
17 | tool.WithDescription("询问用户的具体需求以提供个性化的服务"),
18 | tool.WithParameters(tool.Parameter{
19 | Name: "inquiry",
20 | LLMDescrition: "询问用户的具体需求以提供个性化的服务",
21 | Required: true,
22 | Type: tool.String,
23 | }),
24 | tool.WithFunction(func(ctx context.Context, args string) (string, error) {
25 | var params struct {
26 | Inquiry string `json:"inquiry"`
27 | }
28 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
29 | return "", err
30 | }
31 | // 等待用户输入
32 | var input string
33 | fmt.Printf("请根据提示输入你的需求 (%s): ", params.Inquiry)
34 | fmt.Scanf("%s", &input)
35 | return input, nil
36 | }),
37 | )
38 | tool.Register(t)
39 | }
40 |
--------------------------------------------------------------------------------
/workflow/node/node.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/workflow"
7 | )
8 |
9 | // Identifier TODO
10 | type Identifier struct{ id string }
11 |
12 | // ID TODO
13 | func (i *Identifier) ID() string { return i.id }
14 |
15 | // SetID TODO
16 | func (i *Identifier) SetID(id string) { i.id = id }
17 |
18 | type (
19 | executeable struct{}
20 |
21 | // Nodes TODO
22 | Nodes []Node
23 |
24 | // Node TODO
25 | Node interface {
26 | ID() string
27 | SetID(string)
28 | Execute(context.Context, *ExecRequest) (ExecResponse, error)
29 | }
30 |
31 | execFn func(context.Context, *ExecRequest) (ExecResponse, error)
32 | )
33 |
34 | type ExecRequest struct {
35 | ProcessID string
36 | WorkflowID string
37 | NodeID string
38 | Sponsor string
39 | Runner string // 运行人
40 | Inputs map[string]any
41 | Scope map[string]any
42 | // Graph *dag.Graph
43 | Events chan *workflow.Event
44 | }
45 |
46 | type ExecResponse any
47 |
--------------------------------------------------------------------------------
/example/deepdoc/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "strings"
8 |
9 | "github.com/showntop/llmack/rag/deepdoc/chunk"
10 | "github.com/showntop/llmack/rag/deepdoc/extractor"
11 |
12 | pstrings "github.com/showntop/llmack/pkg/strings"
13 | )
14 |
15 | func main() {
16 |
17 | file, err := os.OpenFile(
18 | "example/deepdoc/企业级 SaaS 行业增长白皮书.pdf",
19 | os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
20 | if err != nil {
21 | panic(err)
22 | }
23 | content, _ := io.ReadAll(file)
24 | sections, err := extractor.Extract(
25 | &extractor.Meta{Filename: "企业级 SaaS 行业增长白皮书.pdf"}, content, "doc")
26 | if err != nil {
27 | panic(err)
28 | }
29 |
30 | chunker := chunk.NewCharacterChunker(500, 50, "")
31 | chunks, _ := chunker.Chunk(string(strings.Join(sections, "\n")))
32 | fmt.Println(len(chunks))
33 | for i := 0; i < len(chunks); i++ {
34 | fmt.Println("--------------------")
35 | fmt.Printf("%s\n", pstrings.TrimSpecial(chunks[i]))
36 | fmt.Println(len([]rune(pstrings.TrimSpecial(chunks[i]))))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/speech/asr/mock.go:
--------------------------------------------------------------------------------
1 | package asr
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | // MockASR MockASR
8 | type MockASR struct {
9 | current int
10 | contents []string
11 | calback func(string, bool) error
12 | }
13 |
14 | // NewMockASR 新建一个MockASR
15 | func NewMockASR(c func(string, bool) error) *MockASR {
16 | t := &MockASR{calback: c}
17 | // t.contents = []string{"你好,", "请问怎么", "处理广告", "不起量的", "问题。"}
18 | t.contents = []string{"你是谁。"}
19 | return t
20 | }
21 |
22 | // Input 实现语音识别,转录为文字
23 | func (t *MockASR) Input(content []byte) error {
24 | if len(content) == 0 {
25 | return nil
26 | }
27 | if t.current >= len(t.contents) {
28 | return nil
29 | }
30 |
31 | ssss := t.contents[t.current]
32 | final := false
33 | if t.current == len(t.contents)-1 {
34 | ssss = strings.Join(t.contents, "")
35 | final = true
36 | // t.current = 0
37 | }
38 |
39 | t.calback(ssss, final)
40 | t.current++
41 | return nil
42 | }
43 |
44 | // Recognize 实现语音识别,转录为文字
45 | func (t *MockASR) Recognize(content []byte) (string, error) {
46 | return "", nil
47 | }
48 |
--------------------------------------------------------------------------------
/llm/qwen/llm.go:
--------------------------------------------------------------------------------
1 | package qwen
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 |
8 | "github.com/showntop/llmack/llm"
9 | )
10 |
11 | const (
12 | // Name name of llm
13 | Name = "qwen"
14 | )
15 |
16 | func init() {
17 | llm.Register(Name, func(o *llm.ProviderOptions) llm.Provider { return &LLM{} })
18 | }
19 |
20 | // LLM TODO
21 | type LLM struct {
22 | once sync.Once
23 | engine *llm.OAILLM
24 | }
25 |
26 | var initOnce sync.Once
27 |
28 | // Invoke TODO
29 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) {
30 | var err error
31 | m.once.Do(func() {
32 | url := "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
33 | config, _ := llm.Config.Get(Name).(map[string]any)
34 | if config == nil {
35 | err = fmt.Errorf(Name + " config not found")
36 | }
37 | apiKey, _ := config["api_key"].(string)
38 | m.engine = llm.NewOAILLM(url, apiKey)
39 | })
40 | if err != nil {
41 | return nil, err
42 | }
43 | return m.engine.Invoke(ctx, messages, opts)
44 | }
45 |
--------------------------------------------------------------------------------
/program/program.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/llm"
7 | "github.com/showntop/llmack/llm/deepseek"
8 | )
9 |
10 | // var defaultLLM = llm.NewInstance(moonshot.Name, llm.WithDefaultModel("moonshot-v1-8k"))
11 | // var defaultLLM = llm.NewInstance(hunyuan.Name, llm.WithDefaultModel("hunyuan"))
12 | // var defaultLLM = llm.NewInstance(zhipu.Name, llm.WithDefaultModel("GLM-4-Flash"))
13 | var defaultLLM = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat"))
14 |
15 | // SetLLM can not set concurrent
16 | func SetLLM(provider, model string) {
17 | defaultLLM = llm.NewInstance(provider, llm.WithDefaultModel(model))
18 | }
19 |
20 | // Program defines the interface for a program
21 | // Program 定义了一个可运行的程序,不会保存状态,随用随 new
22 | type Program interface {
23 | Prompt() *Promptx
24 | Update(...option)
25 | Forward(context.Context, map[string]any) (any, error)
26 | }
27 |
28 | // ProgramFunc is a function that implements the Program interface
29 | type ProgramFunc func(context.Context, string) (string, error)
30 |
--------------------------------------------------------------------------------
/rag/deepdoc/pptx.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "bytes"
5 |
6 | "baliance.com/gooxml/presentation"
7 | )
8 |
9 | // PptxDocument ...
10 | type PptxDocument struct {
11 | }
12 |
13 | // Pptx ...
14 | func Pptx() *PptxDocument {
15 | return &PptxDocument{}
16 | }
17 |
18 | // Extract ...
19 | func (d *PptxDocument) Extract(filename string, binary []byte) ([]string, error) {
20 | doc, err := presentation.Read(bytes.NewReader(binary), int64(len(binary)))
21 | if err != nil {
22 | return nil, err
23 | }
24 |
25 | sections := make([]string, 0)
26 |
27 | for _, slide := range doc.Slides() {
28 | //run为每个段落相同格式的文字组成的片段
29 | for _, choice := range slide.X().CSld.SpTree.Choice {
30 | if choice.Sp == nil {
31 | continue
32 | }
33 | for _, sp := range choice.Sp {
34 | for _, p := range sp.TxBody.P {
35 | for _, run := range p.EG_TextRun {
36 | if run.R == nil {
37 | continue
38 | }
39 | sections = append(sections, run.R.T)
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
46 | return sections, nil
47 | }
48 |
--------------------------------------------------------------------------------
/storage/session.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/showntop/llmack/llm"
7 | "github.com/showntop/llmack/memory"
8 | )
9 |
10 | // for domain usage
11 | type Session struct {
12 | UID string `json:"uid" gorm:"column:uid;type:varchar(255);"` // session id
13 | EngineID string `json:"engine_id" gorm:"column:engine_id;type:varchar(255);"` // engine id
14 | EngineType string `json:"engine_type" gorm:"column:engine_type;type:varchar(255);"` // engine type
15 | EngineData map[string]any `json:"engine_data" gorm:"column:engine_data;type:jsonb"` // engine data
16 |
17 | Memory memory.Memory `json:"memory"` // 记忆
18 |
19 | Messages []llm.Message `json:"messages"`
20 |
21 | Data any `json:"data"`
22 |
23 | CreatedAt time.Time `json:"created_at" gorm:"column:created_at;autoCreateTime"`
24 | UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at;autoUpdateTime"`
25 | }
26 |
27 | func NewSession(id string) *Session {
28 | return &Session{
29 | UID: id,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/workflow/node/factory.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/showntop/llmack/workflow"
7 | )
8 |
9 | // Build 根据节点类型构建节点
10 | func Build(n *workflow.Node, outgoing ...*workflow.Edge) (Node, error) {
11 |
12 | switch n.Kind {
13 | case workflow.NodeKindStart:
14 | return StartNode(n), nil
15 | case workflow.NodeKindLLM:
16 | return LLMNode(n), nil
17 | case workflow.NodeKindGateway:
18 | switch n.Subref {
19 | case "exclusive":
20 | return ExclGateway(n, outgoing...)
21 | case "inclusive":
22 | return ForkGateway(n, outgoing...)
23 | case "parallel":
24 | return ForkGateway(n, outgoing...)
25 | }
26 | case workflow.NodeKindWait:
27 | // return WaitNode(n)
28 | case workflow.NodeKindHuman:
29 | switch n.Subref {
30 | case "approval":
31 | case "human":
32 | }
33 | case workflow.NodeKindEnd:
34 | return EndNode(n)
35 | case workflow.NodeKindTool:
36 | return ToolNode(n)
37 | case workflow.NodeKindExpr:
38 | return ExprNode(n), nil
39 | }
40 | return nil, fmt.Errorf("unknown workflow node kind %q", n.Kind)
41 | }
42 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bookkeeper-web",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@emotion/react": "^11.14.0",
14 | "@emotion/styled": "^11.14.0",
15 | "@mui/icons-material": "^7.1.0",
16 | "@mui/material": "^7.1.0",
17 | "@types/recharts": "^1.8.29",
18 | "axios": "^1.9.0",
19 | "react": "^19.1.0",
20 | "react-dom": "^19.1.0",
21 | "recharts": "^2.15.3"
22 | },
23 | "devDependencies": {
24 | "@eslint/js": "^9.25.0",
25 | "@types/react": "^19.1.2",
26 | "@types/react-dom": "^19.1.2",
27 | "@vitejs/plugin-react": "^4.4.1",
28 | "eslint": "^9.25.0",
29 | "eslint-plugin-react-hooks": "^5.2.0",
30 | "eslint-plugin-react-refresh": "^0.4.19",
31 | "globals": "^16.0.0",
32 | "typescript": "~5.8.3",
33 | "typescript-eslint": "^8.30.1",
34 | "vite": "^6.3.5"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/llm/vision/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 | "github.com/showntop/llmack/llm"
10 | openaic "github.com/showntop/llmack/llm/openai-c"
11 | )
12 |
13 | func init() {
14 | godotenv.Load()
15 | }
16 |
17 | func main() {
18 | ctx := context.Background()
19 |
20 | llm.WithSingleConfig(map[string]any{
21 | "base_url": os.Getenv("hunyuan_base_url"),
22 | "api_key": os.Getenv("hunyuan_api_key"),
23 | })
24 |
25 | resp, err := llm.NewInstance(openaic.Name).Invoke(ctx, []llm.Message{
26 | llm.NewUserMultipartMessage(
27 | llm.MultipartContentImageURL("https://img.tukuppt.com/bg_grid/05/37/54/v40ZCaqERa.jpg!/fh/350"),
28 | llm.MultipartContentText("给这张图片添加一个太阳"),
29 | ),
30 | },
31 | llm.WithModel("hunyuan-vision"),
32 | llm.WithStream(true),
33 | )
34 |
35 | if err != nil {
36 | panic(err)
37 | }
38 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() {
39 | if len(it.Choices) > 0 {
40 | fmt.Println("final: ", it.Choices[0].Delta.String())
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/speech/outbound/websocket.go:
--------------------------------------------------------------------------------
1 | package outbound
2 |
3 | import (
4 | "encoding/base64"
5 | "os"
6 |
7 | "github.com/gorilla/websocket"
8 | "github.com/showntop/llmack/speech"
9 | )
10 |
11 | // WsOutbound ...
12 | type WsOutbound struct {
13 | *RateLimitOutbound
14 | ws *websocket.Conn
15 |
16 | ff *os.File
17 | }
18 |
19 | // NewWsOutbound ...
20 | func NewWsOutbound(ws *websocket.Conn) speech.Outbound {
21 | out := &WsOutbound{ws: ws}
22 | out.RateLimitOutbound = NewRateLimitOutbound()
23 | out.handle = out.write
24 | out.ff, _ = os.Create("out-ws.wav")
25 | return out
26 | }
27 |
28 | func (o *WsOutbound) write(data []byte) error {
29 | rr := base64.StdEncoding.EncodeToString(data)
30 | o.ff.Write([]byte(rr))
31 | o.ff.Write([]byte{'\n'})
32 | // log.InfoContextf(context.TODO(), "message %s, out", rr[:10])
33 | // return o.ws.WriteMessage(websocket.BinaryMessage, data)
34 | return o.ws.WriteJSON(map[string]any{
35 | "type": "websocket_audio",
36 | "data": rr,
37 | })
38 | }
39 |
40 | // Close ...
41 | func (o *WsOutbound) Close() error {
42 | return o.ws.Close()
43 | }
44 |
--------------------------------------------------------------------------------
/rag/deepdoc/xlsx.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 |
7 | "github.com/xuri/excelize/v2"
8 | )
9 |
10 | // XlsxDocument ...
11 | type XlsxDocument struct {
12 | }
13 |
14 | // Excel ...
15 | func Excel() *XlsxDocument {
16 | return &XlsxDocument{}
17 | }
18 |
19 | // Extract ...
20 | func (d *XlsxDocument) Extract(filename string, binary []byte) ([]string, error) {
21 | doc, err := excelize.OpenReader(bytes.NewReader(binary))
22 | if err != nil {
23 | return nil, err
24 | }
25 | if len(doc.GetSheetList()) <= 0 {
26 | return nil, fmt.Errorf("empty excel")
27 | }
28 | sheet := doc.GetSheetList()[0]
29 |
30 | sections := []string{}
31 | rows, err := doc.GetRows(sheet)
32 | if err != nil {
33 | return nil, err
34 | }
35 | for i, row := range rows {
36 | for j := range row {
37 | cell, _ := excelize.CoordinatesToCellName(j+1, i+1)
38 | value, _ := doc.GetCellValue(sheet, cell)
39 |
40 | _, link, _ := doc.GetCellHyperLink(sheet, cell)
41 | value += link
42 | sections = append(sections, value)
43 | }
44 | }
45 |
46 | return sections, nil
47 | }
48 |
--------------------------------------------------------------------------------
/example/llm/doubao/doubao.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | "github.com/joho/godotenv"
9 |
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/llm/doubao"
12 | )
13 |
14 | func init() {
15 | godotenv.Load()
16 | }
17 |
18 | func main() {
19 | ctx := context.Background()
20 | llm.WithSingleConfig(map[string]any{
21 | // "api_key": os.Getenv("doubao_api_key"),
22 | "base_url": "https://ark.cn-beijing.volces.com/api/v3",
23 | })
24 |
25 | resp, err := llm.NewInstance(doubao.Name).Invoke(ctx,
26 | // []llm.Message{llm.UserPromptMessage("Prove that all entire functions that are also injective take the form f (z) = az + 6 with a, b € C, and a ‡ 0.")},
27 | []llm.Message{llm.NewUserTextMessage("你好")},
28 | llm.WithModel("doubao-1.5-ui-tars-250328"),
29 | llm.WithStream(true),
30 | )
31 | if err != nil {
32 | panic(err)
33 | }
34 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() {
35 | xxx, _ := json.Marshal(it)
36 | fmt.Println(string(xxx))
37 | // fmt.Println("final: ", it.Delta.Message)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/speech/tts/tencent/options.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import "github.com/showntop/tencentcloud-speech-sdk-go/tts"
4 |
5 | // Options ...
6 | type Options struct {
7 | secretID string
8 | secretKey string
9 | appID int64
10 | longText bool
11 | Listener tts.SpeechWsv2SynthesisListener
12 | }
13 |
14 | // Option ...
15 | type Option func(*Options)
16 |
17 | // WithSecretID ...
18 | func WithSecretID(secretID string) Option {
19 | return func(o *Options) {
20 | o.secretID = secretID
21 | }
22 | }
23 |
24 | // WithSecretKey ...
25 | func WithSecretKey(secretKey string) Option {
26 | return func(o *Options) {
27 | o.secretKey = secretKey
28 | }
29 | }
30 |
31 | // WithLongText ...
32 | func WithLongText(longText bool) Option {
33 | return func(o *Options) {
34 | o.longText = longText
35 | }
36 | }
37 |
38 | // WithAppID ...
39 | func WithAppID(appID int64) Option {
40 | return func(o *Options) {
41 | o.appID = appID
42 | }
43 | }
44 |
45 | // WithListener ...
46 | func WithListener(listener tts.SpeechWsv2SynthesisListener) Option {
47 | return func(o *Options) {
48 | o.Listener = listener
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/rag/deepdoc/extractor/extractor.go:
--------------------------------------------------------------------------------
1 | package extractor
2 |
3 | import (
4 | "regexp"
5 | )
6 |
7 | var reDocx = regexp.MustCompile(`\.docx$`)
8 | var reXlsx = regexp.MustCompile(`\.xlsx$`)
9 | var rePptx = regexp.MustCompile(`\.pptx$`)
10 | var rePdf = regexp.MustCompile(`\.pdf$`)
11 | var reTxtx = regexp.MustCompile(`\.(txt|py|js|java|c|cpp|h|php|go|ts|sh|cs|kt)$`)
12 | var reDoc = regexp.MustCompile(`\.doc$`)
13 | var reCsv = regexp.MustCompile(`\.csv$`)
14 | var reJson = regexp.MustCompile(`\.json$`)
15 | var reMdx = regexp.MustCompile(`\.md$`)
16 |
17 | var extractors = map[string]Extractor{}
18 |
19 | func init() {
20 | extractors["doc"] = NewDocumentExtractor()
21 | extractors["faq"] = NewFaqExtractor()
22 | extractors["unknown"] = NewUnknownExtractor()
23 | }
24 |
25 | // Extractor ...
26 | type Extractor interface {
27 | Extract(*Meta, []byte) ([]string, error)
28 | }
29 |
30 | // Meta ...
31 | type Meta struct {
32 | Path string
33 | Filename string
34 | }
35 |
36 | // Extract ...
37 | func Extract(meta *Meta, content []byte, typ string) ([]string, error) {
38 | return extractors[typ].Extract(meta, content)
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 shawn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/llm/claude/claude.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 |
9 | "github.com/joho/godotenv"
10 |
11 | "github.com/showntop/llmack/llm"
12 | "github.com/showntop/llmack/llm/anthropic"
13 | )
14 |
15 | func init() {
16 | godotenv.Load()
17 | }
18 |
19 | func main() {
20 | ctx := context.Background()
21 | llm.WithSingleConfig(map[string]any{
22 | "api_key": os.Getenv("claude_api_key"),
23 | "base_url": "http://v2.open.venus.oa.com/llmproxy",
24 | })
25 |
26 | resp, err := llm.NewInstance(anthropic.Name).Invoke(ctx,
27 | // []llm.Message{llm.UserPromptMessage("Prove that all entire functions that are also injective take the form f (z) = az + 6 with a, b € C, and a ‡ 0.")},
28 | []llm.Message{llm.NewUserTextMessage("你好")},
29 | llm.WithStream(true),
30 | llm.WithModel("claude-3-7-sonnet-20250219"))
31 | if err != nil {
32 | panic(err)
33 | }
34 | // fmt.Println(resp.Result())
35 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() {
36 | xxx, _ := json.Marshal(it)
37 | fmt.Println(string(xxx))
38 | // fmt.Println("final: ", it.Delta.Message)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/example/bookkeeper/api/chat_handler.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/bookkeeper-ai/bookkeeper/services"
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | type ChatHandler struct {
11 | chatService *services.ChatService
12 | }
13 |
14 | func NewChatHandler(chatService *services.ChatService) *ChatHandler {
15 | return &ChatHandler{
16 | chatService: chatService,
17 | }
18 | }
19 |
20 | type ChatRequest struct {
21 | Message string `json:"message" binding:"required"`
22 | Medias []string `json:"medias"`
23 | }
24 |
25 | type ChatResponse struct {
26 | Response string `json:"response"`
27 | }
28 |
29 | func (h *ChatHandler) SendMessage(c *gin.Context) {
30 | var req ChatRequest
31 | if err := c.ShouldBindJSON(&req); err != nil {
32 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
33 | return
34 | }
35 |
36 | response, err := h.chatService.SendMessage(c.Request.Context(), req.Message, req.Medias)
37 | if err != nil {
38 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
39 | return
40 | }
41 |
42 | c.JSON(http.StatusOK, ChatResponse{
43 | Response: response,
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/llm/config.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | // ConfigSupplier ...
8 | type ConfigSupplier interface {
9 | Get(key string) any
10 | }
11 |
12 | // ConfigSupplierFunc ...
13 | type ConfigSupplierFunc func(key string) any
14 |
15 | // WithConfigSupplier ...
16 | func WithConfigSupplier(c ConfigSupplier) {
17 | Config = c
18 | }
19 |
20 | // Config default cache
21 | var Config ConfigSupplier = &envConfig{}
22 |
23 | type envConfig struct {
24 | Value map[string]any
25 | }
26 |
27 | // Get ...
28 | func (c *envConfig) Get(key string) any {
29 | return os.Getenv(key)
30 | }
31 |
32 | // WithConfigs ...
33 | func WithConfigs(c map[string]any) {
34 | Config = &mapConfig{Value: c}
35 | }
36 |
37 | type mapConfig struct {
38 | Value map[string]any
39 | }
40 |
41 | // Get ...
42 | func (c *mapConfig) Get(key string) any {
43 | return c.Value[key]
44 | }
45 |
46 | // WithSingleConfig ...
47 | func WithSingleConfig(c any) {
48 | Config = &anyConfig{Value: c}
49 | }
50 |
51 | // anyConfig for any type config
52 | type anyConfig struct {
53 | Value any
54 | }
55 |
56 | func (c *anyConfig) Get(key string) any {
57 | return c.Value
58 | }
59 |
--------------------------------------------------------------------------------
/tool/factory.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import "sync"
4 |
5 | // 注册表锁
6 | var registerLock sync.RWMutex
7 |
8 | // Repo 工具仓库
9 | type Repo interface {
10 | // FetchTool(context.Context, int64, string) (*Metadata, error)
11 | }
12 |
13 | // Factory 工具工厂
14 | type Factory struct {
15 | repo Repo
16 | }
17 |
18 | // NewFactory ...
19 | func NewFactory(repo Repo) *Factory {
20 | return &Factory{repo: repo}
21 | }
22 |
23 | // Spawn ...
24 | func (f *Factory) Spawn(name string) *Tool {
25 | registerLock.RLock()
26 | defer registerLock.RUnlock()
27 | if t, ok := tools[name]; ok {
28 | return t
29 | }
30 | return nil
31 | }
32 |
33 | var defaultFactory = NewFactory(nil)
34 |
35 | // WithRepo ...
36 | func WithRepo(repo Repo) {
37 | defaultFactory.repo = repo
38 | }
39 |
40 | // Spawn ...
41 | func Spawn(name string) *Tool {
42 | x := defaultFactory.Spawn(name)
43 | if x == nil {
44 | return NilTool
45 | }
46 | return x
47 | }
48 |
49 | // Tools 工具注册表
50 | var tools map[string]*Tool = make(map[string]*Tool)
51 |
52 | // Register 注册工具
53 | func Register(t *Tool) {
54 | registerLock.Lock()
55 | defer registerLock.Unlock()
56 | tools[t.Name] = t
57 | }
58 |
--------------------------------------------------------------------------------
/example/bookkeeper/readme:
--------------------------------------------------------------------------------
1 | # AI 记账助手
2 |
3 | 一个基于 AI 的智能记账工具,可以帮助用户通过对话方式记录和管理日常收支。
4 |
5 | ## 功能特点
6 |
7 | - 支持通过对话方式记录收支
8 | - 支持上传消费截图,自动识别金额和类别
9 | - 提供消费分析和预算管理建议
10 | - 支持导出消费明细报表
11 | - 提供投资理财建议
12 |
13 | ## 技术栈
14 |
15 | ### 后端
16 | - Go
17 | - Gin Web 框架
18 | - GORM ORM 框架
19 | - PostgreSQL 数据库
20 | - LLMACK AI 框架
21 |
22 | ### 前端
23 | - React
24 | - TypeScript
25 | - Material-UI
26 | - Vite
27 |
28 | ## 快速开始
29 |
30 | ### 后端设置
31 |
32 | 1. 安装 PostgreSQL 数据库
33 | 2. 创建数据库:
34 | ```sql
35 | CREATE DATABASE bookkeeper;
36 | ```
37 |
38 | 3. 配置数据库连接:
39 | 编辑 `config/config.yaml` 文件,设置数据库连接信息
40 |
41 | 4. 运行后端服务:
42 | ```bash
43 | cd bookkeeper
44 | go mod tidy
45 | go run main.go
46 | ```
47 |
48 | ### 前端设置
49 |
50 | 1. 安装依赖:
51 | ```bash
52 | cd bookkeeper-web
53 | npm install
54 | ```
55 |
56 | 2. 运行开发服务器:
57 | ```bash
58 | npm run dev
59 | ```
60 |
61 | ## API 文档
62 |
63 | ### 交易记录 API
64 |
65 | - POST /api/transactions - 创建新的交易记录
66 | - GET /api/transactions - 获取交易记录列表
67 | - GET /api/transactions/analysis - 获取交易分析数据
68 |
69 | ## 贡献指南
70 |
71 | 1. Fork 项目
72 | 2. 创建特性分支
73 | 3. 提交更改
74 | 4. 推送到分支
75 | 5. 创建 Pull Request
76 |
77 | ## 许可证
78 |
79 | MIT
--------------------------------------------------------------------------------
/storage/storage.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/showntop/llmack/llm"
8 | )
9 |
10 | type Journey struct {
11 | SessionID string
12 | StepNo int
13 | Kind string
14 | Duration time.Duration
15 | Messages []llm.Message
16 | Metadata any
17 | }
18 |
19 | type Storage interface {
20 | AddNewJourney(ctx context.Context, journey *Journey) error
21 | SaveSession(ctx context.Context, session *Session) error
22 | FetchSession(ctx context.Context, id string) (*Session, error)
23 | UpdateSession(ctx context.Context, session *Session) error
24 | DeleteSession(ctx context.Context, id string) error
25 | }
26 |
27 | type NoneStorage struct {
28 | }
29 |
30 | func (s *NoneStorage) SaveSession(ctx context.Context, session *Session) error {
31 | return nil
32 | }
33 |
34 | func (s *NoneStorage) FetchSession(ctx context.Context, id string) (*Session, error) {
35 | return nil, nil
36 | }
37 |
38 | func (s *NoneStorage) UpdateSession(ctx context.Context, session *Session) error {
39 | return nil
40 | }
41 |
42 | func (s *NoneStorage) DeleteSession(ctx context.Context, id string) error {
43 | return nil
44 | }
45 |
--------------------------------------------------------------------------------
/prompt/readme:
--------------------------------------------------------------------------------
1 | # Prompt Core
2 |
3 | A Go implementation of prompt optimization framework inspired by Ape, DSPy and PromptWizard.
4 |
5 | ## Key Components
6 |
7 | 1. Prompt Management
8 | - Structured prompt representation
9 | - Template system for dynamic prompts
10 | - Version control for prompts
11 |
12 | 2. Optimization Pipeline
13 | - Mutation strategies
14 | - Evaluation metrics
15 | - Feedback loop
16 |
17 | 3. Training Framework
18 | - Few-shot learning
19 | - Example management
20 | - Performance tracking
21 |
22 | 4. Integration
23 | - LLM API connectors
24 | - Metrics collection
25 | - Result analysis
26 |
27 | ## Basic Usage
28 |
29 |
30 | ## Key Features
31 |
32 | 1. Modular Architecture
33 | - Easily extensible components
34 | - Pluggable optimization strategies
35 | - Custom metric support
36 |
37 | 2. Built-in Methods
38 | - Few-shot optimization
39 | - Prompt mutation
40 | - Example synthesis
41 | - Performance evaluation
42 |
43 | 3. Production Ready
44 | - Concurrent optimization
45 | - Result caching
46 | - Error handling
47 | - Logging
48 |
49 | 4. Developer Experience
50 | - Clear interfaces
51 | - Comprehensive documentation
52 | - Example implementations
--------------------------------------------------------------------------------
/agent/response.go:
--------------------------------------------------------------------------------
1 | package agent
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/showntop/llmack/llm"
7 | )
8 |
9 | type TeamRunResponse struct {
10 | sync.RWMutex // for thread safe
11 |
12 | Reasoning string `json:"reasoning"`
13 | Answer string `json:"answer"`
14 | ToolCalls []llm.ToolCall `json:"tool_calls"`
15 | Stream chan *llm.Chunk
16 | MemberResponses []*AgentRunResponse
17 |
18 | Error error
19 | }
20 |
21 | func (t *TeamRunResponse) AddMemberResponse(response *AgentRunResponse) {
22 | t.Lock()
23 | defer t.Unlock()
24 |
25 | if t.MemberResponses == nil {
26 | t.MemberResponses = []*AgentRunResponse{}
27 | }
28 | t.MemberResponses = append(t.MemberResponses, response)
29 | }
30 |
31 | func (t *TeamRunResponse) String() string {
32 | return t.Answer
33 | }
34 |
35 | type AgentRunResponse struct {
36 | Reasoning string `json:"reasoning"`
37 | Answer string `json:"answer"`
38 | ToolCalls []llm.ToolCall `json:"tool_calls"`
39 | Error error
40 |
41 | Stream chan *llm.Chunk
42 | Usage llm.Usage
43 | }
44 |
45 | func (a *AgentRunResponse) Completion() string {
46 | return a.Answer
47 | }
48 |
--------------------------------------------------------------------------------
/llm/openai-c/llm.go:
--------------------------------------------------------------------------------
1 | package deepseek
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 |
8 | "github.com/showntop/llmack/llm"
9 | )
10 |
11 | // Name ...
12 | var Name = "openai-c"
13 |
14 | func init() {
15 | llm.Register(Name, func(o *llm.ProviderOptions) llm.Provider { return &LLM{} })
16 | }
17 |
18 | // LLM ...
19 | type LLM struct {
20 | once sync.Once
21 | engine *llm.OAILLM
22 | }
23 |
24 | // NewLLM ...
25 | func NewLLM() *LLM {
26 | return &LLM{}
27 | }
28 |
29 | // Name ...
30 | func (m *LLM) Name() string {
31 | return Name
32 | }
33 |
34 | // Invoke ...
35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) {
36 | var err error
37 | m.once.Do(func() {
38 | config, _ := llm.Config.Get(Name).(map[string]any)
39 | if config == nil {
40 | err = fmt.Errorf("openai-c config not found")
41 | }
42 | apiKey, _ := config["api_key"].(string)
43 | baseURL, _ := config["base_url"].(string)
44 | url := baseURL + "/chat/completions"
45 | m.engine = llm.NewOAILLM(url, apiKey)
46 | })
47 | if err != nil {
48 | return nil, err
49 | }
50 | return m.engine.Invoke(ctx, messages, opts)
51 | }
52 |
--------------------------------------------------------------------------------
/rag/deepdoc/chunk/chunk_test.go:
--------------------------------------------------------------------------------
1 | package chunk
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | )
7 |
8 | func TestBase_mergeSplits(t *testing.T) {
9 | type fields struct {
10 | chunkSize int
11 | overlapSize int
12 | }
13 | type args struct {
14 | splits []string
15 | separator string
16 | }
17 | tests := []struct {
18 | name string
19 | fields fields
20 | args args
21 | want []string
22 | }{
23 | // TODO: Add test cases.
24 | {
25 | name: "test1",
26 | fields: fields{
27 | chunkSize: 21,
28 | overlapSize: 5,
29 | },
30 | args: args{
31 | splits: []string{"1234567890", "1234567890", "1234567890", "1234567890"},
32 | separator: ",",
33 | },
34 | want: []string{"1234567890,1234567890", "1234567890,1234567890"},
35 | },
36 | }
37 | for _, tt := range tests {
38 | t.Run(tt.name, func(t *testing.T) {
39 | b := &Base{
40 | chunkSize: tt.fields.chunkSize,
41 | overlapSize: tt.fields.overlapSize,
42 | }
43 | if got := b.mergeSplits(tt.args.splits, tt.args.separator); !reflect.DeepEqual(got, tt.want) {
44 | t.Errorf("Base.mergeSplits() = %v, want %v", got, tt.want)
45 | }
46 | })
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/speech/tts/aliyun/utils.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | // Header is the basic struct for the TTS header.
10 | type Header struct {
11 | MessageID string `json:"message_id"`
12 | TaskID string `json:"task_id"`
13 | Namespace string `json:"namespace"`
14 | Name string `json:"name"`
15 | Appkey string `json:"appkey"`
16 | }
17 |
18 | func newHeader(taskID, name string) Header {
19 | return Header{
20 | TaskID: taskID,
21 | Namespace: "FlowingSpeechSynthesizer",
22 | Name: name,
23 | Appkey: "N3CyrsLkHXD2yD3c",
24 | MessageID: Get32UUID(),
25 | }
26 | }
27 |
28 | // Response is the basic struct for the TTS response.
29 | type Response struct {
30 | Header Header `json:"header"`
31 | Payload map[string]interface{} `json:"payload,omitempty"`
32 | }
33 |
34 | // Request is the basic struct for the TTS Request.
35 | type Request struct {
36 | Header Header `json:"header"`
37 | Payload map[string]interface{} `json:"payload,omitempty"`
38 | }
39 |
40 | // Get32UUID ...
41 | func Get32UUID() string {
42 | return strings.ReplaceAll(uuid.NewString(), "-", "")
43 | }
44 |
--------------------------------------------------------------------------------
/example/llm/deepseek/deepseek.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 |
9 | "github.com/joho/godotenv"
10 |
11 | "github.com/showntop/llmack/llm"
12 | "github.com/showntop/llmack/llm/deepseek"
13 | )
14 |
15 | func init() {
16 | godotenv.Load()
17 | }
18 |
19 | func main() {
20 | ctx := context.Background()
21 | llm.WithSingleConfig(map[string]any{
22 | "api_key": os.Getenv("deepseek_api_key"),
23 | // "api_key": os.Getenv("deepseek_api_key2"),
24 | // "base_url": "https://api.lkeap.cloud.tencent.com/v1",
25 | })
26 |
27 | resp, err := llm.NewInstance(deepseek.Name).Invoke(ctx,
28 | // []llm.Message{llm.UserPromptMessage("Prove that all entire functions that are also injective take the form f (z) = az + 6 with a, b € C, and a ‡ 0.")},
29 | []llm.Message{llm.NewUserTextMessage("你好")},
30 | llm.WithStream(true),
31 | llm.WithModel("deepseek-chat"))
32 | if err != nil {
33 | panic(err)
34 | }
35 | // fmt.Println(resp.Result())
36 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() {
37 | xxx, _ := json.Marshal(it)
38 | fmt.Println(string(xxx))
39 | // fmt.Println("final: ", it.Delta.Message)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/embedding/string_embedder_test.go:
--------------------------------------------------------------------------------
1 | package embedding
2 |
3 | import (
4 | "context"
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | func TestStringEmbedder_Embed(t *testing.T) {
10 | type args struct {
11 | ctx context.Context
12 | text string
13 | }
14 | tests := []struct {
15 | name string
16 | s StringEmbedder
17 | args args
18 | want []float32
19 | wantErr bool
20 | }{
21 | // TODO: Add test cases.
22 | {
23 | name: "test",
24 | s: StringEmbedder{},
25 | args: args{
26 | ctx: context.Background(),
27 | text: "hello world",
28 | },
29 | want: []float32{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
30 | wantErr: false,
31 | },
32 | }
33 | for _, tt := range tests {
34 | t.Run(tt.name, func(t *testing.T) {
35 | s := StringEmbedder{}
36 | got, err := s.Embed(tt.args.ctx, tt.args.text)
37 | if (err != nil) != tt.wantErr {
38 | t.Errorf("StringEmbedder.Embed() error = %v, wantErr %v", err, tt.wantErr)
39 | return
40 | }
41 | if !reflect.DeepEqual(got, tt.want) {
42 | t.Errorf("StringEmbedder.Embed() = %v, want %v", got, tt.want)
43 | }
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/example/agent/browser/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 | "github.com/showntop/llmack/agent"
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/llm/deepseek"
12 | "github.com/showntop/llmack/log"
13 | "github.com/showntop/llmack/pkg/browser"
14 | )
15 |
16 | func init() {
17 | godotenv.Load()
18 |
19 | log.SetLogger(&log.WrapLogger{})
20 |
21 | // llm.WithSingleConfig(map[string]any{
22 | // "api_key": os.Getenv("qwen_api_key"),
23 | // })
24 | llm.WithSingleConfig(map[string]any{
25 | "api_key": os.Getenv("deepseek_api_key"),
26 | })
27 | }
28 |
29 | func main() {
30 |
31 | browserAgent := agent.NewBrowserAgent("browser agent",
32 | // agent.WithModel(llm.NewInstance(qwen.Name, llm.WithDefaultModel("qwen-vl-max-latest"))),
33 | agent.WithModel(llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat"))),
34 | agent.WithBrowserConfig(&browser.BrowserConfig{
35 | "headless": false,
36 | }),
37 | )
38 |
39 | response := browserAgent.Invoke(context.Background(), "去美团外卖查找所有烤鸭店的电话号码、名称、地址")
40 | if response.Error != nil {
41 | panic(response.Error)
42 | }
43 | fmt.Println(response.Completion())
44 | }
45 |
--------------------------------------------------------------------------------
/rag/deepdoc/doc.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "os"
5 | "os/exec"
6 |
7 | "baliance.com/gooxml/document"
8 | "github.com/google/uuid"
9 | )
10 |
11 | // DocDocument ...
12 | type DocDocument struct {
13 | }
14 |
15 | // Doc ...
16 | func Doc() *DocDocument {
17 | return &DocDocument{}
18 | }
19 |
20 | // Extract ...
21 | func (d *DocDocument) Extract(filename string, binary []byte) ([]string, error) {
22 | // save tmp file
23 | f, err := os.CreateTemp("/tmp", uuid.NewString())
24 | // defer os.Remove(f.Name())
25 | _, err = f.Write(binary)
26 | // safe 校验
27 | cmd := exec.Command("soffice", "--headless", "--convert-to", "docx", f.Name(), "--outdir", "/tmp")
28 | // cmd.Stdout = os.Stdout
29 | // cmd.Stderr = os.Stderr
30 | if err := cmd.Run(); err != nil {
31 | return nil, err
32 | }
33 |
34 | // doc, err := document.Read(bytes.NewReader(binary), int64(len(binary)))
35 | doc, err := document.Open(f.Name() + ".docx")
36 | if err != nil {
37 | return nil, err
38 | }
39 |
40 | sections := []string{}
41 | for _, para := range doc.Paragraphs() {
42 | //run为每个段落相同格式的文字组成的片段
43 | for _, run := range para.Runs() {
44 | sections = append(sections, run.Text())
45 | }
46 | }
47 | return sections, nil
48 | }
49 |
--------------------------------------------------------------------------------
/llm/doubao/doubao.go:
--------------------------------------------------------------------------------
1 | package doubao
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 |
8 | "github.com/showntop/llmack/llm"
9 | )
10 |
11 | // Name ...
12 | var Name = "doubao"
13 |
14 | func init() {
15 | llm.Register(Name, NewLLM)
16 | }
17 |
18 | // LLM ...
19 | type LLM struct {
20 | once sync.Once
21 | engine *llm.OAILLM
22 | }
23 |
24 | // NewLLM ...
25 | func NewLLM(o *llm.ProviderOptions) llm.Provider {
26 | return &LLM{}
27 | }
28 |
29 | // Name ...
30 | func (m *LLM) Name() string {
31 | return Name
32 | }
33 |
34 | // Invoke ...
35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) {
36 | var err error
37 | m.once.Do(func() {
38 | url := "https://ark.cn-beijing.volces.com/api/v3"
39 | config, _ := llm.Config.Get(Name).(map[string]any)
40 | if config == nil {
41 | err = fmt.Errorf("doubao config not found")
42 | }
43 | apiKey, _ := config["api_key"].(string)
44 | baseURL, _ := config["base_url"].(string)
45 | if baseURL != "" {
46 | url = baseURL + "/chat/completions"
47 | }
48 | m.engine = llm.NewOAILLM(url, apiKey)
49 | })
50 | if err != nil {
51 | return nil, err
52 | }
53 | return m.engine.Invoke(ctx, messages, opts)
54 | }
55 |
--------------------------------------------------------------------------------
/llm/anthropic/claude.go:
--------------------------------------------------------------------------------
1 | package anthropic
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 |
8 | "github.com/showntop/llmack/llm"
9 | )
10 |
11 | // Name ...
12 | var Name = "anthropic"
13 |
14 | func init() {
15 | llm.Register(Name, NewLLM)
16 | }
17 |
18 | // LLM ...
19 | type LLM struct {
20 | once sync.Once
21 | engine *llm.OAILLM
22 | }
23 |
24 | // NewLLM ...
25 | func NewLLM(options *llm.ProviderOptions) llm.Provider {
26 | return &LLM{}
27 | }
28 |
29 | // Name ...
30 | func (m *LLM) Name() string {
31 | return Name
32 | }
33 |
34 | // Invoke ...
35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) {
36 | var err error
37 | m.once.Do(func() {
38 | url := "https://api.anthropic.com/v1/messages"
39 | config, _ := llm.Config.Get(Name).(map[string]any)
40 | if config == nil {
41 | err = fmt.Errorf("claude config not found")
42 | }
43 | apiKey, _ := config["api_key"].(string)
44 | baseURL, _ := config["base_url"].(string)
45 | if baseURL != "" {
46 | url = baseURL + "/chat/completions"
47 | }
48 | m.engine = llm.NewOAILLM(url, apiKey)
49 | })
50 | if err != nil {
51 | return nil, err
52 | }
53 | return m.engine.Invoke(ctx, messages, opts)
54 | }
55 |
--------------------------------------------------------------------------------
/program/prompt.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | // Field represents an input or output field in a signature
9 | type Field struct {
10 | Name string
11 | Type reflect.Kind
12 | Description string
13 | Marker string
14 | Kind string // input or output
15 | }
16 |
17 | // Promptx defines the instruction and input/output about a program
18 | type Promptx struct {
19 | Name string
20 | Description string
21 | Prompt string
22 | Instruction string
23 | InputFields map[string]*Field
24 | OutputFields map[string]*Field
25 | }
26 |
27 | // ValidateInputs checks if the provided inputs match the signature
28 | func (m *Promptx) ValidateInputs(inputs map[string]any) error {
29 | for name := range m.InputFields {
30 | if _, ok := inputs[name]; !ok {
31 | return fmt.Errorf("missing required input field: %s", name)
32 | }
33 | }
34 | return nil
35 | }
36 |
37 | // ValidateOutputs checks if the outputs match the signature
38 | func (m *Promptx) ValidateOutputs(outputs map[string]any) error {
39 | for name := range m.OutputFields {
40 | if _, ok := outputs[name]; !ok {
41 | return fmt.Errorf("missing required output field: %s", name)
42 | }
43 | }
44 | return nil
45 | }
46 |
--------------------------------------------------------------------------------
/embedding/string_embedder.go:
--------------------------------------------------------------------------------
1 | package embedding
2 |
3 | import "context"
4 |
5 | // StringEmbedder ...
6 | type StringEmbedder struct {
7 | }
8 |
9 | // NewStringEmbedder 创建一个新的StringEmbedder实例
10 | var NewStringEmbedder = func() Embedder {
11 | return StringEmbedder{}
12 | }
13 |
14 | // Embed 将文本转换为固定维度的向量
15 | func (s StringEmbedder) Embed(ctx context.Context, text string) ([]float32, error) {
16 | // 1. 固定向量长度
17 | vectorSize := s.Dimension()
18 | result := make([]float32, vectorSize)
19 |
20 | // 2. 获取字符串的字节
21 | bytes := []byte(text)
22 |
23 | // 3. 使用字节生成向量
24 | for i := range vectorSize {
25 | var sum float32 = 0
26 | // 对每个位置,使用一组字节计算一个float值
27 | for j := i; j < len(bytes); j += vectorSize {
28 | sum += float32(bytes[j%len(bytes)])
29 | }
30 | result[i] = sum
31 | }
32 |
33 | return result, nil
34 | }
35 |
36 | // BatchEmbed 批量将多个文本转换为向量
37 | func (s StringEmbedder) BatchEmbed(ctx context.Context, texts []string) ([][]float32, error) {
38 | results := make([][]float32, len(texts))
39 | for i, text := range texts {
40 | embedding, err := s.Embed(ctx, text)
41 | if err != nil {
42 | return nil, err
43 | }
44 | results[i] = embedding
45 | }
46 | return results, nil
47 | }
48 |
49 | // Dimension ...
50 | func (s StringEmbedder) Dimension() int {
51 | return 32
52 | }
53 |
--------------------------------------------------------------------------------
/llm/deepseek/llm.go:
--------------------------------------------------------------------------------
1 | package deepseek
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 |
8 | "github.com/showntop/llmack/llm"
9 | )
10 |
11 | // Name ...
12 | var Name = "deepseek"
13 |
14 | func init() {
15 | llm.Register(Name, NewLLM)
16 | }
17 |
18 | // LLM ...
19 | type LLM struct {
20 | once sync.Once
21 | engine *llm.OAILLM
22 | }
23 |
24 | // NewLLM ...
25 | func NewLLM(o *llm.ProviderOptions) llm.Provider {
26 | return &LLM{}
27 | }
28 |
29 | // Name ...
30 | func (m *LLM) Name() string {
31 | return Name
32 | }
33 |
34 | // Invoke ...
35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) {
36 | var err error
37 | m.once.Do(func() {
38 | url := "https://api.deepseek.com/chat/completions"
39 | config, _ := llm.Config.Get(Name).(map[string]any)
40 | if config == nil {
41 | err = fmt.Errorf("deepseek config not found")
42 | // return nil, fmt.Errorf("deepseek config not found")
43 | }
44 | apiKey, _ := config["api_key"].(string)
45 | baseURL, _ := config["base_url"].(string)
46 | if baseURL != "" {
47 | url = baseURL + "/chat/completions"
48 | }
49 | m.engine = llm.NewOAILLM(url, apiKey)
50 | })
51 | if err != nil {
52 | return nil, err
53 | }
54 | return m.engine.Invoke(ctx, messages, opts)
55 | }
56 |
--------------------------------------------------------------------------------
/llm/cache_query.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | // QueryProcessor ...
8 | type QueryProcessor func([]Message) (string, error)
9 |
10 | // LastQueryMessage ...
11 | func LastQueryMessage(messages []Message) (string, error) {
12 | return messages[len(messages)-1].Content(), nil // TODO when with multipart content
13 | }
14 |
15 | // ConcatQueryMessage ...
16 | func ConcatQueryMessage(messages []Message) (string, error) {
17 | builder := strings.Builder{}
18 | for i := 0; i < len(messages); i++ {
19 | builder.WriteString(string(messages[i].Role()))
20 | builder.WriteByte(':')
21 | builder.WriteString(messages[i].Content())
22 | }
23 | return builder.String(), nil
24 | }
25 |
26 | // CompressQueryMessage ...
27 | func CompressQueryMessage(messages []Message) (string, error) {
28 | builder := strings.Builder{}
29 | for i := 0; i < len(messages); i++ {
30 | builder.WriteString(string(messages[i].Role()))
31 | builder.WriteByte(':')
32 | builder.WriteString(messages[i].Content())
33 | }
34 | return builder.String(), nil
35 | }
36 |
37 | // SummarizeQueryMessage extracts an intelligent summary from conversation messages
38 | func SummarizeQueryMessage(messages []Message) (string, error) {
39 | // 如果消息为空则返回
40 | if len(messages) == 0 {
41 | return "", nil
42 | }
43 | summary := ""
44 |
45 | return summary, nil
46 | }
47 |
--------------------------------------------------------------------------------
/tool/crawl/tool.go:
--------------------------------------------------------------------------------
1 | package crawl
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | "github.com/showntop/llmack/tool"
9 | )
10 |
11 | var (
12 | Jina = "Jina"
13 | Colly = "Colly"
14 | )
15 |
16 | func init() {
17 | register(Jina)
18 | register(Colly)
19 | }
20 |
21 | func register(name string) {
22 | t := tool.New(
23 | tool.WithName(name),
24 | tool.WithKind("code"),
25 | tool.WithDescription("Used to scrape website urls and extract text content"),
26 | tool.WithParameters(tool.Parameter{
27 | Name: "link",
28 | Type: tool.String,
29 | Required: true,
30 | LLMDescrition: "Valid website url without any quotes.",
31 | Default: "",
32 | }),
33 | tool.WithFunction(func(ctx context.Context, args string) (string, error) {
34 | var params struct {
35 | Link string `json:"link"`
36 | }
37 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
38 | return "", err
39 | }
40 | engine, ok := Crawlers[name]
41 | if !ok {
42 | return "", fmt.Errorf("crawler %s not found", name)
43 | }
44 | result, err := engine.Crawl(ctx, params.Link)
45 | if err != nil {
46 | return "", err
47 | }
48 | bytes, err := json.Marshal(result)
49 | if err != nil {
50 | return "", err
51 | }
52 | return string(bytes), nil
53 | }),
54 | )
55 | tool.Register(t)
56 | }
57 |
--------------------------------------------------------------------------------
/example/bookkeeper/api/analysis.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 |
7 | "github.com/bookkeeper-ai/bookkeeper/services"
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | type AnalysisHandler struct {
12 | analysisService *services.AnalysisService
13 | }
14 |
15 | func NewAnalysisHandler(analysisService *services.AnalysisService) *AnalysisHandler {
16 | return &AnalysisHandler{
17 | analysisService: analysisService,
18 | }
19 | }
20 |
21 | // GetMonthlyAnalysis 获取月度消费分析
22 | func (h *AnalysisHandler) GetMonthlyAnalysis(c *gin.Context) {
23 | userID := uint(1) // TODO: 从认证中获取用户ID
24 | year, _ := strconv.Atoi(c.DefaultQuery("year", "2024"))
25 | month, _ := strconv.Atoi(c.DefaultQuery("month", "1"))
26 |
27 | analysis, err := h.analysisService.GetMonthlyAnalysis(userID, year, month)
28 | if err != nil {
29 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
30 | return
31 | }
32 |
33 | c.JSON(http.StatusOK, analysis)
34 | }
35 |
36 | // GetBudgetRecommendation 获取预算建议
37 | func (h *AnalysisHandler) GetBudgetRecommendation(c *gin.Context) {
38 | userID := uint(1) // TODO: 从认证中获取用户ID
39 |
40 | recommendation, err := h.analysisService.GetBudgetRecommendation(userID)
41 | if err != nil {
42 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
43 | return
44 | }
45 |
46 | c.JSON(http.StatusOK, recommendation)
47 | }
48 |
--------------------------------------------------------------------------------
/example/llm/asure-openai/openai.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 |
10 | "github.com/showntop/llmack/llm"
11 | openaic "github.com/showntop/llmack/llm/openai-c"
12 | "github.com/showntop/llmack/log"
13 | )
14 |
15 | func init() {
16 | godotenv.Load()
17 | }
18 |
19 | func main() {
20 | runWithCache()
21 | }
22 |
23 | func runWithCache() {
24 | ctx := context.Background()
25 |
26 | llm.WithSingleConfig(map[string]any{
27 | "base_url": os.Getenv("hunyuan_base_url"),
28 | "api_key": os.Getenv("hunyuan_api_key"),
29 | })
30 |
31 | instance := llm.NewInstance(openaic.Name,
32 | llm.WithCache(llm.NewMemoCache()),
33 | llm.WithLogger(&log.WrapLogger{}),
34 | )
35 |
36 | resp, err := instance.Invoke(ctx,
37 | []llm.Message{llm.NewUserTextMessage("你好")},
38 | llm.WithModel("hunyuan"),
39 | llm.WithStream(true),
40 | )
41 | if err != nil {
42 | panic(err)
43 | }
44 | fmt.Println(resp.Result())
45 |
46 | resp, err = instance.Invoke(ctx,
47 | []llm.Message{llm.NewUserTextMessage("你好")},
48 | llm.WithModel("hunyuan"),
49 | llm.WithStream(true),
50 | )
51 | if err != nil {
52 | panic(err)
53 | }
54 | // stream := resp.Stream()
55 | // for v := stream.Next(); v != nil; v = stream.Next() {
56 | // fmt.Println(string(v.Delta.Message.Content().Data))
57 | // }
58 | fmt.Println(resp.Result())
59 | }
60 |
--------------------------------------------------------------------------------
/example/llm/openai-c/openai-c.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 |
10 | "github.com/showntop/llmack/llm"
11 | openaic "github.com/showntop/llmack/llm/openai-c"
12 | "github.com/showntop/llmack/log"
13 | )
14 |
15 | func init() {
16 | godotenv.Load()
17 | }
18 |
19 | func main() {
20 | runWithCache()
21 | }
22 |
23 | func runWithCache() {
24 | ctx := context.Background()
25 |
26 | llm.WithSingleConfig(map[string]any{
27 | "base_url": os.Getenv("hunyuan_base_url"),
28 | "api_key": os.Getenv("hunyuan_api_key"),
29 | })
30 |
31 | instance := llm.NewInstance(openaic.Name,
32 | llm.WithCache(llm.NewMemoCache()),
33 | llm.WithLogger(&log.WrapLogger{}),
34 | )
35 |
36 | resp, err := instance.Invoke(ctx,
37 | []llm.Message{llm.NewUserTextMessage("你好")},
38 | llm.WithModel("hunyuan"),
39 | llm.WithStream(true),
40 | )
41 | if err != nil {
42 | panic(err)
43 | }
44 | fmt.Println(resp.Result())
45 |
46 | resp, err = instance.Invoke(ctx,
47 | []llm.Message{llm.NewUserTextMessage("你好")},
48 | llm.WithModel("hunyuan"),
49 | llm.WithStream(true),
50 | )
51 | if err != nil {
52 | panic(err)
53 | }
54 | // stream := resp.Stream()
55 | // for v := stream.Next(); v != nil; v = stream.Next() {
56 | // fmt.Println(string(v.Delta.Message.Content().Data))
57 | // }
58 | fmt.Println(resp.Result())
59 | }
60 |
--------------------------------------------------------------------------------
/tool/crawl/jina.go:
--------------------------------------------------------------------------------
1 | package crawl
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "strings"
10 | )
11 |
12 | func init() {
13 | Register(Jina, NewJinaCrawler())
14 | }
15 |
16 | // JinaCrawler 使用jina搜索
17 | type JinaCrawler struct {
18 | }
19 |
20 | // NewJinaCrawler 创建jina爬虫
21 | func NewJinaCrawler() Crawler {
22 | return &JinaCrawler{}
23 | }
24 |
25 | // Crawl 爬取
26 | func (c *JinaCrawler) Crawl(ctx context.Context, link string) (*Result, error) {
27 | accessURL := fmt.Sprintf("https://r.jina.ai/%s", link)
28 |
29 | req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, accessURL, strings.NewReader(""))
30 | if err != nil {
31 | return nil, err
32 | }
33 | req.Header.Set("Accept", "application/json")
34 | resp, err := http.DefaultClient.Do(req)
35 | if err != nil {
36 | return nil, err
37 | }
38 | defer resp.Body.Close()
39 |
40 | if resp.StatusCode != 200 {
41 | return nil, fmt.Errorf("status code error: %d %s", resp.StatusCode, resp.Status)
42 | }
43 | raw, err := io.ReadAll(resp.Body)
44 | if err != nil {
45 | return nil, err
46 | }
47 | var result struct {
48 | Code int `json:"code"`
49 | Status int `json:"status"`
50 | Data Result `json:"data"`
51 | }
52 |
53 | if err := json.Unmarshal(raw, &result); err != nil {
54 | return nil, err
55 | }
56 |
57 | return &result.Data, nil
58 | }
59 |
--------------------------------------------------------------------------------
/tool/file/write_file.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "os"
7 |
8 | "github.com/showntop/llmack/tool"
9 | )
10 |
11 | const WriteFile = "WriteFile"
12 |
13 | func init() {
14 | t := tool.New(
15 | tool.WithName(WriteFile),
16 | tool.WithKind("code"),
17 | tool.WithDescription("Writes text to a file"),
18 | tool.WithParameters(
19 | tool.Parameter{
20 | Name: "file_name",
21 | LLMDescrition: "Name of the file to write. Only include the file name. Don't include path.",
22 | Type: tool.String,
23 | Required: true,
24 | },
25 | tool.Parameter{
26 | Name: "content",
27 | LLMDescrition: "File content to write.",
28 | Type: tool.String,
29 | Required: true,
30 | },
31 | ),
32 | tool.WithFunction(func(ctx context.Context, args string) (string, error) {
33 | var params struct {
34 | FileName string `json:"file_name"`
35 | Content string `json:"content"`
36 | }
37 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
38 | return "", err
39 | }
40 | ff, err := os.Create(params.FileName)
41 | if err != nil {
42 | return "", err
43 | }
44 | defer ff.Close()
45 | _, err = ff.WriteString(params.Content)
46 | if err != nil {
47 | return "", err
48 | }
49 | return "文件写入成功", nil
50 | }),
51 | )
52 | tool.Register(t)
53 | }
54 |
--------------------------------------------------------------------------------
/example/agent/memory/agent.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 | "github.com/showntop/llmack/agent"
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/llm/deepseek"
12 | "github.com/showntop/llmack/log"
13 | "github.com/showntop/llmack/memory"
14 | )
15 |
16 | var (
17 | model = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat"))
18 | )
19 |
20 | func init() {
21 | godotenv.Load()
22 |
23 | log.SetLogger(&log.WrapLogger{})
24 | llm.WithSingleConfig(map[string]any{
25 | "api_key": os.Getenv("deepseek_api_key"),
26 | })
27 | }
28 |
29 | func main() {
30 | sessionID := "session_1"
31 |
32 | agentx := agent.NewAgent("",
33 | agent.WithMemory(memory.NewFlatMemory(nil)),
34 | agent.WithModel(model),
35 | )
36 |
37 | response := agentx.Invoke(context.Background(),
38 | "My name is John Doe and I like to hike in the mountains on weekends.",
39 | agent.WithStream(false),
40 | agent.WithSessionID(sessionID),
41 | )
42 | if response.Error != nil {
43 | panic(response.Error)
44 | }
45 | fmt.Println(response.Answer)
46 |
47 | response2 := agentx.Invoke(context.Background(),
48 | "What are my hobbies?",
49 | agent.WithStream(false),
50 | agent.WithSessionID(sessionID),
51 | )
52 | if response2.Error != nil {
53 | panic(response2.Error)
54 | }
55 | fmt.Println(response2.Answer)
56 | }
57 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | a {
17 | font-weight: 500;
18 | color: #646cff;
19 | text-decoration: inherit;
20 | }
21 | a:hover {
22 | color: #535bf2;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | display: flex;
28 | place-items: center;
29 | min-width: 320px;
30 | min-height: 100vh;
31 | }
32 |
33 | h1 {
34 | font-size: 3.2em;
35 | line-height: 1.1;
36 | }
37 |
38 | button {
39 | border-radius: 8px;
40 | border: 1px solid transparent;
41 | padding: 0.6em 1.2em;
42 | font-size: 1em;
43 | font-weight: 500;
44 | font-family: inherit;
45 | background-color: #1a1a1a;
46 | cursor: pointer;
47 | transition: border-color 0.25s;
48 | }
49 | button:hover {
50 | border-color: #646cff;
51 | }
52 | button:focus,
53 | button:focus-visible {
54 | outline: 4px auto -webkit-focus-ring-color;
55 | }
56 |
57 | @media (prefers-color-scheme: light) {
58 | :root {
59 | color: #213547;
60 | background-color: #ffffff;
61 | }
62 | a:hover {
63 | color: #747bff;
64 | }
65 | button {
66 | background-color: #f9f9f9;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/workflow/definition.go:
--------------------------------------------------------------------------------
1 | package workflow
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Link 链接节点
8 | func (w *Workflow) LinkWithCondition(node1, expr string, node2 any, nodes ...Node) *Workflow {
9 | var targetID string
10 | if node, ok := node2.(Node); ok {
11 | targetID = node.ID
12 | w.AddNode(node)
13 | } else {
14 | targetID = node2.(string)
15 | }
16 | edges := make([]Edge, 0)
17 | edges = append(edges, Edge{
18 | ID: fmt.Sprintf("%s-%s", node1, targetID),
19 | Source: node1,
20 | Target: targetID,
21 | Express: expr,
22 | })
23 | w.Edges = append(w.Edges, edges...)
24 |
25 | if node, ok := node2.(Node); ok && len(nodes) > 0 {
26 | nodes = append([]Node{node}, nodes...)
27 | w.Link(nodes...)
28 | }
29 | return w
30 | }
31 |
32 | // Link 链接节点
33 | func (w *Workflow) Link(nodes ...Node) *Workflow {
34 | w.AddNode(nodes...)
35 |
36 | edges := make([]Edge, 0)
37 | for i := 0; i < len(nodes)-1; i++ {
38 | edges = append(edges, Edge{
39 | ID: fmt.Sprintf("%s-%s", nodes[i].ID, nodes[i+1].ID),
40 | Source: nodes[i].ID,
41 | Target: nodes[i+1].ID,
42 | })
43 | }
44 | w.Edges = append(w.Edges, edges...)
45 | return w
46 | }
47 |
48 | // AddNode 添加节点
49 | func (w *Workflow) AddNode(nodes ...Node) *Workflow {
50 | w.Nodes = append(w.Nodes, nodes...)
51 | return w
52 | }
53 |
54 | // NewWorkflow 初始化workflow
55 | func NewWorkflow(id int64, name string) *Workflow {
56 | return &Workflow{
57 | ID: id,
58 | Name: name,
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rag/indexer.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/showntop/llmack/log"
7 | "github.com/showntop/llmack/vdb"
8 | )
9 |
10 | // Indexer ...
11 | type Indexer struct {
12 | vdb vdb.VDB
13 | scalarDB ScalarDB // object
14 | }
15 |
16 | // NewIndexer ...
17 | func NewIndexer(name string, config any) (*Indexer, error) {
18 | vdb, err := vdb.NewVDB(name, config) // new vdb
19 | if err != nil {
20 | return nil, err
21 | }
22 |
23 | return &Indexer{vdb: vdb}, nil
24 | }
25 |
26 | // Retrieve TODO: 实现检索逻辑
27 | func (r *Indexer) Retrieve(ctx context.Context, query string, opts ...SearchOption) ([]*vdb.Document, error) {
28 | searchOpts := &SearchOptions{}
29 | for _, opt := range opts {
30 | opt(searchOpts)
31 | }
32 | log.InfoContextf(ctx, "Retrieve knowledge: %v query: %s options: %+v", searchOpts.LibraryID, query, searchOpts)
33 |
34 | vdbSearchOpts := &vdb.SearchOptions{
35 | TopK: searchOpts.TopK,
36 | Threshold: searchOpts.ScoreThreshold,
37 | }
38 | docs, err := r.vdb.SearchQueryWithOptions(ctx, query, vdbSearchOpts)
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | return docs, nil
44 | }
45 |
46 | func (r *Indexer) Index(ctx context.Context, docs []*vdb.Document, opts *SearchOptions) ([]*vdb.Document, error) {
47 | log.InfoContextf(ctx, "Index documents %+v with options %+v", docs, opts)
48 | // 将知识库中的数据转换为向量
49 | if err := r.vdb.Store(ctx, docs...); err != nil {
50 | return nil, err
51 | }
52 |
53 | return nil, nil
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/browser/dom/process_dom_test.go:
--------------------------------------------------------------------------------
1 | package dom
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "os"
7 | "testing"
8 | "time"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | func TestProcessDOM(t *testing.T) {
14 | pw, err := playwright.Run()
15 | if err != nil {
16 | t.Fatalf("could not start playwright: %v", err)
17 | }
18 | browser, err := pw.Chromium.Launch()
19 | if err != nil {
20 | t.Fatalf("could not launch browser: %v", err)
21 | }
22 | page, err := browser.NewPage()
23 | if err != nil {
24 | t.Fatalf("could not create page: %v", err)
25 | }
26 | if _, err = page.Goto("https://kayak.com/flights"); err != nil {
27 | t.Fatalf("could not goto: %v", err)
28 | }
29 |
30 | jsCode, err := os.ReadFile("./buildDomTree.js")
31 | if err != nil {
32 | t.Fatalf("failed to read JS file: %v", err)
33 | }
34 |
35 | start := time.Now()
36 | domTree, err := page.Evaluate(string(jsCode))
37 | if err != nil {
38 | log.Fatalf("failed to evaluate JS: %v", err)
39 | }
40 | elapsed := time.Since(start)
41 | t.Logf("Time: %.2fs\n", elapsed.Seconds())
42 |
43 | if err := os.MkdirAll("./tmp", 0755); err != nil {
44 | log.Fatalf("failed to create tmp dir: %v", err)
45 | }
46 | domTreeJSON, err := json.MarshalIndent(domTree, "", " ")
47 | if err != nil {
48 | log.Fatalf("failed to marshal domTree: %v", err)
49 | }
50 | if err := os.WriteFile("./tmp/dom.json", domTreeJSON, 0644); err != nil {
51 | log.Fatalf("failed to write dom.json: %v", err)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tool/browser/dom/process_dom_test.go:
--------------------------------------------------------------------------------
1 | package dom
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "os"
7 | "testing"
8 | "time"
9 |
10 | "github.com/playwright-community/playwright-go"
11 | )
12 |
13 | func TestProcessDOM(t *testing.T) {
14 | pw, err := playwright.Run()
15 | if err != nil {
16 | t.Fatalf("could not start playwright: %v", err)
17 | }
18 | browser, err := pw.Chromium.Launch()
19 | if err != nil {
20 | t.Fatalf("could not launch browser: %v", err)
21 | }
22 | page, err := browser.NewPage()
23 | if err != nil {
24 | t.Fatalf("could not create page: %v", err)
25 | }
26 | if _, err = page.Goto("https://kayak.com/flights"); err != nil {
27 | t.Fatalf("could not goto: %v", err)
28 | }
29 |
30 | jsCode, err := os.ReadFile("./buildDomTree.js")
31 | if err != nil {
32 | t.Fatalf("failed to read JS file: %v", err)
33 | }
34 |
35 | start := time.Now()
36 | domTree, err := page.Evaluate(string(jsCode))
37 | if err != nil {
38 | log.Fatalf("failed to evaluate JS: %v", err)
39 | }
40 | elapsed := time.Since(start)
41 | t.Logf("Time: %.2fs\n", elapsed.Seconds())
42 |
43 | if err := os.MkdirAll("./tmp", 0755); err != nil {
44 | log.Fatalf("failed to create tmp dir: %v", err)
45 | }
46 | domTreeJSON, err := json.MarshalIndent(domTree, "", " ")
47 | if err != nil {
48 | log.Fatalf("failed to marshal domTree: %v", err)
49 | }
50 | if err := os.WriteFile("./tmp/dom.json", domTreeJSON, 0644); err != nil {
51 | log.Fatalf("failed to write dom.json: %v", err)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/example/bookkeeper/services/chat_service.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | "github.com/showntop/llmack/llm"
8 | "github.com/showntop/llmack/llm/qwen"
9 |
10 | "github.com/bookkeeper-ai/bookkeeper/config"
11 | )
12 |
13 | type ChatService struct {
14 | llmClient *llm.Instance
15 | }
16 |
17 | func NewChatService(cfg *config.Config) (*ChatService, error) {
18 | // 初始化 LLM 客户端
19 | // llmClient := llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat"))
20 | llmClient := llm.NewInstance(qwen.Name, llm.WithDefaultModel("qwen-vl-max"))
21 |
22 | return &ChatService{
23 | llmClient: llmClient,
24 | }, nil
25 | }
26 |
27 | func (s *ChatService) SendMessage(ctx context.Context, message string, images []string) (string, error) {
28 | multipartContents := make([]*llm.MultipartContent, 0)
29 | multipartContents = append(multipartContents, llm.MultipartContentText(message))
30 | for _, image := range images {
31 | multipartContents = append(multipartContents, llm.MultipartContentImageURL(image))
32 | }
33 |
34 | // 构建消息
35 | messages := []llm.Message{
36 | llm.NewSystemMessage("你是一个智能记账助手,根据用户的输入识别其中的内容,并根据内容生成记账凭证。"),
37 | llm.NewUserMultipartMessage(
38 | multipartContents...,
39 | ),
40 | }
41 |
42 | // 调用 LLM
43 | response, err := s.llmClient.Invoke(ctx, messages, llm.WithStream(true))
44 | if err != nil {
45 | log.Printf("Error invoking LLM: %v", err)
46 | return "", err
47 | }
48 | return response.Result().Message.Content(), nil
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/browser/utils.go:
--------------------------------------------------------------------------------
1 | package browser
2 |
3 | import (
4 | "errors"
5 | "runtime"
6 |
7 | "github.com/kbinani/screenshot"
8 | )
9 |
10 | // getScreenResolution returns the width and height of the main (first) display
11 | func getScreenResolution() map[string]int {
12 | n := screenshot.NumActiveDisplays()
13 | if n == 0 {
14 | return map[string]int{"width": 1920, "height": 1080} // fallback
15 | }
16 | b := screenshot.GetDisplayBounds(0)
17 | return map[string]int{
18 | "width": b.Dx(),
19 | "height": b.Dy(),
20 | }
21 | }
22 |
23 | // getWindowAdjustments returns (borderAdjust, titlebarAdjust) for major OSes
24 | func getWindowAdjustments() (int, int) {
25 | os := runtime.GOOS
26 | switch os {
27 | case "darwin":
28 | return -4, 24 // macOS: small title bar, no border
29 | case "win32":
30 | return -8, 0 // Windows: border on the left
31 | default:
32 | return 0, 0 // Linux or others
33 | }
34 | }
35 |
36 | func ParseNumberToInt(value any) (int, error) {
37 | if value == nil {
38 | return 0, nil
39 | }
40 | if v, ok := value.(int); ok {
41 | return v, nil
42 | }
43 | if v, ok := value.(float64); ok {
44 | return int(v), nil
45 | }
46 | return 0, errors.New("value is not a number")
47 | }
48 |
49 | func ParseNumberToFloat(value any) (float64, error) {
50 | if value == nil {
51 | return 0, nil
52 | }
53 | if v, ok := value.(float64); ok {
54 | return v, nil
55 | }
56 | if v, ok := value.(int); ok {
57 | return float64(v), nil
58 | }
59 | return 0, errors.New("value is not a number")
60 | }
61 |
--------------------------------------------------------------------------------
/embedding/openai_embedder.go:
--------------------------------------------------------------------------------
1 | package embedding
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/sashabaranov/go-openai"
7 | )
8 |
9 | // OpenAIEmbedder OpenAI embedder
10 | type OpenAIEmbedder struct {
11 | client *openai.Client
12 | model openai.EmbeddingModel
13 | }
14 |
15 | // NewOpenAIEmbedder 创建一个新的OpenAI embedder实例
16 | func NewOpenAIEmbedder(apiKey string, model openai.EmbeddingModel) *OpenAIEmbedder {
17 | if model == "" {
18 | model = openai.AdaEmbeddingV2 // 默认使用 text-embedding-ada-002
19 | }
20 |
21 | return &OpenAIEmbedder{
22 | client: openai.NewClient(apiKey),
23 | model: model,
24 | }
25 | }
26 |
27 | // Embed 将文本转换为向量
28 | func (e *OpenAIEmbedder) Embed(ctx context.Context, text string) ([]float32, error) {
29 | resp, err := e.client.CreateEmbeddings(ctx, openai.EmbeddingRequest{
30 | Input: []string{text},
31 | Model: e.model,
32 | })
33 |
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | if len(resp.Data) == 0 {
39 | return []float32{}, nil
40 | }
41 |
42 | return resp.Data[0].Embedding, nil
43 | }
44 |
45 | // BatchEmbed 批量将多个文本转换为向量
46 | func (e *OpenAIEmbedder) BatchEmbed(ctx context.Context, texts []string) ([][]float32, error) {
47 | resp, err := e.client.CreateEmbeddings(ctx, openai.EmbeddingRequest{
48 | Input: texts,
49 | Model: e.model,
50 | })
51 |
52 | if err != nil {
53 | return nil, err
54 | }
55 |
56 | embeddings := make([][]float32, len(resp.Data))
57 | for i, data := range resp.Data {
58 | embeddings[i] = data.Embedding
59 | }
60 |
61 | return embeddings, nil
62 | }
63 |
--------------------------------------------------------------------------------
/agent/memory.go:
--------------------------------------------------------------------------------
1 | package agent
2 |
3 | import (
4 | "context"
5 | "sync"
6 | )
7 |
8 | type TeamMemory struct {
9 | sync.RWMutex
10 |
11 | SharedContexts SharedContexts
12 | }
13 |
14 | type SharedContexts struct {
15 | Context string
16 | TeamMemberInteractions []TeamMemberInteraction
17 | }
18 |
19 | type TeamMemberInteraction struct {
20 | MemberName string
21 | Task string
22 | Response *AgentRunResponse
23 | }
24 |
25 | func (t *TeamMemory) SetSharedContext(ctx context.Context, text string) (string, error) {
26 | t.Lock()
27 | defer t.Unlock()
28 |
29 | t.SharedContexts.Context = text
30 | return t.SharedContexts.Context, nil
31 | }
32 |
33 | func (t *TeamMemory) GetSharedContext(ctx context.Context) (string, error) {
34 | t.RLock()
35 | defer t.RUnlock()
36 |
37 | return t.SharedContexts.Context, nil
38 | }
39 |
40 | func (t *TeamMemory) GetTeamMemberInteractions(ctx context.Context) ([]TeamMemberInteraction, error) {
41 | t.RLock()
42 | defer t.RUnlock()
43 |
44 | return t.SharedContexts.TeamMemberInteractions, nil
45 | }
46 |
47 | func (t *TeamMemory) AddTeamMemberInteractions(ctx context.Context, memberName string, task string, response *AgentRunResponse) error {
48 | t.Lock()
49 | defer t.Unlock()
50 |
51 | t.SharedContexts.TeamMemberInteractions = append(t.SharedContexts.TeamMemberInteractions, struct {
52 | MemberName string
53 | Task string
54 | Response *AgentRunResponse
55 | }{
56 | MemberName: memberName,
57 | Task: task,
58 | Response: response,
59 | })
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/llm/cache.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "time"
7 |
8 | "github.com/showntop/llmack/embedding"
9 | "github.com/showntop/llmack/vdb"
10 | )
11 |
12 | // CachedDocument ...
13 | type CachedDocument struct {
14 | ID string
15 | Query string
16 | Answer string
17 | Score float64
18 | Vector []float32
19 | }
20 |
21 | // CacheFactory ...
22 | type CacheFactory func() Cache
23 |
24 | // Cache ...
25 | type Cache interface {
26 | Fetch(context.Context, []Message) (*CachedDocument, bool, error)
27 | Store(context.Context, *CachedDocument, string) error
28 | }
29 |
30 | // BaseCache ...
31 | type BaseCache struct {
32 | Config *CacheConfig
33 | Embedder embedding.Embedder
34 | VectorStore vdb.VDB
35 | Ranker Scorer
36 | }
37 |
38 | // SetQueryProcessor ...
39 | func (c *BaseCache) SetQueryProcessor(p QueryProcessor) {
40 | c.Config.QueryProcessor = p
41 | }
42 |
43 | // CacheConfig 定义缓存的配置选项
44 | type CacheConfig struct {
45 | TTL time.Duration
46 | MaxEntries int
47 | CleanupInterval time.Duration
48 | EnableMetrics bool
49 | QueryProcessor QueryProcessor
50 | }
51 |
52 | // DefaultConfig 返回默认配置
53 | func DefaultConfig() *CacheConfig {
54 | return &CacheConfig{
55 | TTL: 5 * time.Minute,
56 | MaxEntries: 1000,
57 | CleanupInterval: 1 * time.Minute,
58 | EnableMetrics: true,
59 | QueryProcessor: LastQueryMessage,
60 | }
61 | }
62 |
63 | // 添加自定义错误类型
64 | var (
65 | ErrCacheMiss = errors.New("cache miss")
66 | ErrCacheFull = errors.New("cache is full")
67 | )
68 |
--------------------------------------------------------------------------------
/tool/entity.go:
--------------------------------------------------------------------------------
1 | package tool
2 |
3 | import (
4 | "github.com/getkin/kin-openapi/openapi3"
5 | )
6 |
7 | // ParameterType 枚举类型
8 | type ParameterType string
9 |
10 | const (
11 | Array ParameterType = "array"
12 | String ParameterType = "string"
13 | Number ParameterType = "number"
14 | Boolean ParameterType = "boolean"
15 | Select ParameterType = "select"
16 | SecretInput ParameterType = "secret-input"
17 | File ParameterType = "file"
18 | )
19 |
20 | // Parameter 用于描述API参数的结构体
21 | type Parameter struct {
22 | Name string
23 | Label string
24 | HumanDescription string
25 | Placeholder string
26 | Type ParameterType
27 | LLMDescrition string
28 | Required bool
29 | Default any
30 | Min float64
31 | Max float64
32 | Options []string
33 | }
34 |
35 | type ParamsOneOf struct {
36 | params1 []Parameter
37 | params2 *openapi3.Schema
38 | }
39 |
40 | func (p *ParamsOneOf) Parameters() any {
41 | if p == nil {
42 | return nil
43 | }
44 | if len(p.params1) > 0 {
45 | properties := map[string]any{}
46 | required := []string{}
47 | for _, p := range p.params1 {
48 | properties[p.Name] = map[string]any{
49 | "description": p.LLMDescrition,
50 | "type": p.Type,
51 | }
52 | if p.Required {
53 | required = append(required, p.Name)
54 | }
55 | }
56 | return map[string]any{
57 | "type": "object",
58 | "properties": properties,
59 | "required": required,
60 | }
61 | }
62 | return p.params2
63 | }
64 |
--------------------------------------------------------------------------------
/example/llm/qwen/qwen.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 |
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/llm/qwen"
12 | "github.com/showntop/llmack/log"
13 | )
14 |
15 | func init() {
16 | godotenv.Load()
17 | }
18 |
19 | func main() {
20 | runWithCache()
21 | }
22 |
23 | func runWithCache() {
24 | ctx := context.Background()
25 |
26 | llm.WithSingleConfig(map[string]any{
27 | "api_key": os.Getenv("qwen_api_key"),
28 | })
29 |
30 | instance := llm.NewInstance(qwen.Name,
31 | llm.WithCache(llm.NewMemoCache()),
32 | llm.WithLogger(&log.WrapLogger{}),
33 | )
34 |
35 | resp, err := instance.Invoke(ctx,
36 | []llm.Message{
37 | llm.NewUserTextMessage("你好"),
38 | llm.NewUserMultipartMessage(
39 | llm.MultipartContentImageURL("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"),
40 | llm.MultipartContentText("这是一张关于猫的照片吗"),
41 | ),
42 | },
43 | llm.WithModel("qwen-vl-max-latest"),
44 | llm.WithStream(true),
45 | )
46 | if err != nil {
47 | panic(err)
48 | }
49 | fmt.Println(resp.Result())
50 |
51 | resp, err = instance.Invoke(ctx,
52 | []llm.Message{llm.NewUserTextMessage("你好")},
53 | llm.WithModel("qwen-vl-max-latest"),
54 | llm.WithStream(true),
55 | )
56 | if err != nil {
57 | panic(err)
58 | }
59 | // stream := resp.Stream()
60 | // for v := stream.Next(); v != nil; v = stream.Next() {
61 | // fmt.Println(string(v.Delta.Message.Content().Data))
62 | // }
63 | fmt.Println(resp.Result())
64 | }
65 |
--------------------------------------------------------------------------------
/speech/tts/aliyun/handler.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/showntop/llmack/speech"
8 | )
9 |
10 | // SpeechWsSynthesisListener is the listener of
11 | type SpeechWsSynthesisListener interface {
12 | OnSynthesizeStarted(*speech.TTSResult)
13 | OnSentenceEnded(*speech.TTSResult)
14 | OnSynthesized(*speech.TTSResult)
15 | OnSentenceStarted(*speech.TTSResult)
16 | OnSynthesizeCompleted(*speech.TTSResult, error)
17 | OnSynthesizeFailed(*speech.TTSResult, error)
18 | OnAudioResult([]byte)
19 | }
20 |
21 | type listener struct {
22 | ff *os.File
23 | t *TTS
24 | }
25 |
26 | func newListener(t *TTS) *listener {
27 | f, _ := os.Create("tts.wav")
28 | return &listener{
29 | ff: f,
30 | t: t,
31 | }
32 | }
33 |
34 | func (l *listener) OnAudioResult(data []byte) {
35 | l.ff.Write(data)
36 | l.t.result <- data
37 | }
38 | func (l *listener) OnSentenceEnded(r *speech.TTSResult) {
39 | fmt.Printf("tts sentence ended, result:%+v\n", r)
40 | }
41 | func (l *listener) OnSentenceStarted(r *speech.TTSResult) {
42 | fmt.Printf("tts sentence started, result:%+v\n", r)
43 | }
44 | func (l *listener) OnSynthesizeCompleted(_ *speech.TTSResult, _ error) {
45 | fmt.Printf("tts synthesize completed\n")
46 | }
47 | func (l *listener) OnSynthesizeStarted(_ *speech.TTSResult) {
48 | fmt.Println("tts synthesize started")
49 | }
50 | func (l *listener) OnSynthesized(_ *speech.TTSResult) {
51 | fmt.Println("tts sentence synthesized 有新的合成结果返回。")
52 | }
53 | func (l *listener) OnSynthesizeFailed(_ *speech.TTSResult, _ error) {
54 | fmt.Println("tts OnSynthesizeFailed")
55 | }
56 |
--------------------------------------------------------------------------------
/embedding/onnx_embedder.go:
--------------------------------------------------------------------------------
1 | package embedding
2 |
3 | // import (
4 | // "fmt"
5 |
6 | // "github.com/owulveryck/onnx-go"
7 | // "github.com/owulveryck/onnx-go/backend/x/gorgonnx"
8 | // )
9 |
10 | // // OnnxEmbedder 实现基于ONNX的文本嵌入
11 | // type OnnxEmbedder struct {
12 | // model *onnx.Model
13 | // dim int
14 | // }
15 |
16 | // // NewOnnxEmbedder 创建新的ONNX嵌入器
17 | // func NewOnnxEmbedder(modelPath string, dimension int) (*OnnxEmbedder, error) {
18 | // backend := gorgonnx.NewGraph()
19 | // model := onnx.NewModel(backend)
20 |
21 | // // 加载模型
22 | // if err := model.Load(modelPath); err != nil {
23 | // return nil, fmt.Errorf("failed to load model: %v", err)
24 | // }
25 |
26 | // return &OnnxEmbedder{
27 | // model: model,
28 | // dim: dimension,
29 | // }, nil
30 | // }
31 |
32 | // // Embed 生成文本的向量表示
33 | // func (e *OnnxEmbedder) Embed(text string) ([]float64, error) {
34 | // // 这里需要根据具体的模型实现文本预处理和推理
35 | // // 示例实现:
36 | // input := preprocessText(text)
37 | // output, err := e.model.Predict(input)
38 | // if err != nil {
39 | // return nil, fmt.Errorf("prediction failed: %v", err)
40 | // }
41 |
42 | // return normalizeOutput(output), nil
43 | // }
44 |
45 | // // Dimension 返回嵌入向量的维度
46 | // func (e *OnnxEmbedder) Dimension() int {
47 | // return e.dim
48 | // }
49 |
50 | // // 文本预处理函数
51 | // func preprocessText(text string) []float32 {
52 | // // 实现文本预处理逻辑
53 | // // 例如:分词、编码等
54 | // return nil
55 | // }
56 |
57 | // // 输出归一化
58 | // func normalizeOutput(output interface{}) []float64 {
59 | // // 实现输出处理逻辑
60 | // // 将模型输出转换为标准化的float64切片
61 | // return nil
62 | // }
63 |
--------------------------------------------------------------------------------
/tool/mobile/controller/action.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | "github.com/getkin/kin-openapi/openapi3gen"
9 | "github.com/showntop/llmack/pkg/structx"
10 | "github.com/showntop/llmack/tool"
11 | )
12 |
13 | type ActionFunc[T, D any] func(ctx context.Context, input T) (output D, err error)
14 |
15 | type Action struct {
16 | Name string
17 | Tool *tool.Tool
18 | }
19 |
20 | func NewAction[T, D any](name string, description string, actionFunc ActionFunc[T, D]) (*Action, error) {
21 | fun := func(ctx context.Context, args string) (string, error) {
22 | var inst T
23 | inst = structx.NewInstance[T]()
24 |
25 | if err := json.Unmarshal([]byte(args), &inst); err != nil {
26 | return "", fmt.Errorf("failed to unmarshal arguments in json, %v", err)
27 | }
28 |
29 | resp, err := actionFunc(ctx, inst)
30 | if err != nil {
31 | return "", fmt.Errorf("failed to execute action, %v", err)
32 | }
33 |
34 | output, err := json.Marshal(resp)
35 | if err != nil {
36 | return "", fmt.Errorf("failed to marshal output in json, %v", err)
37 | }
38 |
39 | return string(output), nil
40 | }
41 |
42 | schemaCustomizer := tool.DefaultSchemaCustomizer
43 |
44 | sc, err := openapi3gen.NewSchemaRefForValue(structx.NewInstance[T](), nil, openapi3gen.SchemaCustomizer(schemaCustomizer))
45 | if err != nil {
46 | return nil, fmt.Errorf("new SchemaRef from T failed: %w", err)
47 | }
48 |
49 | tool := tool.New(
50 | tool.WithName(name),
51 | tool.WithDescription(description),
52 | tool.WithParameters(sc.Value),
53 | tool.WithFunction(fun),
54 | )
55 |
56 | return &Action{
57 | Name: name,
58 | Tool: tool,
59 | }, nil
60 | }
61 |
--------------------------------------------------------------------------------
/tool/think/think.go:
--------------------------------------------------------------------------------
1 | package think
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 |
7 | "github.com/showntop/llmack/tool"
8 | )
9 |
10 | const Think = "ThinkTool"
11 |
12 | func init() {
13 | t := tool.New(
14 | tool.WithName(Think),
15 | tool.WithKind("code"),
16 | tool.WithDescription("Intelligent problem-solving assistant that comprehends tasks, identifies key variables, and makes efficient decisions, all while providing detailed, self-driven reasoning for its choices. Do not assume anything, take the details from given data only."),
17 | tool.WithParameters(tool.Parameter{
18 | Name: "task",
19 | Type: tool.String,
20 | Required: true,
21 | LLMDescrition: "Task description which needs reasoning.",
22 | Default: "",
23 | }),
24 | tool.WithFunction(func(ctx context.Context, args string) (string, error) {
25 | var params struct {
26 | Task string `json:"task"`
27 | }
28 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
29 | return "", err
30 | }
31 | // TODO: Implement actual thinking logic
32 | return "任务分析:" + params.Task + "。基于现有信息进行推理分析...", nil
33 | }),
34 | )
35 | tool.Register(t)
36 | }
37 |
38 | var prompt = `
39 | Given the following overall objective
40 | Objective:
41 | {goals}
42 |
43 | and the following task, {{task_description}}.
44 |
45 | Below is last tool response:
46 | {{last_tool_response}}
47 |
48 | Below is the relevant tool response:
49 | {{relevant_tool_response}}
50 |
51 | Perform the task by understanding the problem, extracting variables, and being smart
52 | and efficient. Provide a descriptive response, make decisions yourself when
53 | confronted with choices and provide reasoning for ideas / decisions.
54 | `
55 |
--------------------------------------------------------------------------------
/workflow/node/tool.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | "github.com/showntop/llmack/tool"
9 | "github.com/showntop/llmack/workflow"
10 | wf "github.com/showntop/llmack/workflow"
11 | )
12 |
13 | type toolNode struct {
14 | executeable
15 | Identifier
16 | Node *wf.Node
17 | }
18 |
19 | // ToolNode initializes new function node with function definition and configured arguments and results
20 | func ToolNode(node *workflow.Node) (*toolNode, error) {
21 | // if def.Kind != wf.NodeKindFunction.Name() {
22 | // return nil, fmt.Errorf("expecting function kind")
23 | // }
24 |
25 | return &toolNode{Node: node}, nil
26 | }
27 |
28 | // Exec executes function node
29 | //
30 | // Configured arguments are evaluated with the node's scope and input and passed
31 | // to the function
32 | //
33 | // Configured results are evaluated with the results from the function and final scope is then returned
34 | func (n *toolNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) {
35 | toolName, _ := n.Node.Metadata["tool_name"].(string)
36 | stream, _ := n.Node.Metadata["stream"].(bool)
37 |
38 | // if providerKind == "" || toolName == "" {
39 | if toolName == "" {
40 | return nil, fmt.Errorf("provider_kind or tool_name is empty")
41 | }
42 |
43 | toolRun := tool.Spawn(toolName)
44 |
45 | inputsJSON, err := json.Marshal(r.Inputs)
46 | if err != nil {
47 | return nil, fmt.Errorf("failed to marshal inputs: %v", err)
48 | }
49 |
50 | if stream {
51 | result, err := toolRun.Invoke(ctx, string(inputsJSON))
52 | return map[string]any{"result": result}, err
53 | }
54 | result, err := toolRun.Invoke(ctx, string(inputsJSON))
55 | return map[string]any{"result": result}, err
56 | }
57 |
--------------------------------------------------------------------------------
/llm/mock.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | var MockLLMModelName = "mock"
10 |
11 | type MockLLMModel struct {
12 | }
13 |
14 | func (m *MockLLMModel) Invoke(ctx context.Context, messages []Message, opts *InvokeOptions) (*Response, error) {
15 | if opts.Stream {
16 | response := NewStreamResponse()
17 | go func() {
18 | response.stream.Push(NewChunk(0, NewAssistantMessage("我是"), &Usage{
19 | PromptTokens: 1,
20 | CompletionTokens: 1,
21 | }))
22 | response.stream.Push(NewChunk(0, NewAssistantMessage("mock的"), &Usage{
23 | PromptTokens: 1,
24 | CompletionTokens: 1,
25 | }))
26 | response.stream.Push(NewChunk(0, NewAssistantMessage("内容"), &Usage{
27 | PromptTokens: 1,
28 | CompletionTokens: 1,
29 | }))
30 | response.stream.Push(NewChunk(0, NewAssistantMessage("结束"), &Usage{
31 | PromptTokens: 1,
32 | CompletionTokens: 1,
33 | }))
34 | if len(opts.Tools) > 0 {
35 | toolCalls := []*ToolCall{}
36 | for _, tool := range opts.Tools {
37 | toolCalls = append(toolCalls, &ToolCall{
38 | ID: uuid.New().String(),
39 | Function: ToolCallFunction{
40 | Name: tool.Function.Name,
41 | },
42 | })
43 | }
44 | response.stream.Push(NewChunk(0, NewAssistantMessage("").WithToolCalls(toolCalls), &Usage{
45 | PromptTokens: 1,
46 | CompletionTokens: 1,
47 | }))
48 | }
49 |
50 | }()
51 | return response, nil
52 | } else {
53 | return &Response{}, nil
54 | }
55 | }
56 |
57 | func (m *MockLLMModel) Name() string {
58 | return MockLLMModelName
59 | }
60 |
61 | func init() {
62 | Register(MockLLMModelName, func(o *ProviderOptions) Provider { return &MockLLMModel{} })
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/structx/generic.go:
--------------------------------------------------------------------------------
1 | package structx
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | // NewInstance create an instance of the given type T.
8 | // the main purpose of this function is to create an instance of a type, can handle the type of T is a pointer or not.
9 | // eg. NewInstance[int] returns 0.
10 | // eg. NewInstance[*int] returns *0 (will be ptr of 0, not nil!).
11 | func NewInstance[T any]() T {
12 | typ := TypeOf[T]()
13 |
14 | switch typ.Kind() {
15 | case reflect.Map:
16 | return reflect.MakeMap(typ).Interface().(T)
17 | case reflect.Slice, reflect.Array:
18 | return reflect.MakeSlice(typ, 0, 0).Interface().(T)
19 | case reflect.Ptr:
20 | typ = typ.Elem()
21 | origin := reflect.New(typ)
22 | inst := origin
23 |
24 | for typ.Kind() == reflect.Ptr {
25 | typ = typ.Elem()
26 | inst = inst.Elem()
27 | inst.Set(reflect.New(typ))
28 | }
29 |
30 | return origin.Interface().(T)
31 | default:
32 | var t T
33 | return t
34 | }
35 | }
36 |
37 | // TypeOf returns the type of T.
38 | // eg. TypeOf[int] returns reflect.TypeOf(int).
39 | // eg. TypeOf[*int] returns reflect.TypeOf(*int).
40 | func TypeOf[T any]() reflect.Type {
41 | return reflect.TypeOf((*T)(nil)).Elem()
42 | }
43 |
44 | // PtrOf returns a pointer of T.
45 | // useful when you want to get a pointer of a value, in some config, for example.
46 | // eg. PtrOf[int] returns *int.
47 | // eg. PtrOf[*int] returns **int.
48 | func PtrOf[T any](v T) *T {
49 | return &v
50 | }
51 |
52 | type Pair[F, S any] struct {
53 | First F
54 | Second S
55 | }
56 |
57 | // Reverse returns a new slice with elements in reversed order.
58 | func Reverse[S ~[]E, E any](s S) S {
59 | d := make(S, len(s))
60 | for i := 0; i < len(s); i++ {
61 | d[i] = s[len(s)-i-1]
62 | }
63 |
64 | return d
65 | }
66 |
--------------------------------------------------------------------------------
/vision/siliconflow/vision.go:
--------------------------------------------------------------------------------
1 | package siliconflow
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "io"
8 | "net/http"
9 |
10 | "github.com/showntop/llmack/vision"
11 | )
12 |
13 | // Name ...
14 | var Name = "siliconflow"
15 |
16 | func init() {
17 | vision.Register(Name, &Vision{})
18 | }
19 |
20 | type Vision struct {
21 | }
22 |
23 | func (v *Vision) GenerateImage(ctx context.Context, prompt string, optFuncs ...vision.InvokeOption) (string, error) {
24 | options := vision.InvokeOptions{}
25 | for _, optFunc := range optFuncs {
26 | optFunc(&options)
27 | }
28 |
29 | url := "https://api.siliconflow.cn/v1/images/generations"
30 |
31 | params := map[string]any{
32 | "prompt": prompt,
33 | "model": "black-forest-labs/FLUX.1-schnell",
34 | "seed": 4999999999,
35 | }
36 | payload, _ := json.Marshal(params)
37 | req, err := http.NewRequest("POST", url, bytes.NewReader(payload))
38 | if err != nil {
39 | return "null", err
40 | }
41 | req.Header.Add("Authorization", "Bearer "+options.ApiKey)
42 | req.Header.Add("Content-Type", "application/json")
43 |
44 | resp, err := http.DefaultClient.Do(req)
45 | if err != nil {
46 | return "null", err
47 | }
48 | defer resp.Body.Close()
49 | body, err := io.ReadAll(resp.Body)
50 | if err != nil {
51 | return "null", err
52 | }
53 | var result Result
54 | json.Unmarshal(body, &result)
55 |
56 | return result.Images[0].Url, nil
57 | }
58 |
59 | type Result struct {
60 | Images []struct {
61 | Url string `json:"url"`
62 | } `json:"images"`
63 | Timings struct {
64 | Inference float64 `json:"inference"`
65 | } `json:"timings"`
66 | Seed int `json:"seed"`
67 | SharedId string `json:"shared_id"`
68 | Data []struct {
69 | Url string `json:"url"`
70 | } `json:"data"`
71 | Created int `json:"created"`
72 | }
73 |
--------------------------------------------------------------------------------
/vdb/vdb.go:
--------------------------------------------------------------------------------
1 | package vdb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/pgvector/pgvector-go"
9 | )
10 |
11 | var constructors = map[string]func(config any) (VDB, error){}
12 |
13 | // NewVDB ...
14 | func NewVDB(name string, config any) (VDB, error) {
15 | constructor, ok := constructors[name]
16 | if !ok {
17 | return nil, fmt.Errorf("vdb %s not found", name)
18 | }
19 | vdb, err := constructor(config)
20 | if err != nil {
21 | return nil, fmt.Errorf("new vdb %s failed: %w", name, err)
22 | }
23 | return vdb, nil
24 | }
25 |
26 | // Register ...
27 | func Register(name string, constructor func(config any) (VDB, error)) {
28 | constructors[name] = constructor
29 | }
30 |
31 | // VDB ...
32 | type VDB interface {
33 | Create(context.Context) error
34 | Store(context.Context, ...*Document) error
35 | // BatchStore(context.Context, []string, [][]float64) error
36 |
37 | SearchWithOptions(context.Context, []float32, *SearchOptions) ([]*Document, error)
38 | Search(context.Context, []float32, ...SearchOption) ([]*Document, error)
39 | SearchQuery(context.Context, string, ...SearchOption) ([]*Document, error)
40 | SearchQueryWithOptions(context.Context, string, *SearchOptions) ([]*Document, error)
41 |
42 | Delete(context.Context, string) error
43 | Close() error
44 | }
45 |
46 | // Document 文档
47 | type Document struct {
48 | ID string `json:"id"`
49 | Title string `json:"title"`
50 | Content string `json:"content"`
51 | ContentHash string `json:"content_hash"`
52 | Embedding pgvector.Vector `json:"embedding"`
53 | Similarity float32 `json:"similarity"`
54 | Metadata map[string]any `json:"metadata"`
55 | CreatedAt time.Time `json:"created_at"`
56 | UpdatedAt time.Time `json:"updated_at"`
57 | }
58 |
--------------------------------------------------------------------------------
/vision/hook.go:
--------------------------------------------------------------------------------
1 | package vision
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "database/sql/driver"
7 | "io"
8 |
9 | "go.opentelemetry.io/otel"
10 | "go.opentelemetry.io/otel/attribute"
11 | "go.opentelemetry.io/otel/codes"
12 | "go.opentelemetry.io/otel/trace"
13 | )
14 |
15 | // Hook ...
16 | type Hook interface {
17 | OnBeforeInvoke(context.Context) context.Context
18 | OnAfterInvoke(ctx context.Context, err error)
19 | OnFirstChunk(context.Context, error) context.Context
20 | }
21 |
22 | // OtelHook ...
23 | type OtelHook struct {
24 | provider trace.TracerProvider
25 | tracer trace.Tracer
26 | }
27 |
28 | // HookOption ...
29 | type HookOption func(*OtelHook)
30 |
31 | // NewOtelHook ...
32 | func NewOtelHook(opts ...HookOption) Hook {
33 | h := &OtelHook{}
34 | h.provider = otel.GetTracerProvider()
35 | h.tracer = h.provider.Tracer("github.com/showntop/llmack/opentelemetry")
36 |
37 | return h
38 | }
39 |
40 | // OnFirstChunk ...
41 | func (h *OtelHook) OnFirstChunk(ctx context.Context, _ error) context.Context {
42 | return ctx
43 | }
44 |
45 | // OnBeforeInvoke ...
46 | func (h *OtelHook) OnBeforeInvoke(ctx context.Context) context.Context {
47 | ctx, _ = h.tracer.Start(ctx, "llm/invoke", trace.WithSpanKind(trace.SpanKindInternal))
48 | return ctx
49 | }
50 |
51 | // OnAfterInvoke ...
52 | func (h *OtelHook) OnAfterInvoke(ctx context.Context, err error) {
53 | span := trace.SpanFromContext(ctx)
54 | if !span.IsRecording() {
55 | return
56 | }
57 | defer span.End()
58 |
59 | attrs := make([]attribute.KeyValue, 0, 4)
60 |
61 | span.SetAttributes(attrs...)
62 | switch err {
63 | case nil,
64 | driver.ErrSkip,
65 | io.EOF, // end of rows iterator
66 | sql.ErrNoRows:
67 | // ignore
68 | default:
69 | span.RecordError(err)
70 | span.SetStatus(codes.Error, err.Error())
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/storage/postgres_store.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "gorm.io/driver/postgres"
8 | "gorm.io/gorm"
9 | )
10 |
11 | type PostgresStorage struct {
12 | db *gorm.DB
13 | }
14 |
15 | func SetupPostgresStorage(dns string) error {
16 | db, err := gorm.Open(postgres.Open(dns), &gorm.Config{})
17 | if err != nil {
18 | return err
19 | }
20 | db.AutoMigrate(&Session{})
21 | return nil
22 | }
23 |
24 | func NewPostgresStorage(db *sql.DB) Storage {
25 | gormDB, err := gorm.Open(postgres.New(postgres.Config{Conn: db}), &gorm.Config{})
26 | if err != nil {
27 | panic(err)
28 | }
29 | return &PostgresStorage{
30 | db: gormDB,
31 | }
32 | }
33 |
34 | func NewPostgresStorageWithDNS(dns string) Storage {
35 | gormDB, err := gorm.Open(postgres.Open(dns), &gorm.Config{})
36 | if err != nil {
37 | panic(err)
38 | }
39 | return &PostgresStorage{
40 | db: gormDB,
41 | }
42 | }
43 |
44 | func NewPostgresStorageWithGormDB(gormDB *gorm.DB) Storage {
45 | return &PostgresStorage{
46 | db: gormDB,
47 | }
48 | }
49 |
50 | func (s *PostgresStorage) AddNewJourney(ctx context.Context, journey *Journey) error {
51 | return s.db.Create(journey).Error
52 | }
53 |
54 | func (s *PostgresStorage) SaveSession(ctx context.Context, session *Session) error {
55 | return s.db.Create(session).Error
56 | }
57 |
58 | func (s *PostgresStorage) FetchSession(ctx context.Context, id string) (*Session, error) {
59 | var session Session
60 | if err := s.db.Where("id = ?", id).First(&session).Error; err != nil {
61 | return nil, err
62 | }
63 | return &session, nil
64 | }
65 |
66 | func (s *PostgresStorage) UpdateSession(ctx context.Context, session *Session) error {
67 | return s.db.Save(session).Error
68 | }
69 |
70 | func (s *PostgresStorage) DeleteSession(ctx context.Context, id string) error {
71 | return s.db.Delete(&Session{}, "id = ?", id).Error
72 | }
73 |
--------------------------------------------------------------------------------
/tool/browser/controller/utils.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "reflect"
7 |
8 | "github.com/invopop/jsonschema"
9 | "github.com/xeipuuv/gojsonschema"
10 | )
11 |
12 | func removeRefRecursive(v interface{}) interface{} {
13 | switch vv := v.(type) {
14 | case map[string]interface{}:
15 | delete(vv, "$ref")
16 | for k, val := range vv {
17 | vv[k] = removeRefRecursive(val)
18 | }
19 | return vv
20 | case []interface{}:
21 | for i, val := range vv {
22 | vv[i] = removeRefRecursive(val)
23 | }
24 | return vv
25 | default:
26 | return v
27 | }
28 | }
29 |
30 | func GenerateSchema(typeDefinition interface{}) string {
31 | var modelName string
32 | if reflect.TypeOf(typeDefinition).Kind() == reflect.Ptr {
33 | modelName = reflect.TypeOf(typeDefinition).Elem().Name()
34 | } else {
35 | modelName = reflect.TypeOf(typeDefinition).Name()
36 | }
37 | s := jsonschema.Reflect(typeDefinition)
38 | s.Definitions[modelName].Title = modelName
39 | b, err := json.Marshal(s.Definitions[modelName])
40 | if err != nil {
41 | panic(err)
42 | }
43 | var m map[string]interface{}
44 | if err := json.Unmarshal(b, &m); err != nil {
45 | panic(err)
46 | }
47 | // remove all $ref
48 | m = removeRefRecursive(m).(map[string]interface{})
49 | b2, err := json.MarshalIndent(m, "", " ")
50 | if err != nil {
51 | panic(err)
52 | }
53 | return string(b2)
54 | }
55 |
56 | func ValidateSchema(schemaString string, data map[string]interface{}) error {
57 | schemaLoader := gojsonschema.NewStringLoader(schemaString)
58 | schema, err := gojsonschema.NewSchema(schemaLoader)
59 | if err != nil {
60 | return err
61 | }
62 | validResult, err := schema.Validate(gojsonschema.NewGoLoader(data))
63 | if err != nil {
64 | return err
65 | }
66 | if validResult.Valid() {
67 | return nil
68 | }
69 | return errors.New("invalid schema")
70 | }
71 |
--------------------------------------------------------------------------------
/speech/tts.go:
--------------------------------------------------------------------------------
1 | package speech
2 |
3 | import "context"
4 |
5 | // StreamTTS ...
6 | type StreamTTS interface {
7 | Prepare() error
8 | Input(string) error
9 | Complete() error
10 | Terminate() error
11 | StreamResult() chan []byte
12 | }
13 |
14 | // TextTTS is the struct for text to speech
15 | type TextTTS interface {
16 | Synthesize(ctx context.Context, text string) (*TextTTSResult, error)
17 | }
18 |
19 | // RealtimeTTS is the struct for text to speech
20 | type RealtimeTTS interface {
21 | Synthesize(ctx context.Context, text string) (*TTSResult, error)
22 | }
23 |
24 | // TTSResult is the basic struct for the TTS response.
25 | type TTSResult struct {
26 | SessionID string `json:"session_id"` //音频流唯一 id,由客户端在握手阶段生成并赋值在调用参数中
27 | // RequestId string `json:"request_id"` //音频流唯一 id,由服务端在握手阶段自动生成
28 | MessageID string `json:"message_id"` //本 message 唯一 id
29 | Data chan []byte `json:"data"` //最新语音合成文本结果/音频流数据
30 | Audios []byte
31 | Subtitles []Subtitle `json:"subtitles"`
32 | }
33 |
34 | func NewTTSResult() *TTSResult {
35 | ttr := &TTSResult{Data: make(chan []byte, 100)}
36 | return ttr
37 | }
38 |
39 | type TextTTSResult struct {
40 | Audio string
41 | Subtitles []Subtitle
42 | }
43 |
44 | type Subtitle struct {
45 | // ⽂本信息。
46 | Text string `json:"text,omitnil,omitempty" name:"text"`
47 |
48 | // ⽂本对应tts语⾳开始时间戳,单位ms。
49 | BeginTime int64 `json:"BeginTime,omitnil,omitempty" name:"BeginTime"`
50 |
51 | // ⽂本对应tts语⾳结束时间戳,单位ms。
52 | EndTime int64 `json:"EndTime,omitnil,omitempty" name:"EndTime"`
53 |
54 | // 该文本在时间戳数组中的开始位置,从0开始。
55 | BeginIndex int64 `json:"BeginIndex,omitnil,omitempty" name:"BeginIndex"`
56 |
57 | // 该文本在时间戳数组中的结束位置,从0开始。
58 | EndIndex int64 `json:"EndIndex,omitnil,omitempty" name:"EndIndex"`
59 |
60 | // 该字的音素。
61 | // 注意:此字段可能返回 null,表示取不到有效值。
62 | Phoneme string `json:"Phoneme,omitnil,omitempty" name:"Phoneme"`
63 | }
64 |
--------------------------------------------------------------------------------
/llm/ollama/llm.go:
--------------------------------------------------------------------------------
1 | package ollama
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "net/http"
7 | "net/url"
8 |
9 | "github.com/ollama/ollama/api"
10 | "github.com/showntop/llmack/llm"
11 | )
12 |
13 | const (
14 | // Name name of llm
15 | Name = "ollama"
16 | )
17 |
18 | func init() {
19 | llm.Register(Name, func(o *llm.ProviderOptions) llm.Provider { return &LLM{} })
20 | }
21 |
22 | // LLM ...
23 | type LLM struct {
24 | model string
25 | client *api.Client
26 | name string
27 | }
28 |
29 | // Invoke ...
30 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) {
31 | if len(messages) == 0 {
32 | return nil, errors.New("empty messages")
33 | }
34 | if err := m.setupClient(); err != nil {
35 | return nil, err
36 | }
37 |
38 | response := llm.NewStreamResponse()
39 | req := &api.ChatRequest{
40 | Model: opts.Model,
41 | Stream: &opts.Stream,
42 | Tools: nil,
43 | }
44 | for _, m := range messages {
45 | req.Messages = append(req.Messages, api.Message{
46 | Role: string(m.Role()),
47 | Content: m.Content(),
48 | })
49 | }
50 | // TODO tools support
51 |
52 | idx := 1
53 | err := m.client.Chat(ctx, req, func(resp api.ChatResponse) error {
54 | response.Stream().Push(llm.NewChunk(idx, llm.NewAssistantMessage(resp.Message.Content), nil))
55 | if resp.Done {
56 | response.Stream().Close()
57 | return nil
58 | }
59 | idx++
60 | return nil
61 | })
62 |
63 | if err != nil {
64 | return nil, err
65 | }
66 | return response, nil
67 | }
68 |
69 | func (m *LLM) setupClient() error {
70 | config, _ := llm.Config.Get(Name).(map[string]any)
71 | if config == nil {
72 | return errors.New("ollama config not found")
73 | }
74 | urlx, err := url.Parse(config["base_url"].(string))
75 | if err != nil {
76 | return err
77 | }
78 | client := api.NewClient(
79 | urlx,
80 | http.DefaultClient,
81 | )
82 | m.client = client
83 | return nil
84 | }
85 |
--------------------------------------------------------------------------------
/example/rag/pgvector/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/joho/godotenv"
11 | "github.com/showntop/llmack/embedding"
12 | "github.com/showntop/llmack/vdb"
13 | "github.com/showntop/llmack/vdb/pgvector"
14 | )
15 |
16 | func main() {
17 | godotenv.Load()
18 |
19 | vvv, err := pgvector.New(&pgvector.Config{
20 | DNS: os.Getenv("pgvector_dns"),
21 | Embedder: embedding.NewStringEmbedder(),
22 | Table: "knowledges",
23 | })
24 | if err != nil {
25 | panic(err)
26 | }
27 |
28 | // err = vvv.Create(context.Background())
29 | // if err != nil {
30 | // panic(err)
31 | // }
32 |
33 | // err = vvv.Store(context.Background(), mockDocuments()...)
34 | // if err != nil {
35 | // panic(err)
36 | // }
37 |
38 | docs, err := vvv.SearchQuery(context.Background(), "埃菲尔铁塔", vdb.WithTopk(2))
39 | if err != nil {
40 | panic(err)
41 | }
42 | for _, doc := range docs {
43 | fmt.Println(doc.ID, doc.Title, doc.Content, doc.Embedding)
44 | }
45 | }
46 |
47 | func mockDocuments() []*vdb.Document {
48 | contents := `
49 | 1. 埃菲尔铁塔:位于法国巴黎,是世界上最著名的地标之一,由古斯塔夫·埃菲尔设计,建于1889年。
50 | 2. 长城:位于中国,是世界七大奇迹之一,始建于秦朝至明朝,全长超过20000公里。
51 | 3. 大峡谷国家公园:位于美国亚利桑那州,以其深深的峡谷和壮丽的景色而闻名,它被科罗拉多河切割而成。
52 | 4. 罗马斗兽场:位于意大利罗马,建于公元70-80年间,是古罗马帝国最大的圆形竞技场。
53 | 5. 泰姬陵:位于印度阿格拉,由莫卧儿皇帝沙贾汗于1653年为纪念他的妻子而建成,是世界新七大奇迹之一。
54 | 6. 悉尼歌剧院:位于澳大利亚悉尼港,是20世纪最具标志性的建筑之一,以其独特的帆船设计而闻名。
55 | 7. 卢浮宫博物馆:位于法国巴黎,是世界上最大的博物馆之一,收藏丰富,包括达·芬奇的《蒙娜丽莎》和希腊的《米洛的维纳斯》。
56 | 8. 尼亚加拉大瀑布:位于美国和加拿大边境,由三个主要瀑布组成,其壮观的景色每年吸引数百万游客。
57 | 9. 圣索菲亚大教堂:位于土耳其伊斯坦布尔,最初建于公元537年,曾是东正教大教堂和清真寺,现为博物馆。
58 | 10. 马丘比丘:位于秘鲁安第斯山脉高原上的古印加遗址,世界新七大奇迹之一,海拔2400多米。
59 | `
60 |
61 | var docs []*vdb.Document
62 | for idx, str := range strings.Split(contents, "\n") {
63 | if str == "" {
64 | continue
65 | }
66 | docs = append(docs, &vdb.Document{
67 | ID: strconv.FormatInt(int64(idx+1), 10) + "X",
68 | Content: str,
69 | })
70 | }
71 | return docs
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/browser/dom/utils_test.go:
--------------------------------------------------------------------------------
1 | package dom
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestConvertSimpleXpathToCssSelector(t *testing.T) {
8 | // Test empty xpath returns empty string
9 | if got := ConvertSimpleXpathToCssSelector(""); got != "" {
10 | t.Errorf("Expected empty string, got %q", got)
11 | }
12 |
13 | // Test a simple xpath without indices
14 | xpath := "/html/body/div/span"
15 | expected := "html > body > div > span"
16 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected {
17 | t.Errorf("Expected %q, got %q", expected, got)
18 | }
19 |
20 | // Test xpath with an index on one element: [2] should translate to :nth-of-type(2)
21 | xpath = "/html/body/div[2]/span"
22 | expected = "html > body > div:nth-of-type(2) > span"
23 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected {
24 | t.Errorf("Expected %q, got %q", expected, got)
25 | }
26 |
27 | // Test xpath with indices on multiple elements
28 | xpath = "/ul/li[3]/a[1]"
29 | expected = "ul > li:nth-of-type(3) > a:nth-of-type(1)"
30 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected {
31 | t.Errorf("Expected %q, got %q", expected, got)
32 | }
33 | }
34 |
35 | func TestEnhancedCssSelectorForElement(t *testing.T) {
36 | dummyElement := &DOMElementNode{
37 | TagName: "div",
38 | IsVisible: true,
39 | Parent: nil,
40 | Xpath: "/html/body/div[2]",
41 | Attributes: map[string]string{
42 | "class": "foo bar",
43 | "id": "my-id",
44 | "placeholder": `some "quoted" text`,
45 | "data-testid": "123",
46 | },
47 | Children: []DOMBaseNode{},
48 | }
49 |
50 | actualSelector := EnhancedCssSelectorForElement(dummyElement, true)
51 | expectedSelector := `html > body > div:nth-of-type(2).foo.bar[data-testid="123"][id="my-id"][placeholder*="some \"quoted\" text"]`
52 |
53 | if actualSelector != expectedSelector {
54 | t.Errorf("Expected %s, but got %s", expectedSelector, actualSelector)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tool/browser/dom/utils_test.go:
--------------------------------------------------------------------------------
1 | package dom
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestConvertSimpleXpathToCssSelector(t *testing.T) {
8 | // Test empty xpath returns empty string
9 | if got := ConvertSimpleXpathToCssSelector(""); got != "" {
10 | t.Errorf("Expected empty string, got %q", got)
11 | }
12 |
13 | // Test a simple xpath without indices
14 | xpath := "/html/body/div/span"
15 | expected := "html > body > div > span"
16 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected {
17 | t.Errorf("Expected %q, got %q", expected, got)
18 | }
19 |
20 | // Test xpath with an index on one element: [2] should translate to :nth-of-type(2)
21 | xpath = "/html/body/div[2]/span"
22 | expected = "html > body > div:nth-of-type(2) > span"
23 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected {
24 | t.Errorf("Expected %q, got %q", expected, got)
25 | }
26 |
27 | // Test xpath with indices on multiple elements
28 | xpath = "/ul/li[3]/a[1]"
29 | expected = "ul > li:nth-of-type(3) > a:nth-of-type(1)"
30 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected {
31 | t.Errorf("Expected %q, got %q", expected, got)
32 | }
33 | }
34 |
35 | func TestEnhancedCssSelectorForElement(t *testing.T) {
36 | dummyElement := &DOMElementNode{
37 | TagName: "div",
38 | IsVisible: true,
39 | Parent: nil,
40 | Xpath: "/html/body/div[2]",
41 | Attributes: map[string]string{
42 | "class": "foo bar",
43 | "id": "my-id",
44 | "placeholder": `some "quoted" text`,
45 | "data-testid": "123",
46 | },
47 | Children: []DOMBaseNode{},
48 | }
49 |
50 | actualSelector := EnhancedCssSelectorForElement(dummyElement, true)
51 | expectedSelector := `html > body > div:nth-of-type(2).foo.bar[data-testid="123"][id="my-id"][placeholder*="some \"quoted\" text"]`
52 |
53 | if actualSelector != expectedSelector {
54 | t.Errorf("Expected %s, but got %s", expectedSelector, actualSelector)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/example/storage/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 |
10 | "github.com/showntop/llmack/agent"
11 | "github.com/showntop/llmack/llm"
12 | "github.com/showntop/llmack/llm/deepseek"
13 | "github.com/showntop/llmack/log"
14 | "github.com/showntop/llmack/storage"
15 | )
16 |
17 | var model = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat"))
18 |
19 | func init() {
20 | godotenv.Load()
21 |
22 | log.SetLogger(&log.WrapLogger{})
23 | llm.WithSingleConfig(map[string]any{
24 | "api_key": os.Getenv("deepseek_api_key"),
25 | })
26 |
27 | if err := storage.SetupPostgresStorage(os.Getenv("postgres_dns")); err != nil {
28 | panic(err)
29 | }
30 | }
31 |
32 | func main() {
33 |
34 | stockSearcher := agent.NewAgent(
35 | "Stock Searcher",
36 | agent.WithModel(model),
37 | agent.WithRole("Searches the web for information on a stock."),
38 | agent.WithStorage(storage.NewPostgresStorageWithDNS(os.Getenv("postgres_dns"))),
39 | )
40 |
41 | webSearcher := agent.NewAgent(
42 | "Web Searcher",
43 | agent.WithModel(model),
44 | agent.WithRole("Searches the web for information on a company."),
45 | agent.WithStorage(storage.NewJSONStorage("tmp/web_searcher")),
46 | )
47 |
48 | team := agent.NewTeam(
49 | agent.TeamModeCoordinate,
50 | agent.WithName("Stock Team"),
51 | agent.WithModel(model),
52 | agent.WithInstructions(
53 | "You can search the stock market for information about a particular company's stock.",
54 | "You can also search the web for wider company information.",
55 | ),
56 | agent.WithMembers(stockSearcher, webSearcher),
57 | agent.WithStorage(storage.NewJSONStorage("tmp/stock_team")),
58 | )
59 | response := team.Invoke(context.Background(), "What is the stock price of Apple?")
60 | if response.Error != nil {
61 | panic(response.Error)
62 | }
63 | for chunk := range response.Stream {
64 | fmt.Print(chunk.Choices[0].Delta.Content())
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/example/rag/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/redis/go-redis/v9"
10 | "github.com/showntop/llmack/rag"
11 | "github.com/showntop/llmack/vdb"
12 | vredis "github.com/showntop/llmack/vdb/redis"
13 | )
14 |
15 | func main() {
16 |
17 | ctx := context.Background()
18 |
19 | config := &vredis.Config{}
20 | config.Addr = "127.0.0.1:6379"
21 | config.Password = "cdgxxx2025@tx"
22 | config.DB = 0
23 | config.Index = "vdb"
24 | // config = 1536
25 | config.FieldSchema = []*redis.FieldSchema{
26 | {},
27 | }
28 |
29 | indexer, err := rag.NewIndexer(vredis.Name, config)
30 | if err != nil {
31 | panic(err)
32 | }
33 | indexer.Index(ctx, mockDocuments(), &rag.SearchOptions{
34 | LibraryID: 1,
35 | Kind: "text",
36 | IndexID: 1,
37 | TopK: 10,
38 | ScoreThreshold: 0.5,
39 | })
40 |
41 | entities, err := indexer.Retrieve(ctx, "你好", rag.WithTopK(10), rag.WithScoreThreshold(0.5))
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | fmt.Println(entities)
47 | }
48 |
49 | func mockDocuments() []*vdb.Document {
50 | contents := `
51 | 1. 埃菲尔铁塔:位于法国巴黎,是世界上最著名的地标之一,由古斯塔夫·埃菲尔设计,建于1889年。
52 | 2. 长城:位于中国,是世界七大奇迹之一,始建于秦朝至明朝,全长超过20000公里。
53 | 3. 大峡谷国家公园:位于美国亚利桑那州,以其深深的峡谷和壮丽的景色而闻名,它被科罗拉多河切割而成。
54 | 4. 罗马斗兽场:位于意大利罗马,建于公元70-80年间,是古罗马帝国最大的圆形竞技场。
55 | 5. 泰姬陵:位于印度阿格拉,由莫卧儿皇帝沙贾汗于1653年为纪念他的妻子而建成,是世界新七大奇迹之一。
56 | 6. 悉尼歌剧院:位于澳大利亚悉尼港,是20世纪最具标志性的建筑之一,以其独特的帆船设计而闻名。
57 | 7. 卢浮宫博物馆:位于法国巴黎,是世界上最大的博物馆之一,收藏丰富,包括达·芬奇的《蒙娜丽莎》和希腊的《米洛的维纳斯》。
58 | 8. 尼亚加拉大瀑布:位于美国和加拿大边境,由三个主要瀑布组成,其壮观的景色每年吸引数百万游客。
59 | 9. 圣索菲亚大教堂:位于土耳其伊斯坦布尔,最初建于公元537年,曾是东正教大教堂和清真寺,现为博物馆。
60 | 10. 马丘比丘:位于秘鲁安第斯山脉高原上的古印加遗址,世界新七大奇迹之一,海拔2400多米。
61 | `
62 |
63 | var docs []*vdb.Document
64 | for idx, str := range strings.Split(contents, "\n") {
65 | if str == "" {
66 | continue
67 | }
68 | docs = append(docs, &vdb.Document{
69 | ID: strconv.FormatInt(int64(idx+1), 10),
70 | Content: str,
71 | })
72 | }
73 | return docs
74 | }
75 |
--------------------------------------------------------------------------------
/prompt/format.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | )
7 |
8 | var regex = regexp.MustCompile(`\{\{([a-zA-Z_][a-zA-Z0-9_]{0,29}|#histories#|#query#|#context#)\}\}`)
9 | var withVariableTmplRegex = regexp.MustCompile(`\{\{([a-zA-Z_][a-zA-Z0-9_]{0,29}|#[a-zA-Z0-9_]{1,50}\.[a-zA-Z0-9_\.]{1,100}#|#histories#|#query#|#context#)\}\}`)
10 |
11 | // TemplateFormatter ...
12 | type TemplateFormatter struct {
13 | template string
14 | withVariableTmpl bool
15 | regex *regexp.Regexp
16 | variableKeys []string
17 | }
18 |
19 | // NewTemplateFormatter ...
20 | func NewTemplateFormatter(template string, withVariableTmpl bool) *TemplateFormatter {
21 | regex := regex
22 | if withVariableTmpl {
23 | regex = withVariableTmplRegex
24 | }
25 | return &TemplateFormatter{
26 | template: template,
27 | withVariableTmpl: withVariableTmpl,
28 | regex: regex,
29 | variableKeys: extract(template, regex),
30 | }
31 | }
32 |
33 | func extract(template string, regex *regexp.Regexp) []string {
34 | return regex.FindAllString(template, -1)
35 | }
36 |
37 | // Format ...
38 | func (p *TemplateFormatter) Format(inputs map[string]any, removeVars bool) string {
39 | replacer := func(match string) string {
40 | key := regex.FindStringSubmatch(match)[1]
41 | value, ok := inputs[key]
42 | if !ok {
43 | value = match
44 | } else {
45 | value = fmt.Sprintf("%+v", inputs[key])
46 | }
47 | valuex, _ := value.(string)
48 |
49 | if removeVars {
50 | return removeTemplateVariables(valuex, p.withVariableTmpl)
51 | }
52 | return valuex
53 | }
54 | prompt := regex.ReplaceAllStringFunc(p.template, replacer)
55 | prompt = regexp.MustCompile(`<\|.*?\|>`).ReplaceAllString(prompt, "")
56 | return prompt
57 | }
58 |
59 | func removeTemplateVariables(text string, withVariableTmpl bool) string {
60 | regex := regex
61 | if withVariableTmpl {
62 | regex = withVariableTmplRegex
63 | }
64 | return regex.ReplaceAllString(text, "{$1}")
65 | }
66 |
--------------------------------------------------------------------------------
/example/agent/finance/finance.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 | "github.com/showntop/llmack/agent"
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/llm/deepseek"
12 | "github.com/showntop/llmack/log"
13 | )
14 |
15 | var (
16 | model = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat"))
17 | )
18 |
19 | func init() {
20 | godotenv.Load()
21 |
22 | log.SetLogger(&log.WrapLogger{})
23 | llm.WithSingleConfig(map[string]any{
24 | "api_key": os.Getenv("deepseek_api_key"),
25 | })
26 | }
27 |
28 | func main() {
29 | ctx := context.Background()
30 |
31 | agent := agent.NewAgent(
32 | "finance",
33 | agent.WithModel(model),
34 | agent.WithInstructions(
35 | `
36 | You are a seasoned Wall Street analyst with deep expertise in market analysis! 📊
37 |
38 | Follow these steps for comprehensive financial analysis:
39 | 1. Market Overview
40 | - Latest stock price
41 | - 52-week high and low
42 | 2. Financial Deep Dive
43 | - Key metrics (P/E, Market Cap, EPS)
44 | 3. Professional Insights
45 | - Analyst recommendations breakdown
46 | - Recent rating changes
47 |
48 | 4. Market Context
49 | - Industry trends and positioning
50 | - Competitive analysis
51 | - Market sentiment indicators
52 |
53 | Your reporting style:
54 | - Begin with an executive summary
55 | - Use tables for data presentation
56 | - Include clear section headers
57 | - Add emoji indicators for trends (📈 📉)
58 | - Highlight key insights with bullet points
59 | - Compare metrics to industry averages
60 | - Include technical term explanations
61 | - End with a forward-looking analysis
62 |
63 | Risk Disclosure:
64 | - Always highlight potential risk factors
65 | - Note market uncertainties
66 | - Mention relevant regulatory concerns
67 | `),
68 | )
69 |
70 | response := agent.Invoke(ctx, "小米科技的最新舆情和财务表现如何?")
71 | if response.Error != nil {
72 | panic(response.Error)
73 | }
74 | fmt.Println(response.Completion())
75 | }
76 |
--------------------------------------------------------------------------------
/speech/encoding.go:
--------------------------------------------------------------------------------
1 | package speech
2 |
3 | // PcmToWav TODO
4 | /*
5 | *
6 | chunk:二进制字符串
7 | numchannel:1=单声道,2=多声道
8 | saplerate:采样率 8000/16000
9 | */
10 | func PcmToWav(chunk []byte, numchannel int, saplerate int) []byte {
11 | longSampleRate := saplerate
12 | byteRate := 16 * saplerate * numchannel / 8
13 | totalAudioLen := len(chunk)
14 | totalDataLen := totalAudioLen + 36
15 | var header = make([]byte, 44)
16 | // RIFF/WAVE header
17 | header[0] = 'R'
18 | header[1] = 'I'
19 | header[2] = 'F'
20 | header[3] = 'F'
21 | header[4] = byte(totalDataLen & 0xff)
22 | header[5] = byte((totalDataLen >> 8) & 0xff)
23 | header[6] = byte((totalDataLen >> 16) & 0xff)
24 | header[7] = byte((totalDataLen >> 24) & 0xff)
25 | // WAVE
26 | header[8] = 'W'
27 | header[9] = 'A'
28 | header[10] = 'V'
29 | header[11] = 'E'
30 | // 'fmt ' chunk
31 | header[12] = 'f'
32 | header[13] = 'm'
33 | header[14] = 't'
34 | header[15] = ' '
35 | // 4 bytes: size of 'fmt ' chunk
36 | header[16] = 16
37 | header[17] = 0
38 | header[18] = 0
39 | header[19] = 0
40 | // format = 1
41 | header[20] = 1
42 | header[21] = 0
43 | header[22] = byte(numchannel)
44 | header[23] = 0
45 | header[24] = byte(longSampleRate & 0xff)
46 | header[25] = byte((longSampleRate >> 8) & 0xff)
47 | header[26] = byte((longSampleRate >> 16) & 0xff)
48 | header[27] = byte((longSampleRate >> 24) & 0xff)
49 | header[28] = byte(byteRate & 0xff)
50 | header[29] = byte((byteRate >> 8) & 0xff)
51 | header[30] = byte((byteRate >> 16) & 0xff)
52 | header[31] = byte((byteRate >> 24) & 0xff)
53 | // block align
54 | header[32] = byte(2 * 16 / 8)
55 | header[33] = 0
56 | // bits per sample
57 | header[34] = 16
58 | header[35] = 0
59 | // data
60 | header[36] = 'd'
61 | header[37] = 'a'
62 | header[38] = 't'
63 | header[39] = 'a'
64 | header[40] = byte(totalAudioLen & 0xff)
65 | header[41] = byte((totalAudioLen >> 8) & 0xff)
66 | header[42] = byte((totalAudioLen >> 16) & 0xff)
67 | header[43] = byte((totalAudioLen >> 24) & 0xff)
68 |
69 | return append(header, chunk...)
70 | }
71 |
--------------------------------------------------------------------------------
/workflow/node/llm.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/showntop/llmack/llm"
8 | "github.com/showntop/llmack/prompt"
9 | "github.com/showntop/llmack/workflow"
10 | wf "github.com/showntop/llmack/workflow"
11 | )
12 |
13 | // llmNode TODO
14 | type llmNode struct {
15 | Node *wf.Node
16 |
17 | executeable
18 | Identifier
19 | }
20 |
21 | // LLMNode 创建LLMNode
22 | func LLMNode(n *workflow.Node) *llmNode {
23 | return &llmNode{
24 | Node: n,
25 | }
26 | }
27 |
28 | // Execute 执行LLM节点,单次执行
29 | func (n *llmNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) {
30 | // 解析metadata,获取模型配置
31 | stream, ok := n.Node.Metadata["stream"].(bool)
32 | if !ok {
33 | stream = true
34 | }
35 | provider, _ := n.Node.Metadata["provider"].(string)
36 | modelName, _ := n.Node.Metadata["model"].(string)
37 | systemPrompt, _ := n.Node.Metadata["system_prompt"].(string)
38 | userPrompt, _ := n.Node.Metadata["user_prompt"].(string)
39 | model := llm.NewInstance(provider, llm.WithDefaultModel(modelName))
40 |
41 | // 处理 inputs @TODO default from query field
42 | messages := []llm.Message{}
43 | if systemPrompt != "" {
44 | newSystemPrompt, err := prompt.Render(systemPrompt, r.Inputs)
45 | if err != nil {
46 | return nil, err
47 | }
48 | messages = append(messages, llm.NewSystemMessage(newSystemPrompt))
49 | }
50 | newUserPrompt, err := prompt.Render(userPrompt, r.Inputs)
51 | if err != nil {
52 | return nil, err
53 | }
54 | messages = append(messages, llm.NewUserTextMessage(newUserPrompt))
55 | response, err := model.Invoke(ctx, messages, llm.WithStream(true))
56 | if err != nil {
57 | return nil, err
58 | }
59 | if stream {
60 | return response, nil
61 | }
62 | result := response.Result()
63 | // result to map
64 | var mmm = map[string]any{
65 | "message": map[string]any{
66 | "content": strings.TrimLeft(strings.Trim(result.Message.Content(), "```"), "json"),
67 | },
68 | "model": result.Model,
69 | "usage": result.Usage,
70 | }
71 | return mmm, nil
72 | }
73 |
--------------------------------------------------------------------------------
/speech/asr/tencent/listener.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/showntop/tencentcloud-speech-sdk-go/asr"
8 | )
9 |
10 | // DefaultListener implementation of SpeechRecognitionListener
11 | type DefaultListener struct {
12 | t *ASR
13 | ID int
14 | }
15 |
16 | // OnRecognitionStart implementation of SpeechRecognitionListener
17 | func (listener *DefaultListener) OnRecognitionStart(response *asr.SpeechRecognitionResponse) {
18 | fmt.Printf("%s|%s|OnRecognitionStart\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID)
19 | }
20 |
21 | // OnSentenceBegin implementation of SpeechRecognitionListener
22 | func (listener *DefaultListener) OnSentenceBegin(response *asr.SpeechRecognitionResponse) {
23 | fmt.Printf("%s|%s|OnSentenceBegin: %v\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID, response)
24 | }
25 |
26 | // OnRecognitionResultChange implementation of SpeechRecognitionListener
27 | func (listener *DefaultListener) OnRecognitionResultChange(response *asr.SpeechRecognitionResponse) {
28 | // fmt.Printf("OnRecognitionResultChange|%s|: %+v\n", response.MessageID, response)
29 | fmt.Printf("%s|%s|OnRecognitionResultChange: %v\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID, response)
30 | }
31 |
32 | // OnSentenceEnd implementation of SpeechRecognitionListener
33 | func (listener *DefaultListener) OnSentenceEnd(response *asr.SpeechRecognitionResponse) {
34 | fmt.Printf("OnSentenceEnd|%s|: %+v\n", response.VoiceID, response)
35 | }
36 |
37 | // OnRecognitionComplete implementation of SpeechRecognitionListener
38 | func (listener *DefaultListener) OnRecognitionComplete(response *asr.SpeechRecognitionResponse) {
39 | fmt.Printf("%s|%s|OnRecognitionComplete\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID)
40 | }
41 |
42 | // OnFail implementation of SpeechRecognitionListener
43 | func (listener *DefaultListener) OnFail(response *asr.SpeechRecognitionResponse, err error) {
44 | fmt.Printf("OnFail:%s|%s| %v\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID, err)
45 | }
46 |
--------------------------------------------------------------------------------
/vision/vision.go:
--------------------------------------------------------------------------------
1 | package vision
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | )
7 |
8 | // Provider ...
9 | type Provider interface {
10 | GenerateImage(context.Context, string, ...InvokeOption) (string, error)
11 | }
12 |
13 | // Instance ...
14 | type Instance struct {
15 | name string
16 | provider Provider
17 | opts *Options
18 | }
19 |
20 | var providers = map[string]Provider{}
21 |
22 | // Register ...
23 | func Register(name string, provider Provider) {
24 | providers[name] = provider
25 | }
26 |
27 | // Options ...
28 | type Options struct {
29 | hooks []Hook
30 | model string
31 | }
32 |
33 | // Option ...
34 | type Option func(*Options)
35 |
36 | // WithHook ...
37 | // func WithHook(hooks ...Hook) Option {
38 | // return func(options *Options) {
39 | // options.hooks = append(options.hooks, hooks...)
40 | // }
41 | // }
42 |
43 | // NewInstance ...
44 | func NewInstance(provider string, opts ...Option) *Instance {
45 | var options Options = Options{}
46 | for _, o := range opts {
47 | o(&options)
48 | }
49 |
50 | return &Instance{
51 | name: provider,
52 | opts: &options,
53 | provider: providers[provider],
54 | }
55 | }
56 |
57 | // Invoke ...
58 | func (mi *Instance) GenerateImage(ctx context.Context,
59 | prompt string, options ...InvokeOption) (string, error) {
60 |
61 | if mi.provider == nil {
62 | return "null", fmt.Errorf("llm provider of %v is not registered", mi.name)
63 | }
64 | if mi.opts != nil && mi.opts.hooks != nil {
65 | for _, hook := range mi.opts.hooks {
66 | ctx = hook.OnBeforeInvoke(ctx)
67 | }
68 | }
69 |
70 | // 删除 options 中 nil
71 | for i := 0; i < len(options); i++ {
72 | if options[i] == nil {
73 | options = append(options[:i], options[i+1:]...)
74 | i--
75 | }
76 | }
77 | response, err := mi.provider.GenerateImage(ctx, prompt, options...)
78 | if err != nil {
79 | return response, err
80 | }
81 |
82 | if mi.opts != nil && mi.opts.hooks != nil {
83 | for _, hook := range mi.opts.hooks {
84 | hook.OnAfterInvoke(ctx, err)
85 | }
86 | }
87 | return response, err
88 | }
89 |
--------------------------------------------------------------------------------
/example/rag/milvus/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/joho/godotenv"
11 | "github.com/showntop/llmack/embedding"
12 | "github.com/showntop/llmack/vdb"
13 | "github.com/showntop/llmack/vdb/milvus"
14 | )
15 |
16 | func main() {
17 | godotenv.Load()
18 |
19 | stringEmbedder := embedding.NewStringEmbedder()
20 | vvv, err := milvus.New(&milvus.Config{
21 | Address: os.Getenv("milvus_address"),
22 | CollectionName: "documents",
23 | Embedder: stringEmbedder,
24 | Distance: vdb.DistanceCosine,
25 | Dim: stringEmbedder.Dimension(),
26 | })
27 | if err != nil {
28 | panic(err)
29 | }
30 |
31 | err = vvv.Create(context.Background())
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | err = vvv.Store(context.Background(), mockDocuments()...)
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | docs, err := vvv.SearchQuery(context.Background(), "埃菲尔铁塔", vdb.WithTopk(2))
42 | if err != nil {
43 | panic(err)
44 | }
45 | for _, doc := range docs {
46 | fmt.Println(doc.ID, doc.Title, doc.Content, doc.Embedding)
47 | }
48 | }
49 |
50 | func mockDocuments() []*vdb.Document {
51 | contents := `
52 | 1. 埃菲尔铁塔:位于法国巴黎,是世界上最著名的地标之一,由古斯塔夫·埃菲尔设计,建于1889年。
53 | 2. 长城:位于中国,是世界七大奇迹之一,始建于秦朝至明朝,全长超过20000公里。
54 | 3. 大峡谷国家公园:位于美国亚利桑那州,以其深深的峡谷和壮丽的景色而闻名,它被科罗拉多河切割而成。
55 | 4. 罗马斗兽场:位于意大利罗马,建于公元70-80年间,是古罗马帝国最大的圆形竞技场。
56 | 5. 泰姬陵:位于印度阿格拉,由莫卧儿皇帝沙贾汗于1653年为纪念他的妻子而建成,是世界新七大奇迹之一。
57 | 6. 悉尼歌剧院:位于澳大利亚悉尼港,是20世纪最具标志性的建筑之一,以其独特的帆船设计而闻名。
58 | 7. 卢浮宫博物馆:位于法国巴黎,是世界上最大的博物馆之一,收藏丰富,包括达·芬奇的《蒙娜丽莎》和希腊的《米洛的维纳斯》。
59 | 8. 尼亚加拉大瀑布:位于美国和加拿大边境,由三个主要瀑布组成,其壮观的景色每年吸引数百万游客。
60 | 9. 圣索菲亚大教堂:位于土耳其伊斯坦布尔,最初建于公元537年,曾是东正教大教堂和清真寺,现为博物馆。
61 | 10. 马丘比丘:位于秘鲁安第斯山脉高原上的古印加遗址,世界新七大奇迹之一,海拔2400多米。
62 | `
63 |
64 | var docs []*vdb.Document
65 | for idx, str := range strings.Split(contents, "\n") {
66 | if str == "" {
67 | continue
68 | }
69 | docs = append(docs, &vdb.Document{
70 | ID: strconv.FormatInt(int64(idx+1), 10) + "X",
71 | Content: str,
72 | })
73 | }
74 | return docs
75 | }
76 |
--------------------------------------------------------------------------------
/speech/tts/aliyun/client.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "sync"
7 | "time"
8 |
9 | "github.com/gorilla/websocket"
10 | )
11 |
12 | // WsClient ...
13 | type WsClient struct {
14 | sync.Mutex
15 | ws *websocket.Conn
16 | }
17 |
18 | // NewWsClient ...
19 | func NewWsClient(token string) (*WsClient, error) {
20 | wsc := &WsClient{}
21 |
22 | // url := "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1?token=%s"
23 | // url = fmt.Sprintf(url, token)
24 |
25 | // dialer := websocket.Dialer{}
26 | // header := http.Header(make(map[string][]string))
27 | // conn, _, err := dialer.Dial(url, header)
28 | // if err != nil {
29 | // return nil, err
30 | // }
31 | // wsc.ws = conn
32 | // fmt.Println("new")
33 |
34 | // go wsc.keepAlive()
35 |
36 | return wsc, nil
37 | }
38 |
39 | // reconnect ...
40 | func (c *WsClient) reconnect(token string) error {
41 | fmt.Println("reconnecting 1")
42 |
43 | c.Lock()
44 | defer c.Unlock()
45 |
46 | url := "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1?token=%s"
47 | url = fmt.Sprintf(url, token)
48 | fmt.Println("reconnecting")
49 | dialer := websocket.Dialer{}
50 | header := http.Header(make(map[string][]string))
51 | conn, _, err := dialer.Dial(url, header)
52 | if err != nil {
53 | return err
54 | }
55 | fmt.Println("reconnecting done")
56 | c.ws = conn
57 |
58 | return nil
59 | }
60 |
61 | // WriteJSON ...
62 | func (c *WsClient) WriteJSON(x any) error {
63 | c.Lock()
64 | defer c.Unlock()
65 |
66 | return c.ws.WriteJSON(x)
67 | }
68 |
69 | func (c *WsClient) keepAlive() {
70 | for {
71 | fmt.Println("keepAlive")
72 | c.Lock()
73 | if err := c.ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(10*time.Second)); err != nil {
74 | panic(err)
75 | }
76 | c.Unlock()
77 |
78 | time.Sleep(9 * time.Second)
79 | }
80 | }
81 |
82 | // Close ...
83 | func (c *WsClient) close() error {
84 | c.Lock()
85 | defer c.Unlock()
86 |
87 | return c.ws.Close()
88 | }
89 |
90 | // // Close ...
91 | // func (c *WsClient) Write() error {
92 | // return c.ws.Close()
93 | // }
94 |
--------------------------------------------------------------------------------
/tool/crawl/colly.go:
--------------------------------------------------------------------------------
1 | package crawl
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | "github.com/gocolly/colly/v2"
8 | "github.com/showntop/llmack/log"
9 | )
10 |
11 | func init() {
12 | Register(Jina, NewCollyCrawler())
13 | }
14 |
15 | // CollyCrawler 使用jina搜索
16 | type CollyCrawler struct {
17 | }
18 |
19 | // NewCollyCrawler 创建jina爬虫
20 | func NewCollyCrawler() Crawler {
21 | return &CollyCrawler{}
22 | }
23 |
24 | // CrawlMulti 爬取网页内容
25 | func (s *CollyCrawler) CrawlMulti(ctx context.Context, urls []string) (map[string]*Result, error) {
26 | log.InfoContextf(ctx, "tool spider crawl urls: %+v", urls)
27 | // 并发爬取
28 | wg := sync.WaitGroup{}
29 | resultChan := make(chan *Result, len(urls))
30 | for _, url := range urls {
31 | wg.Add(1)
32 | go func(url string) {
33 | defer wg.Done()
34 | content, err := s.Crawl(ctx, url)
35 | if err != nil {
36 | log.ErrorContextf(ctx, "tool spider crawl url %s failed: %v", url, err)
37 | }
38 | resultChan <- content
39 | }(url)
40 | }
41 |
42 | go func() {
43 | wg.Wait()
44 | close(resultChan)
45 | }()
46 |
47 | outputs := make(map[string]*Result, len(urls))
48 | for item := range resultChan {
49 | outputs[item.Link] = item
50 | }
51 | log.InfoContextf(ctx, "tool spider crawl result: %+v", len(outputs))
52 | return outputs, nil
53 | }
54 |
55 | // Crawl ...
56 | func (s *CollyCrawler) Crawl(ctx context.Context, url string) (*Result, error) {
57 |
58 | var title string
59 | var content string
60 | collector := colly.NewCollector(
61 | // colly.AllowedDomains("www.baidu.com"),
62 | colly.MaxDepth(1),
63 | colly.Async(true),
64 | )
65 | collector.OnHTML("title", func(e *colly.HTMLElement) {
66 | // log.InfoContextf(ctx, "title: %s", e.Text)
67 | title = e.Text
68 | })
69 |
70 | collector.OnHTML("body", func(e *colly.HTMLElement) {
71 | content = string(e.Text)
72 | log.InfoContextf(ctx, "tool spider crawl url %s response: %d", url, len(content))
73 | })
74 | collector.Visit(url)
75 | collector.Wait()
76 |
77 | return &Result{
78 | Title: title,
79 | Content: content,
80 | Link: url,
81 | }, nil
82 | }
83 |
--------------------------------------------------------------------------------
/pkg/structx/map.go:
--------------------------------------------------------------------------------
1 | package structx
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | )
7 |
8 | // ToStringMap converts a map[string]any to map[string]string.
9 | // It iterates through the input map and includes only the key-value pairs
10 | // where the value is of type string.
11 | func ToStringMap(inputMap map[string]any) map[string]string {
12 | resultMap := make(map[string]string)
13 | if inputMap == nil {
14 | return resultMap
15 | }
16 | for key, value := range inputMap {
17 | if strValue, ok := value.(string); ok {
18 | resultMap[key] = strValue
19 | }
20 | }
21 | return resultMap
22 | }
23 |
24 | // GetDefaultValue returns the default value if the key is not found in the data
25 | // Example: GetDefaultValue(data, "headless", false)
26 | func GetDefaultValue[T any](data map[string]any, key string, defaultValue T) T {
27 | if value, ok := data[key]; ok {
28 | if value, ok := value.(T); ok {
29 | return value
30 | }
31 | }
32 | return defaultValue
33 | }
34 |
35 | func ToSliceOfInt(input any) ([]int, error) {
36 | if input == nil {
37 | return nil, fmt.Errorf("input cannot be nil")
38 | }
39 | slice, ok := input.([]any)
40 | if !ok {
41 | return nil, fmt.Errorf("input is not a slice of interface")
42 | }
43 | result := make([]int, 0, len(slice))
44 |
45 | for i, elem := range slice {
46 | if elem == nil {
47 | return nil, fmt.Errorf("element at index %d is nil", i)
48 | }
49 | // if typeof elem is string, convert to int using strconv.Atoi, if it's int, just use it
50 | if strValue, ok := elem.(string); ok {
51 | value, err := strconv.Atoi(strValue)
52 | if err != nil {
53 | return nil, fmt.Errorf("failed to convert string to int: %s", err)
54 | }
55 | result = append(result, value)
56 | continue
57 | }
58 | value, ok := elem.(int)
59 | if !ok {
60 | return nil, fmt.Errorf("element at index %d is not of type %T, but %T", i, value, elem)
61 | }
62 | result = append(result, value)
63 | }
64 | return result, nil
65 | }
66 |
67 | func ToOptional[T any](value any) *T {
68 | if value, ok := value.(T); ok {
69 | return &value
70 | }
71 | return nil
72 | }
73 |
--------------------------------------------------------------------------------
/workflow/node/end.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 | "strings"
6 | "sync"
7 |
8 | "github.com/showntop/flatmap"
9 |
10 | "github.com/showntop/llmack/llm"
11 | "github.com/showntop/llmack/workflow"
12 | wf "github.com/showntop/llmack/workflow"
13 | )
14 |
15 | type endNode struct {
16 | Node *wf.Node
17 |
18 | executeable
19 | Identifier
20 | }
21 |
22 | func (nd endNode) Schema() string {
23 | return `{
24 | "kind": "end",
25 | "outputs": [
26 | {
27 | "name": "result",
28 | "type": "json"
29 | }
30 | ]
31 | }`
32 | }
33 |
34 | // EndNode initializes new end node with end definition
35 | func EndNode(node *workflow.Node) (*endNode, error) {
36 | return &endNode{Node: node}, nil
37 | }
38 |
39 | // Exec executes end node
40 | func (nd endNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) {
41 | result := make(map[string]any)
42 |
43 | // 处理输出
44 | // log.InfoContextf(ctx, "end node %+v inputs: %+v", nd.Node.Metadata, r.Inputs)
45 | // extract args
46 | mp, err := flatmap.Flatten(r.Inputs, flatmap.DefaultTokenizer)
47 | if err != nil {
48 | return result, err
49 | }
50 | // log.InfoContextf(ctx, "end node %+v inputs: %+v", nd.Node.Metadata, mp)
51 |
52 | wg := sync.WaitGroup{}
53 | wg.Add(len(nd.Node.Outputs))
54 | for name, p := range nd.Node.Outputs {
55 | pointer := strings.TrimPrefix(p.Value, "{{")
56 | pointer = strings.TrimSuffix(pointer, "}}")
57 | value := mp.Get(pointer)
58 | if response, ok := value.(*llm.Response); ok {
59 | go func() {
60 | stream := response.Stream()
61 | for chunk := stream.Take(); chunk != nil; chunk = stream.Take() {
62 | r.Events <- &workflow.Event{
63 | Name: name,
64 | Data: chunk,
65 | Type: "end",
66 | }
67 | }
68 | wg.Done()
69 | }()
70 | } else {
71 | r.Events <- &workflow.Event{
72 | Name: name,
73 | Data: value,
74 | Type: "end",
75 | }
76 | wg.Done()
77 | }
78 | result[name] = value
79 | }
80 | r.Events <- &workflow.Event{
81 | Name: "end",
82 | Data: result,
83 | }
84 | wg.Wait()
85 | close(r.Events)
86 | return result, nil
87 | }
88 |
--------------------------------------------------------------------------------
/prompt/simple_format.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "strings"
7 |
8 | "github.com/showntop/llmack/llm"
9 | "github.com/showntop/llmack/log"
10 | )
11 |
12 | // SimplePromptFormatter ...
13 | type SimplePromptFormatter struct {
14 | }
15 |
16 | // Format ...
17 | func (p *SimplePromptFormatter) Format(preset string,
18 | inputs map[string]any, query string, contexts string) ([]llm.Message, []string) {
19 | if preset != "" {
20 | formatter := NewTemplateFormatter(preset, false)
21 | preset = formatter.Format(inputs, true)
22 | }
23 | log.DefaultLogger().InfoContextf(context.Background(), "contexts: %v", contexts)
24 | frame := p.getPromptFrame()
25 |
26 | sysPrompt := ""
27 | variables := []string{}
28 | for _, v := range frame["system_prompt_orders"].([]any) {
29 | vname := v.(string)
30 | log.DefaultLogger().InfoContextf(context.Background(), "vname: %v", vname)
31 | if "context" == vname && contexts != "" {
32 | if c, ok := frame[vname].(string); ok {
33 | sysPrompt += c
34 | }
35 | }
36 | if "query" == vname && query != "" {
37 | if c, ok := frame[vname].(string); ok {
38 | sysPrompt += c
39 | }
40 | }
41 | if "preset" == vname {
42 | sysPrompt += preset + "\n"
43 | }
44 |
45 | variables = append(variables, "#"+v.(string)+"#")
46 | }
47 |
48 | sysPrompt = strings.ReplaceAll(sysPrompt, "{{#context#}}", contexts)
49 |
50 | sysMessage := llm.NewSystemMessage(sysPrompt)
51 |
52 | stops, ok := frame["stops"].(string)
53 | if ok {
54 | return []llm.Message{sysMessage}, []string{stops}
55 | }
56 | return []llm.Message{sysMessage}, nil
57 | }
58 |
59 | const simplePromptFrame = `{
60 | "context": "用户在与一个客观的助手对话。助手会尽量尊重找到的材料,给出全面专业的解释,但不会过度演绎。以下为材料内容:\n\n'''\n{{#context#}}\n'''\n,以下为用户的要求:\n",
61 | "system_prompt_orders": [
62 | "context",
63 | "preset"
64 | ],
65 | "query": "{{#query#}}",
66 | "stops": null
67 | }`
68 |
69 | func (p *SimplePromptFormatter) getPromptFrame() map[string]any {
70 | // os.ReadFile("./simple_prompt.json")
71 | prompt := make(map[string]any)
72 | json.Unmarshal([]byte(simplePromptFrame), &prompt)
73 | return prompt
74 | }
75 |
--------------------------------------------------------------------------------
/example/bookkeeper/web/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
13 |
14 | ```js
15 | export default tseslint.config({
16 | extends: [
17 | // Remove ...tseslint.configs.recommended and replace with this
18 | ...tseslint.configs.recommendedTypeChecked,
19 | // Alternatively, use this for stricter rules
20 | ...tseslint.configs.strictTypeChecked,
21 | // Optionally, add this for stylistic rules
22 | ...tseslint.configs.stylisticTypeChecked,
23 | ],
24 | languageOptions: {
25 | // other options...
26 | parserOptions: {
27 | project: ['./tsconfig.node.json', './tsconfig.app.json'],
28 | tsconfigRootDir: import.meta.dirname,
29 | },
30 | },
31 | })
32 | ```
33 |
34 | You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
35 |
36 | ```js
37 | // eslint.config.js
38 | import reactX from 'eslint-plugin-react-x'
39 | import reactDom from 'eslint-plugin-react-dom'
40 |
41 | export default tseslint.config({
42 | plugins: {
43 | // Add the react-x and react-dom plugins
44 | 'react-x': reactX,
45 | 'react-dom': reactDom,
46 | },
47 | rules: {
48 | // other rules...
49 | // Enable its recommended typescript rules
50 | ...reactX.configs['recommended-typescript'].rules,
51 | ...reactDom.configs.recommended.rules,
52 | },
53 | })
54 | ```
55 |
--------------------------------------------------------------------------------
/llm/cache_redis.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/go-redis/redis/v8"
7 | "github.com/google/uuid"
8 | "github.com/showntop/llmack/embedding"
9 | "github.com/showntop/llmack/vdb"
10 | "github.com/showntop/llmack/vdb/memo"
11 | )
12 |
13 | // RedisCache ...
14 | type RedisCache struct {
15 | BaseCache
16 | cli *redis.Client
17 | }
18 |
19 | // NewRedisCache ...
20 | func NewRedisCache(cli *redis.Client) Cache {
21 | return &RedisCache{
22 | BaseCache: BaseCache{
23 | Config: DefaultConfig(),
24 | Embedder: embedding.NewStringEmbedder(),
25 | VectorStore: memo.New(),
26 | },
27 | cli: cli,
28 | }
29 | }
30 |
31 | // Fetch ...
32 | func (m *RedisCache) Fetch(ctx context.Context, messages []Message) (*CachedDocument, bool, error) {
33 | // 处理query
34 | query, err := m.Config.QueryProcessor(messages)
35 | if err != nil {
36 | return nil, false, err
37 | }
38 | // embedding vector
39 | vector, err := m.Embedder.Embed(ctx, query)
40 | if err != nil {
41 | return nil, false, err
42 | }
43 | // search vector ids
44 | relations, err := m.VectorStore.Search(ctx, vector, vdb.WithTopk(5))
45 | if err != nil {
46 | return nil, false, err
47 | }
48 | if relations == nil || len(relations) == 0 {
49 | return &CachedDocument{Query: query, Vector: vector}, false, nil
50 | }
51 | ids := make([]string, 0, len(relations))
52 | for i := 0; i < len(relations); i++ {
53 | ids[i] = relations[i].ID
54 | }
55 | // batch get
56 | res, err := m.cli.MGet(ctx, ids...).Result()
57 | if err != nil {
58 | return nil, false, err
59 | }
60 | _ = res
61 | // object data
62 | documents := make([]*CachedDocument, 0, len(relations))
63 |
64 | for i := 0; i < len(relations); i++ {
65 |
66 | }
67 | if len(documents) == 0 {
68 | return nil, false, nil
69 | }
70 |
71 | // 排序
72 | document := documents[0]
73 | return document, true, nil
74 | }
75 |
76 | // Store ...
77 | func (m *RedisCache) Store(ctx context.Context, document *CachedDocument, value string) error {
78 | document.ID = uuid.NewString()
79 | // if err := m.VectorStore.Store(ctx, document.ID, document.Vector); err != nil {
80 | // return err
81 | // }
82 | return nil
83 | }
84 |
--------------------------------------------------------------------------------
/program/response.go:
--------------------------------------------------------------------------------
1 | package program
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "reflect"
7 |
8 | "github.com/showntop/llmack/llm"
9 | "github.com/showntop/llmack/log"
10 | )
11 |
12 | func (p *predictor) Response() *Response {
13 | return p.reponse
14 | }
15 |
16 | func (p *predictor) Stream() chan *llm.Chunk {
17 | return p.reponse.stream
18 | }
19 |
20 | func (p *predictor) Completion() string {
21 | return p.reponse.message.Content()
22 | }
23 |
24 | func (p *predictor) Message() *llm.AssistantMessage {
25 | return p.reponse.message
26 | }
27 |
28 | func (p *predictor) Error() error {
29 | return p.reponse.err
30 | }
31 |
32 | func (p *predictor) ToolCalls() []*llm.ToolCall {
33 | return p.reponse.toolCalls
34 | }
35 |
36 | type Response struct {
37 | p *predictor
38 | err error
39 | completion string
40 | toolCalls []*llm.ToolCall
41 | stream chan *llm.Chunk
42 |
43 | message *llm.AssistantMessage
44 |
45 | HasToolCalls bool
46 | }
47 |
48 | func NewResponse() *Response {
49 | return &Response{
50 | stream: make(chan *llm.Chunk, 1000),
51 | message: &llm.AssistantMessage{},
52 | }
53 | }
54 |
55 | func (r *Response) Error() error {
56 | return r.err
57 | }
58 |
59 | func (r *Response) Get(value any) error {
60 | return r.p.adapter.Parse(r.completion, value)
61 | }
62 |
63 | func (r *Response) Completion() string {
64 | return r.message.Content()
65 | }
66 |
67 | func (r *Response) ToolCalls() []*llm.ToolCall {
68 | return r.toolCalls
69 | }
70 |
71 | func (r *Response) Stream() chan *llm.Chunk {
72 | return r.stream
73 | }
74 |
75 | // Result ...
76 | func (p *predictor) Result(ctx context.Context, value any) error {
77 | if reflect.TypeOf(value).Kind() != reflect.Ptr {
78 | return fmt.Errorf("predictor result target value must be a pointer")
79 | }
80 |
81 | messages, err := p.adapter.Format(p, p.inputs, value)
82 | if err != nil {
83 | return err
84 | }
85 | response, err := p.model.Invoke(ctx, messages,
86 | llm.WithStream(true),
87 | )
88 | if err != nil {
89 | return err
90 | }
91 | completion := response.Result().Message.Content()
92 | log.InfoContextf(ctx, "response: %s", completion)
93 |
94 | return p.adapter.Parse(completion, value)
95 | }
96 |
--------------------------------------------------------------------------------
/rag/deepdoc/extractor/document.go:
--------------------------------------------------------------------------------
1 | package extractor
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/showntop/llmack/log"
7 | "github.com/showntop/llmack/rag/deepdoc"
8 | )
9 |
10 | // DocumentExtractor ...
11 | type DocumentExtractor struct {
12 | }
13 |
14 | // NewDocumentExtractor ...
15 | func NewDocumentExtractor() *DocumentExtractor {
16 | return &DocumentExtractor{}
17 | }
18 |
19 | // Extract ...
20 | func (e *DocumentExtractor) Extract(m *Meta, binary []byte) ([]string, error) {
21 | filename := m.Filename
22 | var sections []string
23 | var err error
24 | if v := reDocx.FindStringIndex(filename); v != nil { // docx
25 | sections, err = deepdoc.Docx().Extract(filename, binary)
26 | } else if v := reXlsx.FindStringIndex(filename); v != nil {
27 | sections, err = deepdoc.Excel().Extract(filename, binary)
28 | } else if v := rePptx.FindStringIndex(filename); v != nil {
29 | sections, err = deepdoc.Pptx().Extract(filename, binary)
30 | } else if v := rePdf.FindStringIndex(filename); v != nil {
31 | sections, err = deepdoc.Pdf().Extract(filename, binary)
32 | } else if v := reTxtx.FindStringIndex(filename); v != nil {
33 | sections, err = deepdoc.Txtx().Extract(filename, binary)
34 | } else if v := reDoc.FindStringIndex(filename); v != nil {
35 | sections, err = deepdoc.Doc().Extract(filename, binary)
36 | } else if v := reCsv.FindStringIndex(filename); v != nil {
37 | sections, err = deepdoc.Csv().Extract(filename, binary)
38 | } else if v := reJson.FindStringIndex(filename); v != nil {
39 | sections, err = deepdoc.Json().Extract(filename, binary)
40 | } else if v := reMdx.FindStringIndex(filename); v != nil {
41 | sections, err = deepdoc.Mdx().Extract(filename, binary)
42 | } else {
43 | log.WarnContextf(nil, "extract unknow document filename: %s by txt", filename)
44 | sections, err = deepdoc.Txtx().Extract(filename, binary)
45 | // return nil, fmt.Errorf("unsupported file format: %s", filename)
46 | }
47 | if err != nil {
48 | // 兜底 解析 按照文本
49 | log.WarnContextf(nil, "extract document error: %v, filename: %s", err, filename)
50 | sections, err = deepdoc.Txtx().Extract(filename, binary)
51 | return nil, err
52 | }
53 |
54 | return []string{strings.Join(sections, "")}, nil
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/adb/manager.go:
--------------------------------------------------------------------------------
1 | package adb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | )
7 |
8 | // Manager 设备管理器
9 | type Manager struct {
10 | wrapper *Wrapper
11 | devices map[string]*Device
12 | }
13 |
14 | // NewManager 创建新的设备管理器
15 | func NewManager(adbPath string) *Manager {
16 | return &Manager{
17 | wrapper: NewWrapper(adbPath),
18 | devices: make(map[string]*Device),
19 | }
20 | }
21 |
22 | // ListDevices 列出连接的设备
23 | func (m *Manager) ListDevices(ctx context.Context) ([]*Device, error) {
24 | devicesInfo, err := m.wrapper.GetDevices(ctx)
25 | if err != nil {
26 | return nil, err
27 | }
28 |
29 | // 更新设备缓存
30 | currentSerials := make(map[string]bool)
31 | for _, deviceInfo := range devicesInfo {
32 | serial := deviceInfo.Serial
33 | currentSerials[serial] = true
34 |
35 | if _, exists := m.devices[serial]; !exists {
36 | m.devices[serial] = NewDevice(serial, m.wrapper)
37 | }
38 | }
39 |
40 | // 移除已断开连接的设备
41 | for serial := range m.devices {
42 | if !currentSerials[serial] {
43 | delete(m.devices, serial)
44 | }
45 | }
46 |
47 | // 返回设备列表
48 | var devices []*Device
49 | for _, device := range m.devices {
50 | devices = append(devices, device)
51 | }
52 |
53 | return devices, nil
54 | }
55 |
56 | // GetDevice 获取特定设备
57 | func (m *Manager) GetDevice(ctx context.Context, serial string) (*Device, error) {
58 | if device, exists := m.devices[serial]; exists {
59 | return device, nil
60 | }
61 |
62 | // 尝试查找设备
63 | devices, err := m.ListDevices(ctx)
64 | if err != nil {
65 | return nil, err
66 | }
67 |
68 | for _, device := range devices {
69 | if device.Serial == serial {
70 | return device, nil
71 | }
72 | }
73 |
74 | return nil, fmt.Errorf("设备 %s 未找到", serial)
75 | }
76 |
77 | // Connect 连接到网络设备
78 | func (m *Manager) Connect(ctx context.Context, host string, port int) (*Device, error) {
79 | serial, err := m.wrapper.Connect(ctx, host, port)
80 | if err != nil {
81 | return nil, err
82 | }
83 |
84 | return m.GetDevice(ctx, serial)
85 | }
86 |
87 | // Disconnect 断开设备连接
88 | func (m *Manager) Disconnect(ctx context.Context, serial string) error {
89 | err := m.wrapper.Disconnect(ctx, serial)
90 | if err == nil {
91 | delete(m.devices, serial)
92 | }
93 | return err
94 | }
95 |
--------------------------------------------------------------------------------
/example/bookkeeper/services/image_service.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "mime/multipart"
8 | "os"
9 | "path/filepath"
10 | "time"
11 |
12 | "github.com/bookkeeper-ai/bookkeeper/config"
13 | "github.com/showntop/llmack/llm"
14 | "github.com/showntop/llmack/llm/zhipu"
15 | )
16 |
17 | type ImageService struct {
18 | uploadDir string
19 | llmClient *llm.Instance
20 | }
21 |
22 | func NewImageService(cfg *config.Config) (*ImageService, error) {
23 | uploadDir := "uploads"
24 | if err := os.MkdirAll(uploadDir, 0755); err != nil {
25 | return nil, fmt.Errorf("创建上传目录失败: %v", err)
26 | }
27 |
28 | llmClient := llm.NewInstance(zhipu.Name)
29 | // llm.WithSingleConfig(map[string]any{
30 | // "api_key": os.Getenv("ZHIPU_API_KEY"),
31 | // })
32 |
33 | return &ImageService{
34 | uploadDir: uploadDir,
35 | llmClient: llmClient,
36 | }, nil
37 | }
38 |
39 | // UploadAndAnalyze 上传并分析图片
40 | func (s *ImageService) UploadAndAnalyze(file *multipart.FileHeader) (string, map[string]interface{}, error) {
41 | // 保存文件
42 | filename := fmt.Sprintf("%d_%s", time.Now().Unix(), filepath.Base(file.Filename))
43 | filepath := filepath.Join(s.uploadDir, filename)
44 |
45 | src, err := file.Open()
46 | if err != nil {
47 | return "", nil, fmt.Errorf("打开文件失败: %v", err)
48 | }
49 | defer src.Close()
50 |
51 | dst, err := os.Create(filepath)
52 | if err != nil {
53 | return "", nil, fmt.Errorf("创建文件失败: %v", err)
54 | }
55 | defer dst.Close()
56 |
57 | if _, err = io.Copy(dst, src); err != nil {
58 | return "", nil, fmt.Errorf("保存文件失败: %v", err)
59 | }
60 |
61 | // 使用 LLM 分析图片
62 | ctx := context.Background()
63 | resp, err := s.llmClient.Invoke(ctx, []llm.Message{
64 | llm.NewUserTextMessage(fmt.Sprintf("请分析这张消费小票图片,提取以下信息:1. 消费金额 2. 消费类别 3. 消费日期 4. 商家名称。图片路径:%s", filepath)),
65 | }, nil, llm.WithModel("GLM-4-Flash"))
66 |
67 | if err != nil {
68 | return "", nil, fmt.Errorf("分析图片失败: %v", err)
69 | }
70 |
71 | // 解析 LLM 返回的结果
72 | // TODO: 根据实际返回格式解析结果
73 | result := map[string]interface{}{
74 | "amount": 0.0,
75 | "category": "",
76 | "date": time.Now(),
77 | "merchant": "",
78 | "raw_result": resp.Result(),
79 | }
80 |
81 | return filepath, result, nil
82 | }
83 |
--------------------------------------------------------------------------------
/llm/hook.go:
--------------------------------------------------------------------------------
1 | package llm
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "database/sql/driver"
7 | "io"
8 |
9 | "go.opentelemetry.io/otel"
10 | "go.opentelemetry.io/otel/attribute"
11 | "go.opentelemetry.io/otel/codes"
12 | "go.opentelemetry.io/otel/trace"
13 | )
14 |
15 | // Hook ...
16 | type Hook interface {
17 | OnBeforeInvoke(context.Context) context.Context
18 | OnAfterInvoke(ctx context.Context, err error)
19 | OnFirstChunk(context.Context, error) context.Context
20 | OnLastChunk(context.Context, error)
21 | }
22 |
23 | // OtelHook ...
24 | type OtelHook struct {
25 | provider trace.TracerProvider
26 | tracer trace.Tracer
27 | }
28 |
29 | // HookOption ...
30 | type HookOption func(*OtelHook)
31 |
32 | // NewOtelHook ...
33 | func NewOtelHook(opts ...HookOption) Hook {
34 | h := &OtelHook{}
35 | h.provider = otel.GetTracerProvider()
36 | h.tracer = h.provider.Tracer("github.com/showntop/llmack/opentelemetry")
37 |
38 | return h
39 | }
40 |
41 | // OnFirstChunk ...
42 | func (h *OtelHook) OnFirstChunk(ctx context.Context, _ error) context.Context {
43 | ctx, _ = h.tracer.Start(ctx, "llm/response", trace.WithSpanKind(trace.SpanKindInternal))
44 |
45 | return ctx
46 | }
47 |
48 | // OnLastChunk ...
49 | func (h *OtelHook) OnLastChunk(ctx context.Context, err error) {
50 | span := trace.SpanFromContext(ctx)
51 | if !span.IsRecording() {
52 | return
53 | }
54 | defer span.End()
55 | if err != nil {
56 | span.RecordError(err)
57 | span.SetStatus(codes.Error, err.Error())
58 | }
59 | }
60 |
61 | // OnBeforeInvoke ...
62 | func (h *OtelHook) OnBeforeInvoke(ctx context.Context) context.Context {
63 | ctx, _ = h.tracer.Start(ctx, "llm/invoke", trace.WithSpanKind(trace.SpanKindInternal))
64 | return ctx
65 | }
66 |
67 | // OnAfterInvoke ...
68 | func (h *OtelHook) OnAfterInvoke(ctx context.Context, err error) {
69 | span := trace.SpanFromContext(ctx)
70 | if !span.IsRecording() {
71 | return
72 | }
73 | defer span.End()
74 |
75 | attrs := make([]attribute.KeyValue, 0, 4)
76 |
77 | span.SetAttributes(attrs...)
78 | switch err {
79 | case nil,
80 | driver.ErrSkip,
81 | io.EOF, // end of rows iterator
82 | sql.ErrNoRows:
83 | // ignore
84 | default:
85 | span.RecordError(err)
86 | span.SetStatus(codes.Error, err.Error())
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/speech/asr/tencent/tencent.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/showntop/llmack/log"
8 | "github.com/showntop/llmack/speech"
9 | "github.com/showntop/tencentcloud-speech-sdk-go/asr"
10 | "github.com/showntop/tencentcloud-speech-sdk-go/common"
11 | )
12 |
13 | // ASR ...
14 | type ASR struct {
15 | options Options
16 | recognizer *asr.SpeechRecognizer
17 | }
18 |
19 | // NewASR 新建一个ASR
20 | func NewASR(opts ...Option) speech.ASR {
21 | t := &ASR{}
22 |
23 | for i := 0; i < len(opts); i++ {
24 | opts[i](&t.options)
25 | }
26 |
27 | if t.options.listener == nil {
28 | t.options.listener = &DefaultListener{
29 | t: t,
30 | ID: 1,
31 | }
32 | }
33 |
34 | EngineModelType := "16k_zh"
35 | credential := common.NewCredential(t.options.SecretID, t.options.SecretKey)
36 | recognizer := asr.NewSpeechRecognizer(t.options.AppID, credential, EngineModelType, t.options.listener)
37 |
38 | recognizer.VoiceFormat = asr.AudioFormatPCM
39 | // recognizer.VoiceFormat = asr.AudioFormatWav
40 | // recognizer.NeedVad = 1
41 | recognizer.NoiseThreshold = 0.8
42 |
43 | // recognizer.VoiceFormat = asr.AudioFormatWav
44 | if err := recognizer.Start(); err != nil {
45 | panic(err)
46 | }
47 | log.InfoContextf(context.Background(), "asr engine start success")
48 | t.recognizer = recognizer
49 | // fmt.Println(t.recognizer)
50 | return t
51 | }
52 |
53 | // Input 异步实现语音识别,转录为文字
54 | func (t *ASR) Input(content []byte) error {
55 | if len(content) == 0 {
56 | return nil
57 | }
58 |
59 | startTime := time.Now()
60 | // 置换 chunk 状态
61 | chunkSizePerDuration := float64(48000) * 2.00 / float64(time.Second)
62 | durations := time.Duration(float64(len(content)) / chunkSizePerDuration)
63 | if err := t.recognizer.Write(content); err != nil {
64 | panic(err)
65 | }
66 | endTime := time.Now()
67 |
68 | x := durations - endTime.Sub(startTime)
69 | // fmt.Printf("size:%d, chunk: %f durations: %s, sleep x=%s \n", len(content), chunkSizePerDuration, durations, x)
70 | if x > 0 {
71 | time.Sleep(x)
72 | }
73 | return nil
74 | }
75 |
76 | // Recognize 实现语音识别,转录为文字
77 | func (t *ASR) Recognize(content []byte) (string, error) {
78 | panic("implement me")
79 | }
80 |
81 | // Close 关闭
82 | func (t *ASR) Close() error {
83 | return t.recognizer.Stop()
84 | }
85 |
--------------------------------------------------------------------------------
/storage/json_store.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "io"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | type JSONStorage struct {
12 | path string
13 | }
14 |
15 | func NewJSONStorage(path string) Storage {
16 | if _, err := os.Stat(path); os.IsNotExist(err) {
17 | if err := os.MkdirAll(path, 0755); err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | return &JSONStorage{
23 | path: path,
24 | }
25 | }
26 |
27 | func (s *JSONStorage) AddNewJourney(ctx context.Context, journey *Journey) error {
28 | panic("not implemented")
29 | }
30 |
31 | func (s *JSONStorage) SaveSession(ctx context.Context, session *Session) error {
32 | // create file
33 | filePath := filepath.Join(s.path, session.UID+".json")
34 | file, err := os.Create(filePath)
35 | if err != nil {
36 | return err
37 | }
38 | defer file.Close()
39 |
40 | return json.NewEncoder(file).Encode(session)
41 | }
42 |
43 | func (s *JSONStorage) FetchSession(ctx context.Context, id string) (*Session, error) {
44 | filePath := filepath.Join(s.path, id+".json")
45 | // 如果不存在就创建
46 | if _, err := os.Stat(filePath); os.IsNotExist(err) {
47 | _, err := os.Create(filePath)
48 | if err != nil {
49 | return nil, err
50 | }
51 | return &Session{
52 | UID: id,
53 | }, nil
54 | }
55 | file, err := os.Open(filePath)
56 | if err != nil {
57 | return nil, err
58 | }
59 | defer file.Close()
60 |
61 | content, err := io.ReadAll(file)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | if len(content) == 0 {
67 | return &Session{
68 | UID: id,
69 | }, nil
70 | }
71 |
72 | var session Session
73 | if err := json.Unmarshal(content, &session); err != nil {
74 | return nil, err
75 | }
76 |
77 | return &session, nil
78 | }
79 |
80 | func (s *JSONStorage) UpdateSession(ctx context.Context, session *Session) error {
81 | filePath := filepath.Join(s.path, session.UID+".json")
82 | file, err := os.Open(filePath)
83 | if err != nil {
84 | return err
85 | }
86 | defer file.Close()
87 |
88 | return json.NewEncoder(file).Encode(session)
89 | }
90 |
91 | func (s *JSONStorage) DeleteSession(ctx context.Context, id string) error {
92 | filePath := filepath.Join(s.path, id+".json")
93 | if err := os.Remove(filePath); err != nil {
94 | return err
95 | }
96 |
97 | return nil
98 | }
99 |
--------------------------------------------------------------------------------
/tool/adb/registry.go:
--------------------------------------------------------------------------------
1 | package adb
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/showntop/llmack/log"
8 | "github.com/showntop/llmack/tool"
9 | )
10 |
11 | var (
12 | registry *Registry = NewRegistry()
13 | )
14 |
15 | // The main service class that manages action registration and execution
16 | type Registry struct {
17 | Tools map[string]*tool.Tool
18 | }
19 |
20 | func NewRegistry() *Registry {
21 | return &Registry{
22 | Tools: make(map[string]*tool.Tool),
23 | }
24 | }
25 |
26 | // RegisterTool should be called after registry initialization
27 | // registry.Tool("click_element_by_index", ClickElementFunc, "click action", paramModel, domains, pageFilter)
28 | func RegisterTool[T, D any](
29 | r *Registry,
30 | name string,
31 | description string,
32 | function tool.ToolFunc[T, D],
33 | ) error {
34 |
35 | toolx, err := tool.NewWithToolFunc(name, description, function)
36 | if err != nil {
37 | return err
38 | }
39 | r.Tools[name] = toolx
40 | tool.Register(toolx)
41 | log.InfoContextf(context.Background(), "注册工具 name: %s description: %s", name, description)
42 | return nil
43 | }
44 |
45 | func RegisterGlobalTool[T, D any](name string, description string, function tool.ToolFunc[T, D]) error {
46 | return RegisterTool(registry, name, description, function)
47 | }
48 |
49 | // ExecuteTool a registered action
50 | // TODO(LOW): support Context
51 | func (r *Registry) ExecuteTool(
52 | ctx context.Context,
53 | toolName string,
54 | arguments string,
55 | sensitiveData map[string]string,
56 | ) (string, error) {
57 | tool, ok := r.Tools[toolName]
58 | if !ok {
59 | return "", errors.New("tool not found")
60 | }
61 |
62 | result, err := tool.Invoke(ctx, arguments)
63 | if err != nil {
64 | return "", err
65 | }
66 |
67 | return result, nil
68 | }
69 |
70 | func (r *Registry) AvailableTools(includeTools []string) []any {
71 | // Create model from registered actions, used by LLM APIs that support tool calling
72 |
73 | // Filter tools based on includeTools if provided:
74 | // if includeTools is nil, only include tools with no filters
75 | // if includeTools is provided, only include tools that match the includeTools
76 |
77 | availableTools := make([]any, 0)
78 | for name := range r.Tools {
79 | availableTools = append(availableTools, name)
80 | }
81 |
82 | return availableTools
83 | }
84 |
--------------------------------------------------------------------------------
/workflow/node.go:
--------------------------------------------------------------------------------
1 | package workflow
2 |
3 | // NodeKind TODO
4 | type NodeKind string
5 |
6 | const (
7 | // NodeKindTool TODO
8 | NodeKindTool NodeKind = "tool" // ref =
9 | // NodeKindExpr TODO
10 | NodeKindExpr NodeKind = "expr" // ref =
11 | // NodeKindLLM TODO
12 | NodeKindLLM NodeKind = "llm" // ref =
13 | // NodeKindGateway TODO
14 | NodeKindGateway NodeKind = "gateway" // ref = join|fork|excl|incl
15 | // NodeKindIterator TODO
16 | NodeKindIterator NodeKind = "iterator" // ref =
17 | // NodeKindError TODO
18 | NodeKindError NodeKind = "error" // no ref
19 | // NodeKindTermination TODO
20 | NodeKindTermination NodeKind = "termination" // no ref
21 | // NodeKindPrompt TODO
22 | NodeKindPrompt NodeKind = "prompt" // ref =
23 | // NodeKindDelay TODO
24 | NodeKindDelay NodeKind = "delay" // no ref
25 | // NodeKindWait TODO
26 | NodeKindWait NodeKind = "wait" // no ref
27 | // NodeKindVisual TODO
28 | NodeKindVisual NodeKind = "visual" // ref = <*>
29 | // NodeKindDebug TODO
30 | NodeKindDebug NodeKind = "debug" // ref = <*>
31 | // NodeKindBreak TODO
32 | NodeKindBreak NodeKind = "break" // ref = <*>
33 | // NodeKindContinue TODO
34 | NodeKindContinue NodeKind = "continue" // ref = <*>
35 | // NodeKindStart TODO
36 | NodeKindStart NodeKind = "start" // start event
37 | // NodeKindEnd TODO
38 | NodeKindEnd NodeKind = "end" // end event
39 | // NodeKindHuman TODO
40 | NodeKindHuman NodeKind = "human" // user involved in node
41 | )
42 |
43 | // Node ...
44 | type Node struct {
45 | ID string `json:"id"` // 节点ID
46 | Name string `json:"name"` // 节点名称 support expr @TODO
47 | Description string `json:"description"` // 节点描述
48 | Kind NodeKind `json:"kind"` // 节点类型
49 | Subref string `json:"subref"` // reference to function or subprocess (gateway)
50 | // set of expressions to evaluate, test or pass to function
51 | Inputs Parameters `json:"inputs"`
52 | Outputs Parameters `json:"outputs"`
53 |
54 | // Events []Event `json:"events2"` // 边界事件
55 | // Callbacks []*Action `json:"events"` // 普通事件
56 | // for business configure
57 | // its free now, need tobe constraint in the future
58 | Metadata map[string]any `json:"metadata"`
59 | }
60 |
61 | type NodeID struct {
62 | ID string
63 | }
64 |
--------------------------------------------------------------------------------
/prompt/templates/example.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | // ExampleGenerate 样本生成提示词
4 | const ExampleGenerate = `
5 | You are an expert example selector who can help in selection of right in-context examples to help the most suitable agent solve this problem.
6 | You are also given the prompt instruction which is used to solve this task
7 | [Prompt]: {{prompt}}
8 | You are given the task description of the task:
9 | [Task Description]: {{task_description}}
10 | I'm trying to write a few shots prompt using {{num_examples}} in-context examples to effectively solve any questions of the above task.
11 | Think of analysing, understanding and creating examples of task on the criteria of diversity of types of examples, complexity of the nature/characteristics of the examples and relevance/compatibility to the whole example set in total.
12 | Output all the suggestions/ improvement which could be made to improve each individual example of the whole example selection set.
13 | `
14 |
15 | // ExampleOptimization 样本优化提示词
16 | const ExampleOptimization = `
17 | You are an expert example selector who can help in selection of right in-context examples to help the agent solve this problem.
18 | You are also given the prompt instruction which is used to solve this task
19 | [Prompt]: {prompt}
20 | You are given the description of the task:
21 | [Task Description]: {task_description}
22 | I'm trying to write a few shots prompt using {num_examples} in-context examples to effectively solve any questions of the above task.
23 | My current {num_examples} in-context examples set are: {examples}
24 | You are also given a set of suggestions/improvements which could be made to improve each individual example of the whole example selection set:
25 | [SUGGESTION/IMPROVEMENT]: {critique}
26 | Based on the above information, use all of it smartly and diligently to carefully create new set of {num_examples}, which follow these suggestion and improvements.
27 | Make sure to output each example wrapped with and .
28 |
29 | New examples should follow this format strictly:
30 |
31 | [Question] followed by question part of the example
32 | [Answer] followed by the all the steps of logic reasoning statements related to answer. The final answer as "[answer]"
33 |
34 | For Example:
35 | {gt_example}
36 |
37 |
38 | [New Examples]:
39 | `
40 |
--------------------------------------------------------------------------------
/tool/wikipedia/wikipedia.go:
--------------------------------------------------------------------------------
1 | package wikipedia
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | "github.com/showntop/llmack/tool"
9 | gowiki "github.com/trietmn/go-wiki"
10 | )
11 |
12 | func init() {
13 | t := tool.New(
14 | tool.WithName("wikipedia_search"),
15 | tool.WithKind("code"),
16 | tool.WithDescription("A tool for performing a Wikipedia search and extracting snippets and webpages. Input should be a search query."),
17 | tool.WithParameters(
18 | tool.Parameter{
19 | Name: "query", Type: tool.String, Required: true, LLMDescrition: `key words for searching, this should be in the language of "language" parameter`,
20 | },
21 | tool.Parameter{
22 | Name: "language", Type: tool.String, Required: true, LLMDescrition: `
23 | language of the wikipedia to be searched,
24 | only "de" for German,
25 | "en" for English,
26 | "fr" for French,
27 | "hi" for Hindi,
28 | "ja" for Japanese,
29 | "ko" for Korean,
30 | "pl" for Polish,
31 | "pt" for Portuguese,
32 | "ro" for Romanian,
33 | "uk" for Ukrainian,
34 | "vi" for Vietnamese,
35 | and "zh" for Chinese are supported
36 | `,
37 | Options: []string{"de", "en", "fr", "hi", "ja", "ko", "pl", "pt", "ro", "uk", "vi", "zh"},
38 | },
39 | ),
40 | tool.WithFunction(Invoke),
41 | )
42 | tool.Register(t)
43 | }
44 |
45 | // Invoke ...
46 | func Invoke(ctx context.Context, args string) (string, error) {
47 | var params struct {
48 | Query string `json:"query"`
49 | Language string `json:"language"`
50 | }
51 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
52 | return "", err
53 | }
54 |
55 | if params.Query == "" {
56 | return "Please input query", fmt.Errorf("query is empty")
57 | }
58 |
59 | // Search for the Wikipedia page title
60 | searchResult, _, err := gowiki.Search(params.Query, 3, false)
61 | if err != nil {
62 | return "", fmt.Errorf("search failed: %v", err)
63 | }
64 |
65 | if len(searchResult) == 0 {
66 | return "No search results found", nil
67 | }
68 |
69 | // Get the first page
70 | page, err := gowiki.GetPage(searchResult[0], -1, false, true)
71 | if err != nil {
72 | return "", fmt.Errorf("failed to get page: %v", err)
73 | }
74 |
75 | // Get the content of the page
76 | content, err := page.GetContent()
77 | if err != nil {
78 | return "", fmt.Errorf("failed to get content: %v", err)
79 | }
80 |
81 | return content, nil
82 | }
83 |
--------------------------------------------------------------------------------
/rag/deepdoc/pdf.go:
--------------------------------------------------------------------------------
1 | package deepdoc
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "log"
7 |
8 | "github.com/showntop/llmack/pkg/strings"
9 |
10 | "github.com/dslipak/pdf"
11 | "github.com/pdfcpu/pdfcpu/pkg/api"
12 |
13 | "github.com/showntop/unipdf/extractor"
14 | "github.com/showntop/unipdf/model"
15 | )
16 |
17 | // PdfDocument ...
18 | type PdfDocument struct {
19 | }
20 |
21 | // Pdf ...
22 | func Pdf() *PdfDocument {
23 | return &PdfDocument{}
24 | }
25 |
26 | // Extract ...
27 | func (d *PdfDocument) Extract(filename string, binary []byte) ([]string, error) {
28 | sections := make([]string, 0)
29 |
30 | reader, err := model.NewPdfReader(bytes.NewReader(binary))
31 | if err != nil {
32 | return nil, err
33 | }
34 | num, _ := reader.GetNumPages()
35 | for i := 1; i <= num; i++ {
36 | page, _ := reader.GetPage(i)
37 | ext, _ := extractor.New(page)
38 | text, _ := ext.ExtractText()
39 | sections = append(sections, text)
40 | }
41 |
42 | return sections, nil
43 | }
44 |
45 | // Extract ...
46 | func (d *PdfDocument) Extract3(filename string, binary []byte) ([]string, error) {
47 | sections := make([]string, 0)
48 | ctx, err := api.ReadContextFile("./pkg/deepdoc/example/销售认证-销售代表-学习指引.pdf")
49 | // ctx, err := api.ReadContext(bytes.NewReader(binary), model.NewDefaultConfiguration())
50 | // pdf2.ExtractContentFile("in.pdf", "outDir", nil, nil)
51 | if err != nil {
52 | log.Fatal(err)
53 | }
54 | page, _ := api.ExtractPage(ctx, 1)
55 | var p []byte = make([]byte, 100000)
56 | fmt.Println(page.Read(p))
57 |
58 | fmt.Println(string(p))
59 |
60 | api.ExtractContentFile("./pkg/deepdoc/example/销售认证-销售代表-学习指引.pdf", "./", []string{"1"}, nil)
61 | // fmt.Println()
62 | // api.ExtractContent()
63 | // p, _ := ctx.Pages()
64 | // _ = p
65 | // fmt.Println(p.PDFString())
66 | return sections, nil
67 | }
68 |
69 | func (d *PdfDocument) Extract2(filename string, binary []byte) ([]string, error) {
70 | sections := make([]string, 0)
71 | reader, err := pdf.NewReader(bytes.NewReader(binary), int64(len(binary)))
72 | if err != nil {
73 | return nil, err
74 | }
75 |
76 | for i := 1; i <= reader.NumPage(); i++ {
77 | page := reader.Page(i)
78 | for _, txt := range page.Content().Text {
79 | // fmt.Println(txt.Font, txt.S)
80 | _ = txt
81 | }
82 | // content, _ := reader.Page(i).GetPlainText(nil)
83 | content, _ := page.GetPlainText(nil)
84 | sections = append(sections, strings.TrimSpecial(content))
85 | }
86 | return sections, nil
87 | }
88 |
--------------------------------------------------------------------------------