This commit is contained in:
2025-03-12 16:36:17 +01:00
parent 1581628ea3
commit 5ba11aab46
29 changed files with 1860 additions and 33 deletions

1
cli/.gitignore vendored
View File

@@ -1,3 +1,4 @@
hero
compile
compile_upload
vdo

12
cli/vdo.v Normal file
View File

@@ -0,0 +1,12 @@
module main
import freeflowuniverse.herolib.mcp.v_do
fn main() {
// Create and start the MCP server
mut server := v_do.new_server()
server.start() or {
eprintln('Error starting server: $err')
exit(1)
}
}

View File

@@ -0,0 +1,209 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.qdrant
import os
import flag
mut fp := flag.new_flag_parser(os.args)
fp.application('qdrant_example.vsh')
fp.version('v0.1.0')
fp.description('Example script demonstrating Qdrant client usage')
fp.skip_executable()
help_requested := fp.bool('help', `h`, false, 'Show help message')
if help_requested {
println(fp.usage())
exit(0)
}
additional_args := fp.finalize() or {
eprintln(err)
println(fp.usage())
exit(1)
}
// Initialize Qdrant client
mut client := qdrant.get(name: 'default') or {
// If client doesn't exist, create a new one
mut new_client := qdrant.QdrantClient{
name: 'default'
url: 'http://localhost:6333'
}
qdrant.set(new_client) or {
eprintln('Failed to set Qdrant client: ${err}')
exit(1)
}
new_client
}
println('Connected to Qdrant at ${client.url}')
// Check if Qdrant is healthy
is_healthy := client.health_check() or {
eprintln('Failed to check Qdrant health: ${err}')
exit(1)
}
if !is_healthy {
eprintln('Qdrant is not healthy')
exit(1)
}
println('Qdrant is healthy')
// Get service info
service_info := client.get_service_info() or {
eprintln('Failed to get service info: ${err}')
exit(1)
}
println('Qdrant version: ${service_info.version}')
// Collection name for our example
collection_name := 'example_collection'
// Check if collection exists and delete it if it does
collections := client.list_collections() or {
eprintln('Failed to list collections: ${err}')
exit(1)
}
if collection_name in collections.result {
println('Collection ${collection_name} already exists, deleting it...')
client.delete_collection(collection_name: collection_name) or {
eprintln('Failed to delete collection: ${err}')
exit(1)
}
println('Collection deleted')
}
// Create a new collection
println('Creating collection ${collection_name}...')
vectors_config := qdrant.VectorsConfig{
size: 4 // Small size for example purposes
distance: .cosine
}
client.create_collection(
collection_name: collection_name
vectors: vectors_config
) or {
eprintln('Failed to create collection: ${err}')
exit(1)
}
println('Collection created')
// Upsert some points
println('Upserting points...')
points := [
qdrant.PointStruct{
id: '1'
vector: [f32(0.1), 0.2, 0.3, 0.4]
payload: {
'color': 'red'
'category': 'furniture'
'name': 'chair'
}
},
qdrant.PointStruct{
id: '2'
vector: [f32(0.2), 0.3, 0.4, 0.5]
payload: {
'color': 'blue'
'category': 'electronics'
'name': 'laptop'
}
},
qdrant.PointStruct{
id: '3'
vector: [f32(0.3), 0.4, 0.5, 0.6]
payload: {
'color': 'green'
'category': 'food'
'name': 'apple'
}
}
]
client.upsert_points(
collection_name: collection_name
points: points
wait: true
) or {
eprintln('Failed to upsert points: ${err}')
exit(1)
}
println('Points upserted')
// Get collection info to verify points were added
collection_info := client.get_collection(collection_name: collection_name) or {
eprintln('Failed to get collection info: ${err}')
exit(1)
}
println('Collection has ${collection_info.vectors_count} points')
// Search for points
println('Searching for points similar to [0.1, 0.2, 0.3, 0.4]...')
search_result := client.search(
collection_name: collection_name
vector: [f32(0.1), 0.2, 0.3, 0.4]
limit: 3
) or {
eprintln('Failed to search points: ${err}')
exit(1)
}
println('Search results:')
for i, point in search_result.result {
println(' ${i+1}. ID: ${point.id}, Score: ${point.score}')
if payload := point.payload {
println(' Name: ${payload['name']}')
println(' Category: ${payload['category']}')
println(' Color: ${payload['color']}')
}
}
// Search with filter
println('\nSearching for points with category "electronics"...')
filter := qdrant.Filter{
must: [
qdrant.FieldCondition{
key: 'category'
match: 'electronics'
}
]
}
filtered_search := client.search(
collection_name: collection_name
vector: [f32(0.1), 0.2, 0.3, 0.4]
filter: filter
limit: 3
) or {
eprintln('Failed to search with filter: ${err}')
exit(1)
}
println('Filtered search results:')
for i, point in filtered_search.result {
println(' ${i+1}. ID: ${point.id}, Score: ${point.score}')
if payload := point.payload {
println(' Name: ${payload['name']}')
println(' Category: ${payload['category']}')
println(' Color: ${payload['color']}')
}
}
// Clean up - delete the collection
println('\nCleaning up - deleting collection...')
client.delete_collection(collection_name: collection_name) or {
eprintln('Failed to delete collection: ${err}')
exit(1)
}
println('Collection deleted')
println('Example completed successfully')

View File

@@ -3,5 +3,8 @@
import freeflowuniverse.herolib.installers.lang.python as python_module
mut python_installer := python_module.get()!
// python_installer.install()!
python_installer.destroy()!
python_installer.install()!
// python_installer.destroy()!

View File

@@ -1,7 +1,7 @@
module jina
import freeflowuniverse.herolib.core.httpconnection
import os
// import os
import json
@[params]
@@ -242,4 +242,4 @@ pub fn (mut j Jina) rerank(params RerankParams) !RankingOutput {
// // If we get a response, the API key is valid
// return true
// }
// }

View File

@@ -2,7 +2,7 @@ module jina
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
// import freeflowuniverse.herolib.ui.console
__global (
jina_global map[string]&Jina

View File

@@ -2,7 +2,7 @@ module jina
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.core.httpconnection
import net.http
// import net.http
import os
pub const version = '0.0.0'

View File

@@ -1,6 +1,6 @@
module jina
import json
// import json
pub enum JinaRerankModel {
reranker_v2_base_multilingual // 278M

View File

@@ -0,0 +1,394 @@
module qdrant
import freeflowuniverse.herolib.core.httpconnection
import json
// import os
// QdrantClient is the main client for interacting with the Qdrant API
pub struct QdrantClient {
pub mut:
name string = 'default'
secret string
url string = 'http://localhost:6333'
}
// httpclient creates a new HTTP connection to the Qdrant API
fn (mut self QdrantClient) httpclient() !&httpconnection.HTTPConnection {
mut http_conn := httpconnection.new(
name: 'Qdrant_vclient'
url: self.url
)!
// Add authentication header if API key is provided
if self.secret.len > 0 {
http_conn.default_header.add(.api_key, self.secret)
}
return http_conn
}
// Collections API
@[params]
pub struct CreateCollectionParams {
pub mut:
collection_name string @[required]
vectors VectorsConfig @[required]
shard_number ?int
replication_factor ?int
write_consistency_factor ?int
on_disk_payload ?bool
hnsw_config ?HnswConfig
optimizers_config ?OptimizersConfig
wal_config ?WalConfig
quantization_config ?QuantizationConfig
init_from ?InitFrom
timeout ?int
}
// Create a new collection
pub fn (mut q QdrantClient) create_collection(params CreateCollectionParams) !bool {
mut collection_params := CollectionParams{
vectors: params.vectors
}
if v := params.shard_number {
collection_params.shard_number = v
}
if v := params.replication_factor {
collection_params.replication_factor = v
}
if v := params.write_consistency_factor {
collection_params.write_consistency_factor = v
}
if v := params.on_disk_payload {
collection_params.on_disk_payload = v
}
if v := params.hnsw_config {
collection_params.hnsw_config = v
}
if v := params.optimizers_config {
collection_params.optimizers_config = v
}
if v := params.wal_config {
collection_params.wal_config = v
}
if v := params.quantization_config {
collection_params.quantization_config = v
}
if v := params.init_from {
collection_params.init_from = v
}
mut query_params := map[string]string{}
if v := params.timeout {
query_params['timeout'] = v.str()
}
req := httpconnection.Request{
method: .put
prefix: 'collections/${params.collection_name}'
dataformat: .json
data: json.encode(collection_params)
params: query_params
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
result := json.decode(OperationResponse, response.body)!
return result.result
}
@[params]
pub struct ListCollectionsParams {
pub mut:
timeout ?int
}
// List all collections
pub fn (mut q QdrantClient) list_collections(params ListCollectionsParams) !CollectionsResponse {
mut query_params := map[string]string{}
if v := params.timeout {
query_params['timeout'] = v.str()
}
req := httpconnection.Request{
method: .get
prefix: 'collections'
params: query_params
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
return json.decode(CollectionsResponse, response.body)!
}
@[params]
pub struct DeleteCollectionParams {
pub mut:
collection_name string @[required]
timeout ?int
}
// Delete a collection
pub fn (mut q QdrantClient) delete_collection(params DeleteCollectionParams) !bool {
mut query_params := map[string]string{}
if v := params.timeout {
query_params['timeout'] = v.str()
}
req := httpconnection.Request{
method: .delete
prefix: 'collections/${params.collection_name}'
params: query_params
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
result := json.decode(OperationResponse, response.body)!
return result.result
}
@[params]
pub struct GetCollectionParams {
pub mut:
collection_name string @[required]
timeout ?int
}
// Get collection info
pub fn (mut q QdrantClient) get_collection(params GetCollectionParams) !CollectionInfo {
mut query_params := map[string]string{}
if v := params.timeout {
query_params['timeout'] = v.str()
}
req := httpconnection.Request{
method: .get
prefix: 'collections/${params.collection_name}'
params: query_params
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
result := json.decode(CollectionInfoResponse, response.body)!
return result.result
}
// Points API
@[params]
pub struct UpsertPointsParams {
pub mut:
collection_name string @[required]
points []PointStruct @[required]
wait ?bool
ordering ?WriteOrdering
}
// Upsert points
pub fn (mut q QdrantClient) upsert_points(params UpsertPointsParams) !PointsOperationResponse {
mut query_params := map[string]string{}
if v := params.wait {
query_params['wait'] = v.str()
}
mut request_body := map[string]json.Any{}
request_body['points'] = params.points
if v := params.ordering {
request_body['ordering'] = v
}
req := httpconnection.Request{
method: .put
prefix: 'collections/${params.collection_name}/points'
dataformat: .json
data: json.encode(request_body)
params: query_params
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
return json.decode(PointsOperationResponse, response.body)!
}
@[params]
pub struct DeletePointsParams {
pub mut:
collection_name string @[required]
points_selector PointsSelector @[required]
wait ?bool
ordering ?WriteOrdering
}
// Delete points
pub fn (mut q QdrantClient) delete_points(params DeletePointsParams) !PointsOperationResponse {
mut query_params := map[string]string{}
if v := params.wait {
query_params['wait'] = v.str()
}
mut request_body := map[string]json.Any{}
if params.points_selector.points != none {
request_body['points'] = params.points_selector.points
} else if params.points_selector.filter != none {
request_body['filter'] = params.points_selector.filter
}
if v := params.ordering {
request_body['ordering'] = v
}
req := httpconnection.Request{
method: .post
prefix: 'collections/${params.collection_name}/points/delete'
dataformat: .json
data: json.encode(request_body)
params: query_params
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
return json.decode(PointsOperationResponse, response.body)!
}
@[params]
pub struct GetPointParams {
pub mut:
collection_name string @[required]
id string @[required]
with_payload ?WithPayloadSelector
with_vector ?WithVector
}
// Get a point by ID
pub fn (mut q QdrantClient) get_point(params GetPointParams) !GetPointResponse {
mut query_params := map[string]string{}
if v := params.with_payload {
query_params['with_payload'] = json.encode(v)
}
if v := params.with_vector {
query_params['with_vector'] = json.encode(v)
}
req := httpconnection.Request{
method: .get
prefix: 'collections/${params.collection_name}/points/${params.id}'
params: query_params
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
return json.decode(GetPointResponse, response.body)!
}
@[params]
pub struct SearchParams {
pub mut:
collection_name string @[required]
vector []f32 @[required]
limit int = 10
filter ?Filter
params ?SearchParamsConfig
with_payload ?WithPayloadSelector
with_vector ?WithVector
score_threshold ?f32
}
// Search for points
pub fn (mut q QdrantClient) search(params SearchParams) !SearchResponse {
// Create a struct to serialize to JSON
struct SearchRequest {
pub mut:
vector []f32
limit int
filter ?Filter
params ?SearchParamsConfig
with_payload ?WithPayloadSelector
with_vector ?WithVector
score_threshold ?f32
}
mut request := SearchRequest{
vector: params.vector
limit: params.limit
}
if v := params.filter {
request.filter = v
}
if v := params.params {
request.params = v
}
if v := params.with_payload {
request.with_payload = v
}
if v := params.with_vector {
request.with_vector = v
}
if v := params.score_threshold {
request.score_threshold = v
}
req := httpconnection.Request{
method: .post
prefix: 'collections/${params.collection_name}/points/search'
dataformat: .json
data: json.encode(request)
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
return json.decode(SearchResponse, response.data)!
}
// Service API
// Get Qdrant service info
pub fn (mut q QdrantClient) get_service_info() !ServiceInfoResponse {
req := httpconnection.Request{
method: .get
prefix: ''
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
return json.decode(ServiceInfoResponse, response.data)!
}
// Check Qdrant health
pub fn (mut q QdrantClient) health_check() !bool {
req := httpconnection.Request{
method: .get
prefix: 'healthz'
}
mut httpclient := q.httpclient()!
response := httpclient.send(req)!
return response.code == 200
}

View File

@@ -0,0 +1,117 @@
module qdrant
fn test_qdrant_client() {
mut client := QDrantClient{
name: 'test_client'
url: 'http://localhost:6333'
}
// Test creating a collection
vectors_config := VectorsConfig{
size: 128
distance: .cosine
}
// Create collection
create_result := client.create_collection(
collection_name: 'test_collection'
vectors: vectors_config
) or {
assert false, 'Failed to create collection: ${err}'
return
}
assert create_result == true
// List collections
collections := client.list_collections() or {
assert false, 'Failed to list collections: ${err}'
return
}
assert 'test_collection' in collections.result
// Get collection info
collection_info := client.get_collection(
collection_name: 'test_collection'
) or {
assert false, 'Failed to get collection info: ${err}'
return
}
assert collection_info.vectors_count == 0
// Upsert points
points := [
PointStruct{
id: '1'
vector: [f32(0.1), 0.2, 0.3, 0.4]
payload: {
'color': 'red'
'category': 'furniture'
}
},
PointStruct{
id: '2'
vector: [f32(0.2), 0.3, 0.4, 0.5]
payload: {
'color': 'blue'
'category': 'electronics'
}
}
]
upsert_result := client.upsert_points(
collection_name: 'test_collection'
points: points
wait: true
) or {
assert false, 'Failed to upsert points: ${err}'
return
}
assert upsert_result.status == 'ok'
// Search for points
search_result := client.search(
collection_name: 'test_collection'
vector: [f32(0.1), 0.2, 0.3, 0.4]
limit: 1
) or {
assert false, 'Failed to search points: ${err}'
return
}
assert search_result.result.len > 0
// Get a point
point := client.get_point(
collection_name: 'test_collection'
id: '1'
) or {
assert false, 'Failed to get point: ${err}'
return
}
if result := point.result {
assert result.id == '1'
} else {
assert false, 'Point not found'
}
// Delete a point
delete_result := client.delete_points(
collection_name: 'test_collection'
points_selector: PointsSelector{
points: ['1']
}
wait: true
) or {
assert false, 'Failed to delete point: ${err}'
return
}
assert delete_result.status == 'ok'
// Delete collection
delete_collection_result := client.delete_collection(
collection_name: 'test_collection'
) or {
assert false, 'Failed to delete collection: ${err}'
return
}
assert delete_collection_result == true
}

View File

@@ -2,7 +2,7 @@ module qdrant
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
// import freeflowuniverse.herolib.ui.console
__global (
qdrant_global map[string]&QDrantClient

View File

@@ -1,8 +1,9 @@
module qdrant
import freeflowuniverse.herolib.data.paramsparser
// import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import os
// import json
// import os
pub const version = '0.0.0'
const singleton = false
@@ -13,9 +14,9 @@ const default = true
@[heap]
pub struct QDrantClient {
pub mut:
name string = 'default'
secret string
url string = "http://localhost:6333/"
name string = 'default'
secret string
url string = 'http://localhost:6333/'
}
// your checking & initialization code if needed
@@ -34,3 +35,363 @@ pub fn heroscript_loads(heroscript string) !QDrantClient {
mut obj := encoderhero.decode[QDrantClient](heroscript)!
return obj
}
// Base response structure
pub struct BaseResponse {
pub mut:
time f32
status string
}
// Operation response
pub struct OperationResponse {
pub mut:
time f32
status string
result bool
}
// Collections response
pub struct CollectionsResponse {
pub mut:
time f32
status string
result []string
}
// Collection info response
pub struct CollectionInfoResponse {
pub mut:
time f32
status string
result CollectionInfo
}
// Collection info
pub struct CollectionInfo {
pub mut:
status string
optimizer_status OptimizersStatus
vectors_count u64
indexed_vectors_count ?u64
points_count u64
segments_count u64
config CollectionConfig
payload_schema map[string]PayloadIndexInfo
}
// Optimizers status
pub struct OptimizersStatus {
pub mut:
status string
}
// Collection config
pub struct CollectionConfig {
pub mut:
params CollectionParams
hnsw_config ?HnswConfig
optimizer_config ?OptimizersConfig
wal_config ?WalConfig
quantization_config ?QuantizationConfig
}
// Collection params
pub struct CollectionParams {
pub mut:
vectors VectorsConfig
shard_number ?int
replication_factor ?int
write_consistency_factor ?int
on_disk_payload ?bool
hnsw_config ?HnswConfig
optimizers_config ?OptimizersConfig
wal_config ?WalConfig
quantization_config ?QuantizationConfig
init_from ?InitFrom
}
// Vectors config
pub struct VectorsConfig {
pub mut:
size int
distance Distance
hnsw_config ?HnswConfig
quantization_config ?QuantizationConfig
on_disk ?bool
}
// Distance type
pub enum Distance {
cosine
euclid
dot
}
// Convert Distance enum to string
pub fn (d Distance) str() string {
return match d {
.cosine { 'cosine' }
.euclid { 'euclid' }
.dot { 'dot' }
}
}
// HNSW config
pub struct HnswConfig {
pub mut:
m int
ef_construct int
full_scan_threshold ?int
max_indexing_threads ?int
on_disk ?bool
payload_m ?int
}
// Optimizers config
pub struct OptimizersConfig {
pub mut:
deleted_threshold f32
vacuum_min_vector_number int
default_segment_number int
max_segment_size ?int
memmap_threshold ?int
indexing_threshold ?int
flush_interval_sec ?int
max_optimization_threads ?int
}
// WAL config
pub struct WalConfig {
pub mut:
wal_capacity_mb ?int
wal_segments_ahead ?int
}
// Quantization config
pub struct QuantizationConfig {
pub mut:
scalar ?ScalarQuantization
product ?ProductQuantization
binary ?BinaryQuantization
}
// Scalar quantization
pub struct ScalarQuantization {
pub mut:
type_ string
quantile ?f32
always_ram ?bool
}
// Product quantization
pub struct ProductQuantization {
pub mut:
compression string
always_ram ?bool
}
// Binary quantization
pub struct BinaryQuantization {
pub mut:
binary bool
always_ram ?bool
}
// Init from
pub struct InitFrom {
pub mut:
collection string
shard ?int
}
// Payload index info
pub struct PayloadIndexInfo {
pub mut:
data_type string
params ?map[string]string
points int
}
// Points operation response
pub struct PointsOperationResponse {
pub mut:
time f32
status string
result OperationInfo
}
// Operation info
pub struct OperationInfo {
pub mut:
operation_id int
status string
}
// Point struct
pub struct PointStruct {
pub mut:
id string
vector []f32
payload ?map[string]string
}
// Points selector
pub struct PointsSelector {
pub mut:
points ?[]string
filter ?Filter
}
// Filter
pub struct Filter {
pub mut:
must ?[]Condition
must_not ?[]Condition
should ?[]Condition
}
// Filter is serialized directly to JSON
// Condition interface
pub interface Condition {}
// Field condition
pub struct FieldCondition {
pub mut:
key string
match ?string @[json: match]
match_integer ?int @[json: match]
match_float ?f32 @[json: match]
match_bool ?bool @[json: match]
range ?Range
geo_bounding_box ?GeoBoundingBox
geo_radius ?GeoRadius
values_count ?ValuesCount
}
// FieldCondition is serialized directly to JSON
// Range
pub struct Range {
pub mut:
lt ?f32
gt ?f32
gte ?f32
lte ?f32
}
// Range is serialized directly to JSON
// GeoBoundingBox
pub struct GeoBoundingBox {
pub mut:
top_left GeoPoint
bottom_right GeoPoint
}
// GeoBoundingBox is serialized directly to JSON
// GeoPoint
pub struct GeoPoint {
pub mut:
lon f32
lat f32
}
// GeoPoint is serialized directly to JSON
// GeoRadius
pub struct GeoRadius {
pub mut:
center GeoPoint
radius f32
}
// GeoRadius is serialized directly to JSON
// ValuesCount
pub struct ValuesCount {
pub mut:
lt ?int
gt ?int
gte ?int
lte ?int
}
// ValuesCount is serialized directly to JSON
// WithPayloadSelector
pub struct WithPayloadSelector {
pub mut:
include ?[]string
exclude ?[]string
}
// WithPayloadSelector is serialized directly to JSON
// WithVector
pub struct WithVector {
pub mut:
include ?[]string
}
// WithVector is serialized directly to JSON
// Get point response
pub struct GetPointResponse {
pub mut:
time f32
status string
result ?PointStruct
}
// Search params configuration
pub struct SearchParamsConfig {
pub mut:
hnsw_ef ?int
exact ?bool
}
// SearchParamsConfig is serialized directly to JSON
// Search response
pub struct SearchResponse {
pub mut:
time f32
status string
result []ScoredPoint
}
// Scored point
pub struct ScoredPoint {
pub mut:
id string
version int
score f32
payload ?map[string]string
vector ?[]f32
}
// Write ordering
pub struct WriteOrdering {
pub mut:
type_ string
}
// WriteOrdering is serialized directly to JSON
// Service info response
pub struct ServiceInfoResponse {
pub mut:
time f32
status string
result ServiceInfo
}
// Service info
pub struct ServiceInfo {
pub mut:
version string
commit ?string
}

View File

@@ -1,30 +1,169 @@
# qdrant
# Qdrant Client for HeroLib
This is a V client for [Qdrant](https://qdrant.tech/), a high-performance vector database and similarity search engine.
## Features
To get started
- Collection management (create, list, delete, get info)
- Points management (upsert, delete, search, get)
- Service information and health checks
- Support for filters, payload management, and vector operations
```vlang
import freeflowuniverse.herolib.clients. qdrant
mut client:= qdrant.get()!
client...
## Usage
### Initialize Client
```v
// Create a new Qdrant client
import freeflowuniverse.herolib.clients.qdrant
mut client := qdrant.get()!
// Or create with custom configuration
mut custom_client := qdrant.QDrantClient{
name: 'custom',
url: 'http://localhost:6333',
secret: 'your_api_key' // Optional
}
qdrant.set(custom_client)!
```
## example heroscript
### Collection Management
```v
// Create a collection
vectors_config := qdrant.VectorsConfig{
size: 128,
distance: .cosine
}
client.create_collection(
collection_name: 'my_collection',
vectors: vectors_config
)!
// List all collections
collections := client.list_collections()!
// Get collection info
collection_info := client.get_collection(
collection_name: 'my_collection'
)!
// Delete a collection
client.delete_collection(
collection_name: 'my_collection'
)!
```
### Points Management
```v
// Upsert points
points := [
qdrant.PointStruct{
id: '1',
vector: [f32(0.1), 0.2, 0.3, 0.4],
payload: {
'color': 'red',
'category': 'furniture'
}
},
qdrant.PointStruct{
id: '2',
vector: [f32(0.2), 0.3, 0.4, 0.5],
payload: {
'color': 'blue',
'category': 'electronics'
}
}
]
client.upsert_points(
collection_name: 'my_collection',
points: points,
wait: true
)!
// Search for points
search_result := client.search(
collection_name: 'my_collection',
vector: [f32(0.1), 0.2, 0.3, 0.4],
limit: 10
)!
// Get a point by ID
point := client.get_point(
collection_name: 'my_collection',
id: '1'
)!
// Delete points
client.delete_points(
collection_name: 'my_collection',
points_selector: qdrant.PointsSelector{
points: ['1', '2']
},
wait: true
)!
```
### Service Information
```v
// Get service info
service_info := client.get_service_info()!
// Check health
is_healthy := client.health_check()!
```
## Advanced Usage
### Filtering
```v
// Create a filter
filter := qdrant.Filter{
must: [
qdrant.FieldCondition{
key: 'color',
match: 'red'
},
qdrant.FieldCondition{
key: 'price',
range: qdrant.Range{
gte: 10.0,
lt: 100.0
}
}
]
}
// Search with filter
search_result := client.search(
collection_name: 'my_collection',
vector: [f32(0.1), 0.2, 0.3, 0.4],
filter: filter,
limit: 10
)!
```
## Example HeroScript
```hero
!!qdrant.configure
secret: '...'
host: 'localhost'
port: 8888
name: 'default'
secret: 'your_api_key'
url: 'http://localhost:6333'
```
## Installation
Qdrant server can be installed using the provided installer script:
```bash
~/code/github/freeflowuniverse/herolib/examples/installers/db/qdrant.vsh
```
This will install and start a Qdrant server locally.

View File

@@ -7,7 +7,7 @@ To get started
```vlang
import freeflowuniverse.herolib.lib.installers.infra.coredns as coredns_installer
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
heroscript:="
!!coredns.configure name:'test'

View File

@@ -1,8 +1,6 @@
module python
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import os
pub const version = '3.12.0'
const singleton = true

44
lib/mcp/aiprompt.md Normal file
View File

@@ -0,0 +1,44 @@
make an mcp server in @lib/mcp/v_do
use the Standard Input/Output (stdio) transport as described in
https://modelcontextprotocol.io/docs/concepts/transports
The tool has following methods
## test
- args: $fullpath
- cmd: 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}'
if the file is a dir then find the .v files (non recursive) and do it for each opf those
collect the output and return
## run
- args: $fullpath
- cmd: 'v -gc none -stats -enable-globals -n -w -cg -g -cc tcc run ${fullpath}'
if the file is a dir then find the .v files (non recursive) and do it for each opf those
collect the output and return
## compile
- args: $fullpath
- cmd: 'cd /tmp && v -gc none -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc ${fullpath}'
if the file is a dir then find the .v files (non recursive) and do it for each opf those
collect the output and return
## vet
- args: $fullpath
- cmd: 'v vet -v -w ${fullpath}'
if the file is a dir then find the .v files (non recursive) and do it for each opf those
collect the output and return

92
lib/mcp/v_do/README.md Normal file
View File

@@ -0,0 +1,92 @@
# V-Do MCP Server
An implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for V language operations. This server uses the Standard Input/Output (stdio) transport as described in the [MCP documentation](https://modelcontextprotocol.io/docs/concepts/transports).
## Features
The server supports the following operations:
1. **test** - Run V tests on a file or directory
2. **run** - Execute V code from a file or directory
3. **compile** - Compile V code from a file or directory
4. **vet** - Run V vet on a file or directory
## Usage
### Building the Server
```bash
v -gc none -stats -enable-globals -n -w -cg -g -cc tcc /Users/despiegk/code/github/freeflowuniverse/herolib/lib/mcp/v_do
```
### Using the Server
The server communicates using the MCP protocol over stdio. To send a request, use the following format:
```
Content-Length: <length>
{"jsonrpc":"2.0","id":"<request-id>","method":"<method-name>","params":{"fullpath":"<path-to-file-or-directory>"}}
```
Where:
- `<length>` is the length of the JSON message in bytes
- `<request-id>` is a unique identifier for the request
- `<method-name>` is one of: `test`, `run`, `compile`, or `vet`
- `<path-to-file-or-directory>` is the absolute path to the V file or directory to process
### Example
Request:
```
Content-Length: 85
{"jsonrpc":"2.0","id":"1","method":"test","params":{"fullpath":"/path/to/file.v"}}
```
Response:
```
Content-Length: 245
{"jsonrpc":"2.0","id":"1","result":{"output":"Command: v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test /path/to/file.v\nExit code: 0\nOutput:\nAll tests passed!"}}
```
## Methods
### test
Runs V tests on the specified file or directory.
Command used:
```
v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}
```
If a directory is specified, it will run tests on all `.v` files in the directory (non-recursive).
### run
Executes the specified V file or all V files in a directory.
Command used:
```
v -gc none -stats -enable-globals -n -w -cg -g -cc tcc run ${fullpath}
```
### compile
Compiles the specified V file or all V files in a directory.
Command used:
```
cd /tmp && v -gc none -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc ${fullpath}
```
### vet
Runs V vet on the specified file or directory.
Command used:
```
v vet -v -w ${fullpath}
```

View File

@@ -0,0 +1,4 @@
module handlers
import os
import freeflowuniverse.herolib.mcp.v_do.logger

View File

@@ -0,0 +1,21 @@
module handlers
import os
import freeflowuniverse.herolib.mcp.v_do.logger
// list_v_files returns all .v files in a directory (non-recursive), excluding generated files ending with _.v
fn list_v_files(dir string) ![]string {
files := os.ls(dir) or {
return error('Error listing directory: $err')
}
mut v_files := []string{}
for file in files {
if file.ends_with('.v') && !file.ends_with('_.v') {
filepath := os.join_path(dir, file)
v_files << filepath
}
}
return v_files
}

View File

@@ -0,0 +1,4 @@
module handlers
import os
import freeflowuniverse.herolib.mcp.v_do.logger

View File

@@ -0,0 +1,31 @@
module handlers
import os
import freeflowuniverse.herolib.mcp.v_do.logger
// test runs v test on the specified file or directory
pub fn vtest(fullpath string) !string {
logger.info('test $fullpath')
if !os.exists(fullpath) {
return error('File or directory does not exist: $fullpath')
}
if os.is_dir(fullpath) {
mut results:=""
for item in list_v_files(fullpath)!{
results += vtest(item)!
results += '\n-----------------------\n'
}
return results
}else{
cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}'
logger.debug('Executing command: $cmd')
result := os.execute(cmd)
if result.exit_code != 0 {
return error('Test failed for $fullpath with exit code ${result.exit_code}\n${result.output}')
} else {
logger.info('Test completed for $fullpath')
}
return 'Command: $cmd\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
}
}

View File

@@ -0,0 +1,42 @@
module handlers
import os
import freeflowuniverse.herolib.mcp.v_do.logger
// vvet runs v vet on the specified file or directory
pub fn vvet(fullpath string) !string {
logger.info('vet $fullpath')
if !os.exists(fullpath) {
return error('File or directory does not exist: $fullpath')
}
if os.is_dir(fullpath) {
mut results := ""
files := list_v_files(fullpath) or {
return error('Error listing V files: $err')
}
for file in files {
results += vet_file(file) or {
logger.error('Failed to vet $file: $err')
return error('Failed to vet $file: $err')
}
results += '\n-----------------------\n'
}
return results
} else {
return vet_file(fullpath)
}
}
// vet_file runs v vet on a single file
fn vet_file(file string) !string {
cmd := 'v vet -v -w ${file}'
logger.debug('Executing command: $cmd')
result := os.execute(cmd)
if result.exit_code != 0 {
return error('Vet failed for $file with exit code ${result.exit_code}\n${result.output}')
} else {
logger.info('Vet completed for $file')
}
return 'Command: $cmd\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
}

View File

@@ -0,0 +1,50 @@
module logger
import os
// LogLevel defines the severity of log messages
pub enum LogLevel {
debug
info
warn
error
fatal
}
// log outputs a message to stderr with the specified log level
pub fn log(level LogLevel, message string) {
level_str := match level {
.debug { 'DEBUG' }
.info { 'INFO ' }
.warn { 'WARN ' }
.error { 'ERROR' }
.fatal { 'FATAL' }
}
eprintln('[$level_str] $message')
}
// debug logs a debug message to stderr
pub fn debug(message string) {
log(.debug, message)
}
// info logs an info message to stderr
pub fn info(message string) {
log(.info, message)
}
// warn logs a warning message to stderr
pub fn warn(message string) {
log(.warn, message)
}
// error logs an error message to stderr
pub fn error(message string) {
log(.error, message)
}
// fatal logs a fatal error message to stderr and exits the program
pub fn fatal(message string) {
log(.fatal, message)
exit(1)
}

188
lib/mcp/v_do/server.v Normal file
View File

@@ -0,0 +1,188 @@
module v_do
import json
import os
import freeflowuniverse.herolib.mcp.v_do.handlers
import freeflowuniverse.herolib.mcp.v_do.logger
// MCP server implementation using stdio transport
// Based on https://modelcontextprotocol.io/docs/concepts/transports
// MCPRequest represents an MCP request message
struct MCPRequest {
id string
method string
params map[string]string
jsonrpc string = '2.0'
}
// MCPResponse represents an MCP response
struct MCPResponse {
id string
result map[string]string
jsonrpc string = '2.0'
}
// MCPErrorResponse represents an MCP error response
struct MCPErrorResponse {
id string
error MCPError
jsonrpc string = '2.0'
}
// MCPError represents an error in an MCP response
struct MCPError {
code int
message string
}
// Server is the main MCP server struct
pub struct Server {}
// new_server creates a new MCP server
pub fn new_server() &Server {
return &Server{}
}
// start starts the MCP server
pub fn (mut s Server) start() ! {
logger.info('Starting V-Do MCP server')
for {
message := s.read_message() or {
logger.error('Failed to parse message: $err')
s.send_error('0', -32700, 'Failed to parse message: $err')
continue
}
logger.debug('Received message: ${message.method}')
s.handle_message(message) or {
logger.error('Internal error: $err')
s.send_error(message.id, -32603, 'Internal error: $err')
}
}
}
// read_message reads an MCP message from stdin
fn (mut s Server) read_message() !MCPRequest {
mut content_length := 0
// Read headers
for {
line := read_line_from_stdin() or {
logger.error('Failed to read line: $err')
return error('Failed to read line: $err')
}
if line.len == 0 {
break
}
if line.starts_with('Content-Length:') {
content_length_str := line.all_after('Content-Length:').trim_space()
content_length = content_length_str.int()
}
}
if content_length == 0 {
logger.error('No Content-Length header found')
return error('No Content-Length header found')
}
// Read message body
body := read_content_from_stdin(content_length) or {
logger.error('Failed to read content: $err')
return error('Failed to read content: $err')
}
// Parse JSON
message := json.decode(MCPRequest, body) or {
logger.error('Failed to decode JSON: $err')
return error('Failed to decode JSON: $err')
}
return message
}
// read_line_from_stdin reads a line from stdin
fn read_line_from_stdin() !string {
line := os.get_line()
return line
}
// read_content_from_stdin reads content from stdin with the specified length
fn read_content_from_stdin(length int) !string {
// For MCP protocol, we need to read exactly the content length
mut content := ''
mut reader := os.stdin()
mut buf := []u8{len: length}
n := reader.read(mut buf) or {
logger.error('Failed to read from stdin: $err')
return error('Failed to read from stdin: $err')
}
if n < length {
logger.error('Expected to read $length bytes, but got $n')
return error('Expected to read $length bytes, but got $n')
}
content = buf[..n].bytestr()
return content
}
// handle_message handles an MCP message
fn (mut s Server) handle_message(message MCPRequest) ! {
match message.method {
'test' {
fullpath := message.params['fullpath'] or {
logger.error('Missing fullpath parameter')
s.send_error(message.id, -32602, 'Missing fullpath parameter')
return error('Missing fullpath parameter')
}
logger.info('Running test on $fullpath')
result := handlers.vtest(fullpath) or {
logger.error('Test failed: $err')
s.send_error(message.id, -32000, 'Test failed: $err')
return err
}
s.send_response(message.id, {'output': result})
}
else {
logger.error('Unknown method: ${message.method}')
s.send_error(message.id, -32601, 'Unknown method: ${message.method}')
return error('Unknown method: ${message.method}')
}
}
}
// send_response sends an MCP response
fn (mut s Server) send_response(id string, result map[string]string) {
response := MCPResponse{
id: id
result: result
}
json_str := json.encode(response)
logger.debug('Sending response for id: $id')
s.write_message(json_str)
}
// send_error sends an MCP error response
fn (mut s Server) send_error(id string, code int, message string) {
logger.error('Sending error response: $message (code: $code, id: $id)')
error_response := MCPErrorResponse{
id: id
error: MCPError{
code: code
message: message
}
}
json_str := json.encode(error_response)
s.write_message(json_str)
}
// write_message writes an MCP message to stdout
fn (mut s Server) write_message(content string) {
header := 'Content-Length: ${content.len}\r\n\r\n'
print(header)
print(content)
}

View File

@@ -0,0 +1,105 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import os
import flag
import json
// Simple test client for the V-Do MCP server
// This script sends test requests to the MCP server and displays the responses
struct MCPRequest {
id string
method string
params map[string]string
jsonrpc string = '2.0'
}
fn send_request(method string, fullpath string) {
// Create the request
request := MCPRequest{
id: '1'
method: method
params: {
'fullpath': fullpath
}
}
// Encode to JSON
json_str := json.encode(request)
// Format the message with headers
message := 'Content-Length: ${json_str.len}\r\n\r\n${json_str}'
// Write to a temporary file
os.write_file('/tmp/mcp_request.txt', message) or {
eprintln('Failed to write request to file: $err')
return
}
// Execute the MCP server with the request
cmd := 'cat /tmp/mcp_request.txt | v run /Users/despiegk/code/github/freeflowuniverse/herolib/lib/mcp/v_do/main.v'
result := os.execute(cmd)
if result.exit_code != 0 {
eprintln('Error executing MCP server: ${result.output}')
return
}
// Parse and display the response
response := result.output
println('Raw response:')
println('-----------------------------------')
println(response)
println('-----------------------------------')
// Try to extract the JSON part
if response.contains('{') && response.contains('}') {
json_start := response.index_after('{', 0)
json_end := response.last_index_of('}')
if json_start >= 0 && json_end >= 0 && json_end > json_start {
json_part := response[json_start-1..json_end+1]
println('Extracted JSON:')
println(json_part)
}
}
}
// Parse command line arguments
mut fp := flag.new_flag_parser(os.args)
fp.application('test_client.vsh')
fp.version('v0.1.0')
fp.description('Test client for V-Do MCP server')
fp.skip_executable()
method := fp.string('method', `m`, 'test', 'Method to call (test, run, compile, vet)')
fullpath := fp.string('path', `p`, '', 'Path to the file or directory to process')
help_requested := fp.bool('help', `h`, false, 'Show help message')
if help_requested {
println(fp.usage())
exit(0)
}
additional_args := fp.finalize() or {
eprintln(err)
println(fp.usage())
exit(1)
}
if fullpath == '' {
eprintln('Error: Path is required')
println(fp.usage())
exit(1)
}
// Validate method
valid_methods := ['test', 'run', 'compile', 'vet']
if method !in valid_methods {
eprintln('Error: Invalid method. Must be one of: ${valid_methods}')
println(fp.usage())
exit(1)
}
// Send the request
println('Sending $method request for $fullpath...')
send_request(method, fullpath)

12
lib/mcp/v_do/vdo.v Normal file
View File

@@ -0,0 +1,12 @@
module v_do
import freeflowuniverse.herolib.mcp.v_do.logger
fn main() {
logger.info('Starting V-Do server')
mut server := new_server()
server.start() or {
logger.fatal('Error starting server: $err')
exit(1)
}
}

View File

@@ -3,7 +3,7 @@
This module provides functionality for managing DNS records in Redis for use with CoreDNS. It supports various DNS record types and provides a simple interface for adding and managing DNS records.
```v
import freeflowuniverse.herolib.lib.osal.coredns
import freeflowuniverse.herolib.osal.coredns
// Create a new DNS record set
mut rs := coredns.new_dns_record_set()

View File

@@ -14,7 +14,7 @@ The module allows you to:
## Usage Example
```v
import freeflowuniverse.herolib.lib.osal.traefik
import freeflowuniverse.herolib.osal.traefik
fn main() ! {
// Create a new Traefik configuration

View File

@@ -246,7 +246,7 @@ pub fn load_config(cfg_dir string) !Config {
// Load and parse navbar config
navbar_content := os.read_file(os.join_path(cfg_dir, 'navbar.json'))!
navbar := json.decode(Navbar, navbar_content) or {
eprintln('navbar.json in ${cfg_dir} is not in the right format please fix.\nError: ${err}')
eprintln('navbar.json in ${cfg_dir} is not in the right format please fix.\nError: $err')
exit(99)
}