Local chatbot testing.

This commit is contained in:
2025-11-24 22:27:47 +00:00
parent 457abd23e7
commit 8a4db1b7f9
6 changed files with 1536 additions and 1 deletions

View File

@@ -1 +1,2 @@
transformers
transformers
requests >= 2.25.1

42
test/advanced_example.py Normal file
View File

@@ -0,0 +1,42 @@
# advanced_example.py
from ollama_deepseek_client import OllamaDeepSeekClient
def advanced_example():
client = OllamaDeepSeekClient()
# Example 1: Code generation
code_prompt = "Write a Python function to calculate fibonacci numbers"
print("Code Generation Example:")
code_response = client.generate_response(
code_prompt,
temperature=0.3, # Lower temperature for more deterministic code
max_tokens=1000
)
print(code_response)
print("\n" + "="*50 + "\n")
# Example 2: Streaming response
print("Streaming Response Example:")
stream_response = client.generate_response(
"Explain quantum computing in simple terms",
stream=True,
temperature=0.8
)
print("\n" + "="*50 + "\n")
# Example 3: Chat with context
conversation = []
messages = [
"What's the capital of France?",
"Tell me more about its history",
"What are the main tourist attractions there?"
]
for msg in messages:
print(f"You: {msg}")
response, conversation = client.chat(msg, conversation)
print(f"Assistant: {response}\n")
if __name__ == "__main__":
advanced_example()

View File

@@ -0,0 +1,697 @@
# UK Sole Trader Business Plan - TCG & Tabletop Games Retail + Content Creation
**Business Name:** [Your Business Name]
**Business Type:** Sole Trader
**Registration:** [Registration Number]
**Bank Account:** Starling Business Account
**Date:** November 2025
**Planning Horizon:** 5 Years
---
## Executive Summary
Multi-channel TCG and tabletop games business combining retail trading with content creation. Primary focus on buying sealed product at wholesale prices and reselling at market rates, supported by YouTube/social content that drives customer acquisition and engagement.
**Core Strategy:** Use content creation as marketing engine for retail trading business, creating symbiotic relationship where content drives sales and trading funds increasingly premium content.
**Target Market:** UK TCG players and collectors (Magic: The Gathering, Pokémon, Yu-Gi-Oh, One Piece, Dragon Ball Super) and tabletop miniature gamers (Warhammer, Conquest).
**Key Differentiator:** Technical capabilities (software engineering background) enable professional e-commerce platform and superior customer experience compared to typical small retailers.
---
## Business Model
### Revenue Streams (Priority Order)
**1. Sealed Product Trading (Primary - 70% of revenue)**
- Purchase sealed booster boxes, ETBs, decks at wholesale (30-40% below RRP)
- Hold 6-18 months for market appreciation
- Sell via own website, CardMarket, Facebook Marketplace
- Target margins: 40-80% on successful picks
- Rolling inventory model (buy, hold, sell continuously)
**2. Singles Sales (Secondary - 20% of revenue)**
- Singles from pack openings (content creation)
- Purchased bulk singles for resale
- Listed on CardMarket primarily
- Target margins: 30-60%
**3. Content Monetization (Tertiary - 5% of revenue)**
- YouTube ad revenue (once monetized: 1K subs, 4K watch hours)
- Affiliate links (TCGPlayer, Amazon, CardMarket)
- Potential sponsorships (Year 3+)
**4. Painted Miniatures/Commission Work (Future - 5% of revenue)**
- Painted miniatures from content
- Potential commission work (Year 2+)
- Premium pricing for quality work
---
## Market Analysis
### Target Customer Segments
**1. Budget-Conscious Players (40% of customers)**
- Want sealed product below RRP
- Price-sensitive
- Online purchasers
- Compete on price + service
**2. Content Audience (30% of customers)**
- Watch YouTube content
- Buy featured products
- Less price-sensitive (value relationship)
- Higher lifetime value
**3. Collectors/Investors (20% of customers)**
- Looking for specific sealed products
- Out-of-print sets
- Will pay premium for availability
- High-value transactions
**4. Local Gamers (10% of customers)**
- Facebook Marketplace sales
- Cash transactions
- Immediate pickup
- Relationship-based
### Competitive Landscape
**Online Retailers:**
- Magic Madhouse, Chaos Cards, Axion Now (established, better pricing initially)
- eBay power sellers (high fees, poor customer experience)
- CardMarket sellers (competitive on singles)
**Local Game Stores:**
- Higher prices (overhead costs)
- Offer play space and community (advantage)
- Immediate availability
- Not direct competition (different value proposition)
**Our Advantages:**
- Lower overhead (home-based, no storefront)
- Content creates differentiation and customer loyalty
- Technical skills enable professional platform
- Flexible pricing (can undercut when needed)
**Our Disadvantages:**
- No existing customer base (Year 1)
- No WPN status initially (can't host official MTG events)
- No physical play space
- Unknown brand
---
## Financial Projections
### Year 1: Foundation & Validation (Months 1-12)
**Purchases:**
- Content products: £300-500/month (opened for videos)
- Trading stock: £700-1,000/month (sealed product for resale)
- **Total: £1,000-1,500/month = £12,000-18,000/year**
**Product Mix:**
- MTG: 40% (£400-600/month)
- Pokémon: 38% (£380-570/month)
- Yu-Gi-Oh: 22% (£220-330/month)
**Revenue:**
- Q1 (Months 1-3): £300-800 (minimal, inventory building)
- Q2 (Months 4-6): £1,200-2,500 (first sales emerging)
- Q3 (Months 7-9): £2,000-3,500 (6-month-old stock maturing)
- Q4 (Months 10-12): £3,000-5,000 (accelerating)
- **Total Year 1: £6,500-11,800**
**Expenses:**
- Stock purchases: £12,000-18,000
- Shipping supplies: £300-500
- Platform fees (CardMarket, PayPal): £300-600
- Hosting/domain: £100-200
- Insurance: £200-300
- Misc (supplies, postage): £400-600
- **Total: £13,300-20,200**
**Net Loss Year 1: £6,500-13,400**
- Offset against other sole trader income (contract work)
- Tax relief at 20-40% = £1,300-5,360 effective cost reduction
**Key Milestones:**
- Month 1: Website launched, distributor accounts approved
- Month 3: First 20 sales completed, CardMarket reputation established
- Month 6: Regular sales velocity, 100+ YouTube subscribers
- Month 9: £1,000+ monthly revenue achieved
- Month 12: Clear data on what sells, refine strategy for Year 2
---
### Year 2: Growth & Optimization (Months 13-24)
**Purchases:**
- Content products: £500-800/month (better products, more frequent content)
- Trading stock: £1,000-1,500/month (focused on proven sellers)
- **Total: £1,500-2,300/month = £18,000-27,600/year**
**Revenue:**
- Q1: £5,000-8,000 (Year 1 stock fully maturing)
- Q2: £6,000-10,000
- Q3: £7,000-12,000
- Q4: £8,000-14,000
- **Total Year 2: £26,000-44,000**
**Expenses:**
- Stock purchases: £18,000-27,600
- Operating costs: £1,200-2,000
- **Total: £19,200-29,600**
**Net Profit/(Loss) Year 2: £6,800 profit to (£3,600) loss**
- Approaching break-even or modest profit
- Significantly improved from Year 1
**Key Milestones:**
- Month 15: YouTube monetization achieved (1K subs, 4K hours)
- Month 18: £2,000+ monthly revenue consistently
- Month 20: First profitable month
- Month 24: Established customer base, 500+ subs, proven model
---
### Year 3: Profitability & Scaling (Months 25-36)
**Purchases:**
- Content products: £800-1,200/month (premium content, cEDH decks, collector products)
- Trading stock: £1,500-2,000/month (steady-state inventory management)
- **Total: £2,300-3,200/month = £27,600-38,400/year**
**Revenue:**
- Q1: £10,000-16,000
- Q2: £11,000-18,000
- Q3: £12,000-20,000
- Q4: £13,000-22,000
- **Total Year 3: £46,000-76,000**
**Expenses:**
- Stock purchases: £27,600-38,400
- Operating costs: £2,000-3,000
- **Total: £29,600-41,400**
**Net Profit Year 3: £16,400-34,600**
- Now solidly profitable
- Can reinvest into content quality
- Consider reducing contract work hours
**Key Milestones:**
- Month 28: £3,000+ monthly revenue consistently
- Month 30: 1,000-2,000 YouTube subscribers
- Month 32: First sponsorship/collaboration opportunity
- Month 36: Business generates £25-35k profit, could sustain full-time
---
### Years 4-5: Maturity & Potential Full-Time Transition
**Target Revenue: £60,000-100,000/year**
**Revenue Mix:**
- Product sales: £50-75k (70-75%)
- YouTube ads: £5-10k (8-10%)
- Affiliates: £3-8k (5-8%)
- Sponsorships: £2-7k (3-7%)
**Net Profit Target: £25,000-45,000/year**
**Effective Hourly Rate: £30-50/hour** (40-50 hours/week on business)
- Competitive with or exceeding contract work
- With ownership, control, and work variety
---
## Operational Plan
### Time Allocation (Weekly)
**Year 1: 25-35 hours/week on business**
**Content Creation (12-16 hours):**
- Filming (4-6 hours): Pack openings, deck building, gameplay
- Editing (4-6 hours): Video editing, thumbnails, titles
- Planning (2-3 hours): Content calendar, research, scripting
- Publishing (1-2 hours): Upload, descriptions, social media
**Trading Operations (10-15 hours):**
- Sourcing/ordering (2-3 hours): Research deals, place orders
- Inventory management (2-3 hours): Receiving, photographing, cataloging
- Listing products (3-5 hours): CardMarket, website updates, descriptions
- Order processing (2-3 hours): Packing, shipping, tracking
- Customer service (1-2 hours): Emails, inquiries, issues
**Business Admin (3-4 hours):**
- Bookkeeping (1-2 hours): Track expenses, sales, reconciliation
- Strategy/planning (1-2 hours): Analyze what's working, adjust approach
- Platform maintenance (1 hour): Website updates, SEO
**Plus: Contract Work 20-30 hours/week**
- Total: 45-65 hours/week
- Provides work variety (goal achieved)
- Financial safety net maintained
---
### Content Strategy
**Year 1: Foundation Content (2-3 videos/month)**
**Content Types:**
- Pack openings (50%): Current sets, features products being sold
- Deck building (30%): Budget to mid-range decks, gameplay
- Miniature painting (20%): Basic tutorials, army building
**Goals:**
- Establish upload consistency
- Learn editing and production
- Build initial audience (500-1,000 subscribers)
- Drive traffic to store
**Year 2: Quality Improvement (3-4 videos/month)**
**Content Types:**
- Premium pack openings (40%): Collector boosters, special sets
- Advanced deck techs (35%): Competitive builds, cEDH primers
- Painting & battle reports (25%): Better production, full armies
**Goals:**
- Reach monetization (1K subs, 4K watch hours)
- Improve production quality
- Grow to 1,500-3,000 subscribers
- Establish content niche/voice
**Year 3+: Premium Content (4-5 videos/month or weekly)**
**Content Types:**
- High-end openings (35%): Cases, chase products, premium items
- Competitive content (35%): Tournament reports, meta analysis, cEDH
- Advanced painting/hobby (30%): Full armies, advanced techniques, collaborations
**Goals:**
- 3,000-10,000+ subscribers
- Regular views (1,000-5,000+ per video)
- Sponsorship opportunities
- Community building (Discord, etc.)
---
### Product Sourcing Strategy
**Primary Distributor: Asmodee UK**
- Carries all main TCG lines (MTG, Pokémon, YGO, One Piece, Dragon Ball)
- Expected discounts: 30-40% off RRP
- Minimum orders: £150-300
- Application process: Professional website + business registration
**Secondary: Magic Madhouse Wholesale or Lion Rampant**
- Backup if Asmodee declines initially
- Similar product range
- Slightly lower discounts but more accessible
**Future: Games Workshop Trade (Year 2+)**
- For Warhammer products
- 40-50% discounts
- Requires established retail presence
- Apply once TCG business proven
**Purchasing Criteria:**
- Buy below RRP (target 25-35% discount minimum)
- Focus on sets approaching end of print run
- Diversify across MTG, Pokémon, YGO (reduce risk)
- Balance quick flips (6 months) with longer holds (12-18 months)
**Product Selection (Year 1):**
- 40% quick movers (sell within 6 months, prove trading activity)
- 40% medium holds (6-12 months appreciation)
- 20% long-term plays (12-24 months, high conviction picks)
---
### Sales Channels
**1. Own Website (Primary for sealed product)**
- Professional e-commerce site (built using technical skills)
- No marketplace fees (maximize margins)
- Brand control
- Customer data ownership
- Direct YouTube traffic here
**2. CardMarket (Primary for singles)**
- Established TCG marketplace
- Built-in buyer base
- 5% + fees (acceptable for singles)
- Build seller reputation
- High volume potential
**3. Facebook Marketplace (Local sales)**
- No fees
- Cash transactions
- Immediate pickup
- Local customer base
- Quick inventory movement when needed
**4. Social Media (Traffic driver)**
- YouTube (primary): Content drives store traffic
- Instagram: Product photos, short clips
- TikTok: Short-form content (optional Year 2+)
- Facebook: Community, local sales
---
### Inventory Management
**Target Steady-State Inventory: £10,000-15,000**
- Not endlessly growing (key for HMRC defensibility)
- Working capital stabilizes Year 2+
- Mix of ages: fresh stock to 18-month holds
**Inventory Tracking:**
- Spreadsheet system (minimum):
- Product, purchase date, cost, listing date, target sell date
- Track age of inventory
- Flag slow movers (18+ months)
- Future: Inventory management software if scales
**Stock Mix by Age:**
- 0-6 months: 40% of inventory (fresh, building appreciation)
- 6-12 months: 35% of inventory (maturing, starting to sell)
- 12-18 months: 20% of inventory (peak appreciation period)
- 18+ months: 5% of inventory (slow movers, repricing needed)
**Decision Rules:**
- Products unsold after 18 months: reduce price 10-15%
- Products unsold after 24 months: aggressive pricing or bundle deals
- Accept some losses on bad picks (real business reality, strengthens HMRC case)
**Review Quarterly:**
- What's selling vs. not selling
- Margins achieved vs. targets
- Adjust purchasing strategy accordingly
- Document decisions (shows active management)
---
## Marketing & Customer Acquisition
**Primary: Content Marketing (YouTube)**
- Cost: Time investment (already budgeted)
- Best ROI: Audience becomes customers
- Build trust and authority
- "Available in our store" CTAs in videos
- Links in descriptions
**Secondary: SEO & Organic Search**
- Website optimized for product searches
- Blog content (deck guides, set reviews)
- Technical skills advantage (proper SEO implementation)
- Long-term traffic building
**Tertiary: Social Media**
- Instagram: Product photos, reels
- Facebook: Groups, marketplace, community
- TikTok: Optional, short-form content
**Community Building:**
- Discord server (Year 2+)
- Email list (capture from website)
- Customer engagement and retention
**Paid Advertising:**
- Not Year 1 (tight margins)
- Consider Year 2+ if profitable (Facebook/Google ads)
- Test small budgets (£50-100/month)
---
## Risk Management
### Key Risks & Mitigation
**Risk 1: Products Don't Appreciate as Expected**
- Mitigation: Diversify across games and sets
- Mitigation: Research historical appreciation patterns
- Mitigation: Accept some losses (10-20% of picks may fail)
- Mitigation: Adjust strategy based on actual results
**Risk 2: Can't Sell Inventory Fast Enough**
- Mitigation: Multiple sales channels (website, CardMarket, Facebook)
- Mitigation: Competitive pricing when needed
- Mitigation: Content drives traffic to store
- Mitigation: Start with smaller purchase volumes, scale with proven sales
**Risk 3: HMRC Challenge on Losses**
- Mitigation: Demonstrate genuine business activity (content, sales records)
- Mitigation: Professional operations (website, inventory management)
- Mitigation: Clear progression to profitability (Year 3)
- Mitigation: Active trading (continuous buying and selling)
- Mitigation: Proportionate losses to business stage
**Risk 4: Content Doesn't Build Audience**
- Mitigation: Business model doesn't depend on viral success
- Mitigation: Content supports trading (even small audience helps)
- Mitigation: Improve quality over time
- Mitigation: Study successful channels, learn
**Risk 5: Distributor Access Denied**
- Mitigation: Apply to multiple distributors
- Mitigation: Bootstrap with retail purchases initially
- Mitigation: Build track record, reapply in 3-6 months
- Mitigation: Alternative sourcing (bulk purchases, other retailers)
**Risk 6: Reprint Risk (Products Lose Value)**
- Mitigation: Diversify across products and games
- Mitigation: Focus on limited print runs
- Mitigation: Accept this is inherent risk in TCG trading
- Mitigation: Move inventory before reprint announcements when possible
**Risk 7: Time Commitment Too High**
- Mitigation: Start with contract work safety net (20-30 hours/week)
- Mitigation: Can reduce TCG business hours if needed
- Mitigation: Systematize operations over time
- Mitigation: Genuine interest in products sustains effort
---
## HMRC Compliance Strategy
### Demonstrating Genuine Business
**Active Trading Evidence:**
- Regular content uploads (2-3+ per month)
- Continuous sales activity across multiple platforms
- Professional website and operations
- Inventory management systems
- Sales records meticulously tracked
**Commercial Decision-Making:**
- Adjust strategy based on what sells vs. doesn't
- Sometimes sell at lower margins to move inventory
- Accept losses on poor picks (document reasoning)
- Quarterly reviews and strategy adjustments
- Price competitively based on market conditions
**Path to Profitability:**
- Year 1: £6.5-13.4k loss (building inventory, establishing business)
- Year 2: £6.8k profit to (£3.6k) loss (approaching break-even)
- Year 3: £16.4-34.6k profit (solidly profitable)
- Clear trajectory, not indefinite losses
**Documentation Maintained:**
- All purchase receipts and invoices
- Sales records (platform exports, manual tracking)
- Inventory spreadsheet (purchase date, cost, status)
- Content calendar and upload history
- Bank statements (separate business account)
- Quarterly profit/loss summaries
**Red Flags Avoided:**
- ✓ Not buying only products personally desired (diversified range)
- ✓ Not holding inventory indefinitely (6-18 month target, steady-state)
- ✓ Not minimal effort (25-35 hours/week, professional operations)
- ✓ Not just accumulating assets (active selling, growing revenue)
---
## Success Metrics & KPIs
### Year 1 Targets (Foundation)
- Revenue: £6,500-11,800
- Sales velocity: 50-70% of 6+ month old inventory sold
- YouTube subscribers: 500-1,000
- Video uploads: 24-36 (2-3 per month)
- Product listings: 100-200 active SKUs
- Customer reviews: 20+ positive reviews
- Break-even on individual products: 60-70% hit target margins
### Year 2 Targets (Growth)
- Revenue: £26,000-44,000
- Sales velocity: 70-80% of inventory selling within 18 months
- YouTube subscribers: 1,500-3,000 (monetization achieved)
- Video uploads: 36-48 (3-4 per month)
- Product listings: 150-300 active SKUs
- Repeat customers: 20-30%
- Profitability: Break-even to £10k profit
### Year 3 Targets (Profitability)
- Revenue: £46,000-76,000
- Profit: £16,400-34,600
- YouTube subscribers: 3,000-10,000+
- Video uploads: 48-60 (4-5 per month, approaching weekly)
- Sales channels: 3-4 active platforms
- Customer lifetime value: Growing
- Business sustainability: Can support full-time work if desired
### Long-Term Vision (Years 4-5)
- Revenue: £60,000-100,000+
- Profit: £25,000-45,000
- Multiple revenue streams: Products, ads, affiliates, sponsorships
- Established brand in UK TCG community
- Potential to hire help or expand operations
- True business ownership and income control
---
## Decision Points & Pivots
### 3-Month Review (First Quarter)
**Questions:**
- Are distributor accounts approved? If not, what's needed?
- Are sales happening at all? Any customer interest?
- Is content production sustainable at current pace?
- Are products selling faster or slower than expected?
**Potential Pivots:**
- If no distributor access: Bootstrap with retail purchases, reapply with track record
- If zero sales: Reassess pricing, improve marketing, more aggressive outreach
- If content too time-consuming: Reduce upload frequency, simplify production
- If specific games not moving: Shift allocation to what's selling
### 6-Month Review (Mid-Year 1)
**Questions:**
- Are we hitting £1,000+ monthly revenue?
- Which products/games are selling vs. sitting?
- Is audience growing on YouTube?
- Are margins hitting 30-40% targets?
- Is time commitment sustainable with contract work?
**Potential Pivots:**
- Reallocate budget to best-selling games
- Adjust content mix based on what resonates
- If overwhelmed: Reduce purchase volume temporarily
- If going well: Increase purchase budget slightly
### 12-Month Review (End Year 1)
**Questions:**
- Did we hit £6.5-11.8k revenue target?
- Is the business model working (can we sell what we buy)?
- Do we enjoy the work (variety goal achieved)?
- Is path to profitability credible?
- Should we continue, scale, or pivot?
**Go/No-Go Decision:**
- ✓ GO if: Revenue £6k+, regular sales, enjoyed the work, clear path forward
- ⚠ ADJUST if: Revenue £3-6k, mixed results, need to refine approach
- ✗ STOP if: Revenue <£3k, hate the work, no evidence model works
### 24-Month Review (End Year 2)
**Major Assessment:**
- Are we profitable or close?
- Is revenue £25k+?
- Is YouTube monetized?
- Can we see path to £40k+ profit (replacement income)?
**Decision: Scale or Maintain**
- Scale: Reduce contract hours, go bigger on TCG business
- Maintain: Keep hybrid model, profitable side business
- Exit: Cut losses, learned valuable lessons
---
## Personal Goals Alignment
### Work Variety (Primary Goal)
- ✓ Mix of coding (contract work) + content creation + retail operations
- ✓ Different activities daily: filming, editing, deck building, shipping, strategy
- ✓ Creative work (content) + analytical work (trading) + technical work (platform)
- ✓ Engaging with hobby/interest professionally
### Ownership & Control (Primary Goal)
- ✓ Build owned asset (not dependent on employer)
- ✓ Control over work schedule and priorities
- ✓ Decision-making authority
- ✓ Potential for uncapped income (vs. salary/rate ceiling)
- ✓ Can sell business eventually if desired
### Income Goals (Secondary)
- Year 1-2: Contract work (£36-43k) + TCG losses, net lower
- Year 3: Contract work (£21-29k) + TCG profit (£16-35k) = £37-64k
- Year 4-5: TCG primary (£25-45k+), minimal/no contract work
- Long-term: Match or exceed £30/hour effective rate with ownership benefits
### Remote Work Constraint (Reality)
- ✓ Entire business operates from home (compatible with constraint)
- ✓ Content filmed at home
- ✓ Storage at home
- ✓ No office requirement
- ✓ Works around remote contract work
- ✓ Builds career option outside traditional employment path
### TCG Interest (Sustainability)
- ✓ Genuine interest in products sustains effort through difficult periods
- ✓ Content creation enjoyable (not just means to end)
- ✓ Community engagement rewarding
- ✓ Learning and improving skills (deck building, game knowledge, painting)
- ✓ Passion + business = sustainable long-term
---
## Conclusion
This business plan represents a realistic 3-5 year path to building a profitable TCG retail and content business that provides:
- Work variety and ownership (primary goals)
- Potential £25-45k+ annual profit by Years 4-5
- Flexibility and control over work life
- Engagement with genuine interest area
- Alternative career path given remote-only constraint
The model is financially viable if executed well:
- Proven wholesale → retail arbitrage opportunity
- Content marketing provides differentiation
- Technical skills enable competitive advantage
- Diversified revenue streams reduce risk
- Conservative projections with upside potential
Key success factors:
- Actually selling products (not just buying)
- Consistent content creation
- Professional operations
- Patience for 3-5 year timeline
- Willingness to adjust based on results
- Maintaining contract work safety net during build phase
This is not a get-rich-quick scheme. It's a legitimate business requiring significant work, capital, and time. But for someone with remote career limitations, genuine TCG interest, technical skills, and desire for ownership, this represents one of the better available paths forward.
**Recommendation: Proceed with Year 1 plan as outlined. Review quarterly. Adjust based on results. Maintain realistic expectations and contract work safety net.**
---
**Next Steps (Week 1-4):**
1. Build professional website (leverage software engineering skills)
2. List current singles inventory on CardMarket
3. Apply to Asmodee UK, Lion Rampant, Magic Madhouse for trade accounts
4. Set up inventory tracking spreadsheet
5. Plan first 3 months of content (content calendar)
6. Make first stock purchases (whether distributor or retail sources)
7. Film and publish first 2-3 videos
8. Establish business routines and time allocation with contract work
**The journey begins. Execute, learn, adjust, grow.**

11
test/clean.sh Executable file
View File

@@ -0,0 +1,11 @@
# Clean up any existing containers
docker stop ollama-deepseek ollama 2>/dev/null
docker rm ollama-deepseek ollama 2>/dev/null
# Check what's using port 11434
sudo netstat -tulpn | grep 11434
# If something else is using it, stop that process or use:
sudo lsof -i :11434
# Kill the process if it's not needed, or use a different port

View File

@@ -0,0 +1,760 @@
#!/usr/bin/env python3
"""
Ollama DeepSeek V2 Lite Client with improved Docker management
"""
import requests
import json
import time
import subprocess
import sys
import socket
import os
import glob
#!/usr/bin/env python3
"""
Ollama DeepSeek V2 Lite Client - Fixed Version
"""
import requests
import json
import time
import subprocess
import sys
import socket
class OllamaDeepSeekClient:
def __init__(self, base_url="http://localhost:11434"):
self.base_url = base_url
self.model = "deepseek-coder:6.7b"
self.container_name = "ollama-deepseek"
def is_port_in_use(self, port):
"""Check if a port is already in use"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(('localhost', port)) == 0
def check_ollama_running(self):
"""Check if Ollama is running and accessible"""
try:
response = requests.get(f"{self.base_url}/api/tags", timeout=10)
return response.status_code == 200
except requests.exceptions.ConnectionError:
return False
def check_container_exists(self):
"""Check if the container exists (running or stopped)"""
try:
result = subprocess.run([
"docker", "ps", "-a", "--filter", f"name={self.container_name}",
"--format", "{{.Names}}"
], capture_output=True, text=True, check=True)
return self.container_name in result.stdout
except subprocess.CalledProcessError:
return False
def check_container_running(self):
"""Check if the container is running"""
try:
result = subprocess.run([
"docker", "ps", "--filter", f"name={self.container_name}",
"--format", "{{.Names}}"
], capture_output=True, text=True, check=True)
return self.container_name in result.stdout
except subprocess.CalledProcessError:
return False
def setup_ollama(self):
"""Set up Ollama container and pull model"""
print("Setting up Ollama Docker container...")
# Check if port is in use
if self.is_port_in_use(11434):
print("❌ Port 11434 is in use. Checking if it's our container...")
if not self.check_container_running():
print("Another process is using port 11434. Please free the port or use a different one.")
return False
try:
# Remove any existing container
subprocess.run([
"docker", "rm", "-f", self.container_name
], capture_output=True, stderr=subprocess.DEVNULL)
# Create data directory
subprocess.run([
"mkdir", "-p", "~/ollama_data"
], check=True)
# Run new container
print("🚀 Starting Ollama container...")
subprocess.run([
"docker", "run", "-d",
"--name", self.container_name,
"-p", "11434:11434",
"-v", "~/ollama_data:/root/.ollama",
"--restart", "unless-stopped",
"ollama/ollama"
], check=True)
print("⏳ Waiting for Ollama to initialize...")
for i in range(30): # Wait up to 30 seconds
time.sleep(1)
try:
response = requests.get(f"{self.base_url}/api/tags", timeout=5)
if response.status_code == 200:
break
except:
if i == 29:
print("❌ Ollama failed to start in time")
return False
continue
print("📥 Pulling DeepSeek model (this may take a few minutes)...")
result = subprocess.run([
"docker", "exec", self.container_name,
"ollama", "pull", self.model
], capture_output=True, text=True)
if result.returncode == 0:
print("✅ Setup completed successfully!")
return True
else:
print(f"❌ Failed to pull model: {result.stderr}")
return False
except subprocess.CalledProcessError as e:
print(f"❌ Docker command failed: {e}")
return False
def start_ollama(self):
"""Start the existing Ollama container"""
try:
print("🔧 Starting existing container...")
subprocess.run([
"docker", "start", self.container_name
], check=True)
print("⏳ Waiting for Ollama to be ready...")
for i in range(15): # Wait up to 15 seconds
time.sleep(1)
if self.check_ollama_running():
print("✅ Ollama is ready!")
return True
print("❌ Ollama didn't become ready in time")
return False
except subprocess.CalledProcessError as e:
print(f"❌ Failed to start container: {e}")
return False
def get_system_info(self):
"""Get system and container information"""
info = {
'port_11434_used': self.is_port_in_use(11434),
'container_exists': self.check_container_exists(),
'container_running': self.check_container_running(),
'ollama_accessible': self.check_ollama_running()
}
return info
def list_models(self):
"""List available models"""
try:
response = requests.get(f"{self.base_url}/api/tags")
if response.status_code == 200:
models = response.json()
print("\n📚 Available models:")
for model in models.get('models', []):
print(f" - {model['name']}")
return True
else:
print("❌ Failed to list models")
return False
except requests.exceptions.RequestException as e:
print(f"❌ Error listing models: {e}")
return False
def generate_response(self, prompt, stream=False, **kwargs):
"""Generate response from DeepSeek model"""
if not self.check_ollama_running():
print("❌ Ollama is not running")
return None
url = f"{self.base_url}/api/generate"
payload = {
"model": self.model,
"prompt": prompt,
"stream": stream,
"options": {
"temperature": kwargs.get('temperature', 0.7),
"top_p": kwargs.get('top_p', 0.9),
"top_k": kwargs.get('top_k', 40),
"num_predict": kwargs.get('max_tokens', 2048)
}
}
try:
if stream:
return self._stream_response(url, payload)
else:
return self._generate_complete(url, payload)
except requests.exceptions.RequestException as e:
print(f"❌ Error generating response: {e}")
return None
def _generate_complete(self, url, payload):
"""Generate complete response"""
response = requests.post(url, json=payload, timeout=120)
if response.status_code == 200:
result = response.json()
return result.get('response', '')
else:
print(f"❌ API Error: {response.status_code}")
return None
def _stream_response(self, url, payload):
"""Stream the response"""
response = requests.post(url, json=payload, stream=True, timeout=120)
if response.status_code == 200:
full_response = ""
for line in response.iter_lines():
if line:
try:
data = json.loads(line)
if 'response' in data:
chunk = data['response']
print(chunk, end='', flush=True)
full_response += chunk
if data.get('done', False):
print()
break
except json.JSONDecodeError:
continue
return full_response
else:
print(f"❌ API Error: {response.status_code}")
return None
def chat(self, message, conversation_history=None):
"""Chat interface"""
if conversation_history is None:
conversation_history = []
conversation_history.append({"role": "user", "content": message})
formatted_prompt = self._format_conversation(conversation_history)
response = self.generate_response(formatted_prompt, stream=False)
if response:
conversation_history.append({"role": "assistant", "content": response})
return response, conversation_history
else:
return "Sorry, I couldn't generate a response.", conversation_history
def _format_conversation(self, conversation_history):
"""Format conversation history"""
formatted = ""
for turn in conversation_history:
if turn["role"] == "user":
formatted += f"User: {turn['content']}\n\n"
else:
formatted += f"Assistant: {turn['content']}\n\n"
formatted += "Assistant:"
return formatted
def main():
"""Main function with improved setup flow"""
client = OllamaDeepSeekClient()
print("🤖 Ollama DeepSeek V2 Lite Chat")
print("=" * 50)
# Get system info
info = client.get_system_info()
print("\nSystem Status:")
print(f" Port 11434: {'🔴 Used' if info['port_11434_used'] else '🟢 Free'}")
print(f" Container: {'🟢 Exists' if info['container_exists'] else '🔴 Not found'}")
print(f" Running: {'🟢 Yes' if info['container_running'] else '🔴 No'}")
print(f" Ollama API: {'🟢 Accessible' if info['ollama_accessible'] else '🔴 Not accessible'}")
# Decision logic
if info['ollama_accessible']:
print("\n✅ Ollama is ready!")
else:
print("\n🔧 Ollama needs setup...")
if info['container_exists']:
print("Container exists but not running.")
choice = input("Start existing container? (y/n): ").lower().strip()
if choice == 'y':
if client.start_ollama():
print("✅ Container started!")
else:
print("❌ Failed to start container")
choice = input("Set up new container? (y/n): ").lower().strip()
if choice == 'y':
client.setup_ollama()
else:
return
else:
choice = input("Set up new container? (y/n): ").lower().strip()
if choice == 'y':
client.setup_ollama()
else:
return
else:
print("No container found.")
choice = input("Set up new Ollama container? (y/n): ").lower().strip()
if choice == 'y':
client.setup_ollama()
else:
return
# Final check
if not client.check_ollama_running():
print("❌ Ollama is still not accessible. Exiting.")
return
# List models and start chat
client.list_models()
print(f"\n💬 Using model: {client.model}")
print("Commands: 'quit' to exit, 'clear' to clear history")
print("-" * 50)
conversation_history = []
while True:
try:
user_input = input("\nYou: ").strip()
if user_input.lower() in ['quit', 'exit']:
print("👋 Goodbye!")
break
elif user_input.lower() == 'clear':
conversation_history = []
print("🗑️ Conversation cleared")
continue
elif not user_input:
continue
print("Assistant: ", end='')
response, conversation_history = client.chat(user_input, conversation_history)
except KeyboardInterrupt:
print("\n\n👋 Goodbye!")
break
except Exception as e:
print(f"\n❌ Error: {e}")
if __name__ == "__main__":
main()
class DocumentContext:
def __init__(self, client):
self.client = client
self.loaded_documents = {}
def load_markdown_file(self, file_path):
"""Load a single markdown file"""
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
self.loaded_documents[file_path] = content
print(f"✅ Loaded: {file_path} ({len(content)} characters)")
return content
except Exception as e:
print(f"❌ Error loading {file_path}: {e}")
return None
def load_markdown_directory(self, directory_path):
"""Load all .md files from a directory"""
md_files = glob.glob(os.path.join(directory_path, "*.md"))
for file_path in md_files:
self.load_markdown_file(file_path)
def get_document_context(self, max_length=8000):
"""Get all loaded documents as context"""
context = ""
for filename, content in self.loaded_documents.items():
context += f"--- {os.path.basename(filename)} ---\n{content}\n\n"
# Truncate if too long
if len(context) > max_length:
context = context[:max_length] + "\n\n[Content truncated due to length]"
return context
def query_with_context(self, question, document_names=None, temperature=0.7):
"""Query the model with document context"""
if not self.loaded_documents:
return "No documents loaded. Please load some .md files first."
# Get context from specific documents or all
if document_names:
context = ""
for doc_name in document_names:
for filename, content in self.loaded_documents.items():
if doc_name in filename:
context += f"--- {os.path.basename(filename)} ---\n{content}\n\n"
else:
context = self.get_document_context()
prompt = f"""Based on the following documents, please answer the question.
DOCUMENTS:
{context}
QUESTION: {question}
Please provide a comprehensive answer based on the documents above. If the information isn't in the documents, please mention that."""
return self.client.generate_response(prompt, temperature=temperature)
# Make sure to add the missing method to the OllamaDeepSeekClientWithDocs class
class OllamaDeepSeekClientWithDocs(OllamaDeepSeekClient):
def __init__(self, base_url="http://localhost:11434"):
super().__init__(base_url)
self.doc_context = DocumentContext(self)
def load_documents(self, path):
"""Load documents from file or directory"""
if os.path.isfile(path) and path.endswith('.md'):
self.doc_context.load_markdown_file(path)
elif os.path.isdir(path):
self.doc_context.load_markdown_directory(path)
else:
print("❌ Please provide a valid .md file or directory")
def list_loaded_documents(self):
"""List all loaded documents"""
if not self.doc_context.loaded_documents:
print("No documents loaded.")
return
print("\n📚 Loaded Documents:")
total_chars = 0
for filename, content in self.doc_context.loaded_documents.items():
char_count = len(content)
total_chars += char_count
print(f" - {os.path.basename(filename)} ({char_count} characters)")
print(f"Total: {len(self.doc_context.loaded_documents)} documents, {total_chars} characters")
def chat_with_context(self, message, use_documents=True):
"""Chat with document context"""
if use_documents and self.doc_context.loaded_documents:
context = self.doc_context.get_document_context()
enhanced_prompt = f"""Based on the following documents, please answer the user's question.
DOCUMENTS:
{context}
USER QUESTION: {message}
Please provide a comprehensive answer based on the documents above. If the information isn't in the documents, please mention that and provide a general answer if possible."""
return self.generate_response(enhanced_prompt)
else:
return self.generate_response(message)
def main():
"""Main function with document support"""
client = OllamaDeepSeekClientWithDocs()
print("🤖 Ollama DeepSeek with Document Context")
print("=" * 50)
# Setup Ollama
info = client.get_system_info()
print("\nSystem Status:")
print(f" Port 11434: {'🔴 Used' if info['port_11434_used'] else '🟢 Free'}")
print(f" Container: {'🟢 Exists' if info['container_exists'] else '🔴 Not found'}")
print(f" Running: {'🟢 Yes' if info['container_running'] else '🔴 No'}")
print(f" Ollama API: {'🟢 Accessible' if info['ollama_accessible'] else '🔴 Not accessible'}")
if not info['ollama_accessible']:
print("\n🔧 Ollama needs setup...")
if info['container_exists']:
choice = input("Start existing container? (y/n): ").lower().strip()
if choice == 'y':
if not client.start_ollama():
print("❌ Failed to start container")
return
else:
choice = input("Set up new container? (y/n): ").lower().strip()
if choice == 'y':
if not client.setup_ollama():
return
else:
return
else:
choice = input("Set up new Ollama container? (y/n): ").lower().strip()
if choice == 'y':
if not client.setup_ollama():
return
else:
return
# Final check
if not client.check_ollama_running():
print("❌ Ollama is not accessible. Exiting.")
return
# List available models
client.list_models()
print(f"\n💬 Using model: {client.model}")
# Document loading interface
print("\n📁 Document Loading Options")
print("-" * 40)
while True:
print("\nOptions:")
print("1. Load a markdown file")
print("2. Load all markdown files from directory")
print("3. List loaded documents")
print("4. Clear all loaded documents")
print("5. Start chat with documents")
print("6. Start chat without documents")
print("7. Query specific documents")
print("8. Exit")
choice = input("\nChoose option (1-8): ").strip()
if choice == '1':
file_path = input("Enter markdown file path: ").strip()
if os.path.exists(file_path) and file_path.endswith('.md'):
client.load_documents(file_path)
else:
print("❌ File not found or not a .md file")
elif choice == '2':
dir_path = input("Enter directory path: ").strip()
if os.path.exists(dir_path) and os.path.isdir(dir_path):
client.load_documents(dir_path)
else:
print("❌ Directory not found")
elif choice == '3':
client.list_loaded_documents()
elif choice == '4':
client.doc_context.loaded_documents.clear()
print("🗑️ All documents cleared")
elif choice == '5':
if client.doc_context.loaded_documents:
start_chat_session(client, use_documents=True)
else:
print("❌ No documents loaded. Please load documents first.")
elif choice == '6':
start_chat_session(client, use_documents=False)
elif choice == '7':
if client.doc_context.loaded_documents:
query_specific_documents(client)
else:
print("❌ No documents loaded. Please load documents first.")
elif choice == '8':
print("👋 Goodbye!")
break
else:
print("❌ Invalid choice. Please enter 1-8.")
def start_chat_session(client, use_documents=True):
"""Start a chat session with or without documents"""
conversation_history = []
if use_documents:
print(f"\n💡 Chatting with {len(client.doc_context.loaded_documents)} loaded documents")
print("The model will reference your documents automatically.")
else:
print("\n💬 Regular chat mode (no document context)")
print("Commands: 'back' to return, 'clear' to clear history, 'documents' to toggle document usage")
print("-" * 60)
while True:
try:
user_input = input("\nYou: ").strip()
if user_input.lower() == 'back':
break
elif user_input.lower() == 'clear':
conversation_history = []
print("🗑️ Conversation history cleared")
continue
elif user_input.lower() == 'documents':
use_documents = not use_documents
mode = "with documents" if use_documents else "without documents"
print(f"🔄 Switched to chat {mode}")
continue
elif not user_input:
continue
print("Assistant: ", end='', flush=True)
if use_documents:
# Use document context for each query
response = client.chat_with_context(user_input, use_documents=True)
if response:
print(response)
else:
print("❌ No response generated")
else:
# Regular chat without documents
response, conversation_history = client.chat(user_input, conversation_history)
if response:
print(response)
else:
print("❌ No response generated")
except KeyboardInterrupt:
print("\n\nReturning to main menu...")
break
except Exception as e:
print(f"\n❌ Error: {e}")
def query_specific_documents(client):
"""Query specific documents by name"""
print("\n📚 Loaded Documents:")
doc_names = []
for i, filename in enumerate(client.doc_context.loaded_documents.keys()):
doc_name = os.path.basename(filename)
doc_names.append(doc_name)
print(f" {i+1}. {doc_name}")
print("\nSelect documents to query (comma-separated numbers, or 'all'):")
selection = input("Selection: ").strip()
selected_docs = []
if selection.lower() == 'all':
selected_docs = doc_names
print("✅ Using all documents")
else:
try:
indices = [int(x.strip()) - 1 for x in selection.split(',')]
for idx in indices:
if 0 <= idx < len(doc_names):
selected_docs.append(doc_names[idx])
if selected_docs:
print(f"✅ Using documents: {', '.join(selected_docs)}")
else:
print("❌ No valid documents selected")
return
except ValueError:
print("❌ Invalid selection")
return
print("\nEnter your question about the selected documents:")
question = input("Question: ").strip()
if question:
print("\nAssistant: ", end='', flush=True)
response = client.doc_context.query_with_context(question, document_names=selected_docs)
if response:
print(response)
else:
print("❌ No response generated")
else:
print("❌ No question provided")
# def main():
# """Main interactive chat function"""
# client = OllamaDeepSeekClient()
# print("🤖 Ollama DeepSeek V2 Lite Chat")
# print("=" * 50)
# # Check container status
# status = client.get_container_status()
# if status:
# print(f"Container Status: {status['status']}")
# print(f"Ports: {status['ports']}")
# else:
# print("No container found.")
# # Check if Ollama is running
# if not client.check_ollama_running():
# print("\nOllama is not running.")
# choice = input("Do you want to start the existing container? (y/n): ").lower().strip()
# if choice == 'y':
# if not client.start_ollama_container():
# print("Failed to start existing container.")
# choice = input("Do you want to set up a new container? (y/n): ").lower().strip()
# if choice == 'y':
# if not client.setup_ollama_container():
# print("Failed to set up Ollama. Exiting.")
# return
# else:
# print("Exiting.")
# return
# else:
# choice = input("Do you want to set up a new container? (y/n): ").lower().strip()
# if choice == 'y':
# if not client.setup_ollama_container():
# print("Failed to set up Ollama. Exiting.")
# return
# else:
# print("Exiting.")
# return
# # List available models
# print(f"\nUsing model: {client.model}")
# print("Base URL:", client.base_url)
# client.list_models()
# print("\nCommands: 'quit' to exit, 'clear' to clear history, 'status' for container info\n")
# conversation_history = []
# while True:
# try:
# user_input = input("You: ").strip()
# if user_input.lower() in ['quit', 'exit']:
# print("Goodbye! 👋")
# break
# elif user_input.lower() == 'clear':
# conversation_history = []
# print("Conversation history cleared.")
# continue
# elif user_input.lower() == 'status':
# status = client.get_container_status()
# if status:
# print(f"Container: {status['name']}")
# print(f"Status: {status['status']}")
# print(f"Ports: {status['ports']}")
# else:
# print("No container found.")
# continue
# elif not user_input:
# continue
# print("Assistant: ", end='')
# response, conversation_history = client.chat(user_input, conversation_history)
# except KeyboardInterrupt:
# print("\n\nGoodbye! 👋")
# break
# except Exception as e:
# print(f"\nError: {e}")
if __name__ == "__main__":
main()

24
test/setup.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
# setup_ollama_docker.sh
# Pull the official Ollama Docker image
docker pull ollama/ollama
# Create a directory for Ollama data
mkdir -p ~/ollama_data
# Run Ollama in Docker with data persistence
docker run -d \
--name ollama \
-p 11434:11434 \
-v ~/ollama_data:/root/.ollama \
--restart unless-stopped \
ollama/ollama
echo "Ollama is running in Docker. Wait a moment for it to initialize..."
sleep 10
# Pull the DeepSeek V2 Lite model
docker exec ollama ollama pull deepseek-coder:6.7b
echo "Setup complete! Ollama is running with DeepSeek V2 Lite model."