llm_conductor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LlmConductor
4
+ # Collection of pre-built prompt templates for common LLM tasks including
5
+ # content analysis, link extraction, and data summarization.
6
+ module Prompts
7
+ def prompt_featured_links(data)
8
+ <<~PROMPT
9
+ You are an AI assistant tasked with analyzing a webpage's HTML content to extract the most valuable links. Your goal is to identify links related to features, products, solutions, pricing, and social media profiles, prioritizing those from the same domain as the current page. Here are your instructions:
10
+
11
+ - You will be provided with the HTML content of the current page in the following format:
12
+ <page_html>
13
+ #{data[:htmls]}
14
+ </page_html>
15
+
16
+ - Parse the HTML content and extract all hyperlinks (a href attributes). Pay special attention to links in the navigation menu, footer, and main content areas.
17
+
18
+ - Filter and prioritize the extracted links based on the following criteria:
19
+ a. The link must be from the same domain as the current URL.
20
+ b. Prioritize links containing keywords such as "features", "products", "solutions", "pricing", "about", "contact", or similar variations.
21
+ c. Include social media profile links (e.g., LinkedIn, Instagram, Twitter, Facebook) if available.
22
+ d. Exclude links to login pages, search pages, or other utility pages.
23
+
24
+ - Select the top 3 most valuable links based on the above criteria.
25
+
26
+ - Format your output as a JSON array of strings, where each string is a full URL. Use the following format:
27
+ <output_format>
28
+ ["https://example.com/about-us", "https://example.com/products", "https://example.com/services"]
29
+ </output_format>
30
+
31
+ - The links must be the same domain of following
32
+ <domain>
33
+ #{data[:current_url]}
34
+ </domain>
35
+
36
+ If fewer than 3 relevant links are found, include only the available links in the output array.
37
+
38
+ Remember to use the full URL for each link, including the domain name. If you encounter relative URLs, combine them with the domain from the current URL to create absolute URLs.
39
+
40
+ Provide your final output without any additional explanation or commentary.
41
+ PROMPT
42
+ end
43
+
44
+ def prompt_summarize_htmls(data)
45
+ <<~PROMPT
46
+ Extract useful information from the webpage including a domain, detailed description of what the company does, founding year, country, business model, product description and features, customers and partners, development stage, and social media links. output will be json
47
+
48
+ You are tasked with extracting useful information about a company from a given webpage content. Your goal is to analyze the content and extract specific details about the company, its products, and its operations.
49
+
50
+ You will be provided with raw HTML content in the following format:
51
+
52
+ <html_content>
53
+ #{data[:htmls]}
54
+ </html_content>
55
+
56
+ Carefully read through the webpage content and extract the following information about the company:
57
+
58
+ - Name(field name): The company's name
59
+ - Domain name(field domain_name): The company's domain
60
+ - Description(field description): A comprehensive explanation of what the company does
61
+ - Country(field country): The company's country
62
+ - Region(field region): The company's region
63
+ - Location(field location): The company's location
64
+ - Founding on(field founded_on): Which year the company was established
65
+ - Business model(field business_model): How the company generates revenue
66
+ - Product description(product_description): A brief overview of the company's main product(s) or service(s)
67
+ - Product features(product_features): Key features or capabilities of the product(s) or service(s)
68
+ - Customers and partners(field customers_and_partners): Notable clients or business partners
69
+ - Development stage(field development_stage): The current phase of the company (e.g., startup, growth, established)
70
+ - Social media links(field social_media_links): URLs to the company's social media profiles
71
+ - instagram_url
72
+ - linkedin_url
73
+ - twitter_url
74
+
75
+ If any of the above information is not available in the webpage content, use "Not available" as the value for that field.
76
+
77
+ Present your findings in a JSON format. Here's an example of the expected structure:
78
+
79
+ <output_format>
80
+ {
81
+ "name": "AI-powered customer service",
82
+ "domain_name": "example.com",
83
+ "description": "XYZ Company develops AI chatbots that help businesses automate customer support...",
84
+ "founding_on": 2018,
85
+ "country": "United States",
86
+ "Region": "SA",
87
+ "Location": "SFO",
88
+ "business_model": "SaaS subscription",
89
+ "product_description": "AI-powered chatbot platform for customer service automation",
90
+ "product_features": ["Natural language processing", "Multi-language support", "Integration with CRM systems"],
91
+ "customers_and_partners": ["ABC Corp", "123 Industries", "Big Tech Co."],
92
+ "development_stage": "Growth",
93
+ "social_media_links": {
94
+ "linkedin_url": "https://www.linkedin.com/company/xyzcompany",
95
+ "twitter_url": "https://twitter.com/xyzcompany",
96
+ "instagram_url": "https://www.instagram.com/xyzcompany"
97
+ }
98
+ }
99
+ </output_format>
100
+
101
+ Remember to use only the information provided in the webpage content. Do not include any external information or make assumptions beyond what is explicitly stated or strongly implied in the given content.
102
+
103
+ Present your final output in JSON format, enclosed within <json_output> tags.
104
+ PROMPT
105
+ end
106
+
107
+ def prompt_summarize_description(data)
108
+ <<~PROMPT
109
+ Given the company's name, domain, description, and a list of industry-related keywords,
110
+ please summarize the company's core business and identify the three most relevant industries.
111
+ Highlight the company's unique value proposition, its primary market focus,
112
+ and any distinguishing features that set it apart within the identified industries.
113
+ Be as objective as possible.
114
+
115
+ Name: #{data[:name]}
116
+ Domain Name: #{data[:domain_name]}
117
+ Industry: #{data[:industries]}
118
+ Description: #{data[:description]}
119
+ PROMPT
120
+ end
121
+
122
+ def prompt_custom(data)
123
+ template = data.fetch(:template)
124
+ template % data
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LlmConductor
4
+ # Response object that encapsulates the result of LLM generation
5
+ # with metadata like token usage and cost information
6
+ class Response
7
+ attr_reader :output, :input_tokens, :output_tokens, :metadata, :model
8
+
9
+ def initialize(output:, model:, input_tokens: nil, output_tokens: nil, metadata: {})
10
+ @output = output
11
+ @model = model
12
+ @input_tokens = input_tokens
13
+ @output_tokens = output_tokens
14
+ @metadata = metadata || {}
15
+ end
16
+
17
+ def total_tokens
18
+ (@input_tokens || 0) + (@output_tokens || 0)
19
+ end
20
+
21
+ # Calculate estimated cost based on model and token usage
22
+ def estimated_cost
23
+ return nil unless valid_for_cost_calculation?
24
+
25
+ pricing = model_pricing
26
+ return nil unless pricing
27
+
28
+ calculate_cost(pricing[:input_rate], pricing[:output_rate])
29
+ end
30
+
31
+ # Check if the response was successful
32
+ def success?
33
+ !@output.nil? && !@output.empty? && @metadata[:error].nil?
34
+ end
35
+
36
+ # Get metadata with cost included if available
37
+ def metadata_with_cost
38
+ cost = estimated_cost
39
+ cost ? @metadata.merge(cost:) : @metadata
40
+ end
41
+
42
+ # Parse JSON from the output
43
+ def parse_json
44
+ return nil unless success? && @output
45
+
46
+ JSON.parse(@output.strip)
47
+ rescue JSON::ParserError => e
48
+ raise JSON::ParserError, "Failed to parse JSON response: #{e.message}"
49
+ end
50
+
51
+ # Extract text between code blocks
52
+ def extract_code_block(language = nil)
53
+ return nil unless @output
54
+
55
+ pattern = if language
56
+ /```#{Regexp.escape(language)}\s*(.*?)```/m
57
+ else
58
+ /```(?:\w*)\s*(.*?)```/m
59
+ end
60
+
61
+ match = @output.match(pattern)
62
+ match ? match[1].strip : nil
63
+ end
64
+
65
+ private
66
+
67
+ def valid_for_cost_calculation?
68
+ @model && total_tokens.positive?
69
+ end
70
+
71
+ def model_pricing
72
+ case @model
73
+ when /gpt-3\.5-turbo/
74
+ { input_rate: 0.0000015, output_rate: 0.000002 }
75
+ when /gpt-4o-mini/
76
+ { input_rate: 0.000000150, output_rate: 0.0000006 }
77
+ when /gpt-4/
78
+ { input_rate: 0.00003, output_rate: 0.00006 }
79
+ end
80
+ end
81
+
82
+ def calculate_cost(input_rate, output_rate)
83
+ (@input_tokens || 0) * input_rate + (@output_tokens || 0) * output_rate
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LlmConductor
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'llm_conductor/version'
4
+ require_relative 'llm_conductor/configuration'
5
+ require_relative 'llm_conductor/response'
6
+ require_relative 'llm_conductor/data_builder'
7
+ require_relative 'llm_conductor/prompts'
8
+ require_relative 'llm_conductor/prompts/base_prompt'
9
+ require_relative 'llm_conductor/prompt_manager'
10
+ require_relative 'llm_conductor/clients/base_client'
11
+ require_relative 'llm_conductor/clients/gpt_client'
12
+ require_relative 'llm_conductor/clients/ollama_client'
13
+ require_relative 'llm_conductor/clients/openrouter_client'
14
+ require_relative 'llm_conductor/client_factory'
15
+
16
+ # LLM Conductor provides a unified interface for multiple Language Model providers
17
+ # including OpenAI GPT, OpenRouter, and Ollama with built-in prompt templates,
18
+ # token counting, and extensible client architecture.
19
+ module LlmConductor
20
+ class Error < StandardError; end
21
+
22
+ # Main entry point for creating LLM clients
23
+ def self.build_client(model:, type:, vendor: nil)
24
+ ClientFactory.build(model:, type:, vendor:)
25
+ end
26
+
27
+ # Unified generate method supporting both simple prompts and legacy template-based generation
28
+ def self.generate(model: nil, prompt: nil, type: nil, data: nil, vendor: nil)
29
+ if prompt && !type && !data
30
+ generate_simple_prompt(model:, prompt:, vendor:)
31
+ elsif type && data && !prompt
32
+ generate_with_template(model:, type:, data:, vendor:)
33
+ else
34
+ raise ArgumentError,
35
+ "Invalid arguments. Use either: generate(prompt: 'text') or generate(type: :custom, data: {...})"
36
+ end
37
+ end
38
+
39
+ class << self
40
+ private
41
+
42
+ def generate_simple_prompt(model:, prompt:, vendor:)
43
+ model ||= configuration.default_model
44
+ vendor ||= ClientFactory.determine_vendor(model)
45
+ client_class = client_class_for_vendor(vendor)
46
+ client = client_class.new(model:, type: :direct)
47
+ client.generate_simple(prompt:)
48
+ end
49
+
50
+ def generate_with_template(model:, type:, data:, vendor:)
51
+ client = build_client(model:, type:, vendor:)
52
+ client.generate(data:)
53
+ end
54
+
55
+ def client_class_for_vendor(vendor)
56
+ case vendor
57
+ when :openai, :gpt then Clients::GptClient
58
+ when :openrouter then Clients::OpenrouterClient
59
+ when :ollama then Clients::OllamaClient
60
+ else
61
+ raise ArgumentError, "Unsupported vendor: #{vendor}. Supported vendors: openai, openrouter, ollama"
62
+ end
63
+ end
64
+ end
65
+
66
+ # List of supported vendors
67
+ SUPPORTED_VENDORS = %i[openai openrouter ollama].freeze
68
+
69
+ # List of supported prompt types
70
+ SUPPORTED_PROMPT_TYPES = %i[
71
+ featured_links
72
+ summarize_htmls
73
+ summarize_description
74
+ custom
75
+ ].freeze
76
+ end
@@ -0,0 +1,4 @@
1
+ module LlmConductor
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: llm_conductor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Zheng
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-09-19 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ollama-ai
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
40
+ - !ruby/object:Gem::Dependency
41
+ name: ruby-openai
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '7.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '7.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: tiktoken_ruby
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.0.7
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.0.7
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-performance
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.19'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.19'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop-rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.0'
96
+ description: LLM Conductor provides a clean, unified interface for working with multiple
97
+ Language Model providers including OpenAI GPT, OpenRouter, and Ollama. Features
98
+ include prompt templating, token counting, and extensible client architecture.
99
+ email:
100
+ - ben@ekohe.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".DS_Store"
106
+ - ".rspec"
107
+ - ".rubocop.yml"
108
+ - ".rubocop_todo.yml"
109
+ - ".ruby-version"
110
+ - README.md
111
+ - Rakefile
112
+ - config/initializers/llm_conductor.rb
113
+ - examples/data_builder_usage.rb
114
+ - examples/prompt_registration.rb
115
+ - examples/rag_usage.rb
116
+ - examples/simple_usage.rb
117
+ - lib/llm_conductor.rb
118
+ - lib/llm_conductor/client_factory.rb
119
+ - lib/llm_conductor/clients/base_client.rb
120
+ - lib/llm_conductor/clients/gpt_client.rb
121
+ - lib/llm_conductor/clients/ollama_client.rb
122
+ - lib/llm_conductor/clients/openrouter_client.rb
123
+ - lib/llm_conductor/configuration.rb
124
+ - lib/llm_conductor/data_builder.rb
125
+ - lib/llm_conductor/prompt_manager.rb
126
+ - lib/llm_conductor/prompts.rb
127
+ - lib/llm_conductor/prompts/base_prompt.rb
128
+ - lib/llm_conductor/response.rb
129
+ - lib/llm_conductor/version.rb
130
+ - sig/llm_conductor.rbs
131
+ homepage: https://github.com/ekohe/llm_conductor
132
+ licenses: []
133
+ metadata:
134
+ allowed_push_host: https://rubygems.org
135
+ homepage_uri: https://github.com/ekohe/llm_conductor
136
+ source_code_uri: https://github.com/ekohe/llm_conductor
137
+ changelog_uri: https://github.com/ekohe/llm_conductor/blob/main/CHANGELOG.md
138
+ rubygems_mfa_required: 'true'
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: 3.1.0
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubygems_version: 3.6.2
154
+ specification_version: 4
155
+ summary: A flexible Ruby gem for orchestrating multiple LLM providers with unified
156
+ interface
157
+ test_files: []