Updated tests

This commit is contained in:
Cyber MacGeddon 2025-07-11 16:43:27 +01:00
parent 4ff85cd6bd
commit d25c2f4d10
40 changed files with 301 additions and 485 deletions

View file

@ -1,27 +0,0 @@
test-prompt-... is tested with this prompt set...
prompt-template \
-p pulsar://localhost:6650 \
--system-prompt 'You are a {{attitude}}, you are called {{name}}' \
--global-term \
'name=Craig' \
'attitude=LOUD, SHOUTY ANNOYING BOT' \
--prompt \
'question={{question}}' \
'french-question={{question}}' \
"analyze=Find the name and age in this text, and output a JSON structure containing just the name and age fields: {{description}}. Don't add markup, just output the raw JSON object." \
"graph-query=Study the following knowledge graph, and then answer the question.\\n\nGraph:\\n{% for edge in knowledge %}({{edge.0}})-[{{edge.1}}]->({{edge.2}})\\n{%endfor%}\\nQuestion:\\n{{question}}" \
"extract-definition=Analyse the text provided, and then return a list of terms and definitions. The output should be a JSON array, each item in the array is an object with fields 'term' and 'definition'.Don't add markup, just output the raw JSON object. Here is the text:\\n{{text}}" \
--prompt-response-type \
'question=text' \
'analyze=json' \
'graph-query=text' \
'extract-definition=json' \
--prompt-term \
'question=name:Bonny' \
'french-question=attitude:French-speaking bot' \
--prompt-schema \
'analyze={ "type" : "object", "properties" : { "age": { "type" : "number" }, "name": { "type" : "string" } } }' \
'extract-definition={ "type": "array", "items": { "type": "object", "properties": { "term": { "type": "string" }, "definition": { "type": "string" } }, "required": [ "term", "definition" ] } }'

View file

@ -1,21 +0,0 @@
#!/usr/bin/env python3
from trustgraph.graph_rag import GraphRag
import sys
query = " ".join(sys.argv[1:])
gr = GraphRag(
verbose=True,
pulsar_host="pulsar://localhost:6650",
pr_request_queue="non-persistent://tg/request/prompt",
pr_response_queue="non-persistent://tg/response/prompt-response",
)
if query == "":
query="""This knowledge graph describes the Space Shuttle disaster.
Present 20 facts which are present in the knowledge graph."""
resp = gr.query(query)
print(resp)

View file

@ -1,100 +0,0 @@
#!/usr/bin/env python3
"""
Accepts entity/vector pairs and writes them to a Milvus store.
"""
from trustgraph.schema import Chunk
from trustgraph.schema import chunk_ingest_queue
from trustgraph.log_level import LogLevel
from trustgraph.base import Consumer
from threading import Thread, Lock
import time
module = "test-chunk-size"
default_input_queue = chunk_ingest_queue
default_subscriber = module
default_store_uri = 'http://localhost:19530'
class Processor(Consumer):
def __init__(self, **params):
input_queue = params.get("input_queue", default_input_queue)
subscriber = params.get("subscriber", default_subscriber)
width = params.get("width", 200)
super(Processor, self).__init__(
**params | {
"input_queue": input_queue,
"subscriber": subscriber,
"input_schema": Chunk,
}
)
self.sizes = {}
self.width = width
self.lock = Lock()
Thread(target=self.report).start()
def report(self):
while True:
time.sleep(1)
print()
with self.lock:
tot = 0
for i in range(0, 20000, self.width):
k = (i, i + self.width)
if k in self.sizes:
print(f"{i:5d} ..{i+self.width:5d}: {self.sizes[k]}")
tot += self.sizes[k]
print(f"{'Total':13s}: {tot}")
def handle(self, msg):
v = msg.value()
chunk = v.chunk.decode("utf-8")
l = len(chunk)
low = int(l / self.width) * self.width
high = low + self.width
key = (low, high)
with self.lock:
if key not in self.sizes:
self.sizes[key] = 0
self.sizes[key] += 1
@staticmethod
def add_args(parser):
Consumer.add_args(
parser, default_input_queue, default_subscriber,
)
parser.add_argument(
'--width',
type=int,
default=200,
help=f'Histogram width (default: 200)',
)
def run():
Processor.start(module, __doc__)
run()

View file

@ -1,48 +0,0 @@
#!/usr/bin/env python3
import json
import textwrap
from trustgraph.clients.agent_client import AgentClient
def wrap(text, width=75):
if text is None: text = "n/a"
out = textwrap.wrap(
text, width=width
)
return "\n".join(out)
def output(text, prefix="> ", width=78):
out = textwrap.indent(
text, prefix=prefix
)
print(out)
p = AgentClient(
pulsar_host="pulsar://pulsar:6650",
input_queue = "non-persistent://tg/request/agent:0000",
output_queue = "non-persistent://tg/response/agent:0000",
)
q = "How many cats does Mark have? Calculate that number raised to 0.4 power. Is that number lower than the numeric part of the mission identifier of the Space Shuttle Challenger on its last mission? If so, give me an apple pie recipe, otherwise return a poem about cheese."
output(wrap(q), "\U00002753 ")
print()
def think(x):
output(wrap(x), "\U0001f914 ")
print()
def observe(x):
output(wrap(x), "\U0001f4a1 ")
print()
resp = p.request(
question=q, think=think, observe=observe,
)
output(resp, "\U0001f4ac ")
print()

View file

@ -1,2 +0,0 @@
#!/usr/bin/env python3

View file

@ -1,22 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.document_embeddings_client import DocumentEmbeddingsClient
from trustgraph.clients.embeddings_client import EmbeddingsClient
ec = EmbeddingsClient(pulsar_host="pulsar://localhost:6650")
vectors = ec.request("What caused the space shuttle to explode?")
print(vectors)
llm = DocumentEmbeddingsClient(pulsar_host="pulsar://localhost:6650")
limit=10
resp = llm.request(vectors, limit)
print("Response...")
for val in resp:
print(val)

View file

@ -1,25 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
docs = [
"In our house there is a big cat and a small cat.",
"The small cat is black.",
"The big cat is called Fred.",
"The orange stripey cat is big.",
"The black cat pounces on the big cat.",
"The black cat is called Hope."
]
query="What is the name of the cat who pounces on Fred? Provide a full explanation."
resp = p.request_document_prompt(
query=query,
documents=docs,
)
print(resp)

View file

@ -1,19 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.document_rag_client import DocumentRagClient
rag = DocumentRagClient(
pulsar_host="pulsar://localhost:6650",
subscriber="test1",
input_queue = "non-persistent://tg/request/document-rag:default",
output_queue = "non-persistent://tg/response/document-rag:default",
)
query="""
What was the cause of the space shuttle disaster?"""
resp = rag.request(query)
print(resp)

View file

@ -1,18 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.embeddings_client import EmbeddingsClient
embed = EmbeddingsClient(
pulsar_host="pulsar://pulsar:6650",
input_queue="non-persistent://tg/request/embeddings:default",
output_queue="non-persistent://tg/response/embeddings:default",
subscriber="test1",
)
prompt="Write a funny limerick about a llama"
resp = embed.request(prompt)
print(resp)

View file

@ -1,92 +0,0 @@
#!/usr/bin/env python3
import requests
url = "http://localhost:8088/"
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "list-classes",
}
)
print(resp)
print(resp.text)
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "get-class",
"class-name": "default",
}
)
print(resp)
print(resp.text)
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "put-class",
"class-name": "bunch",
"class-definition": "{}",
}
)
print(resp)
print(resp.text)
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "get-class",
"class-name": "bunch",
}
)
print(resp)
print(resp.text)
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "list-classes",
}
)
print(resp)
print(resp.text)
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "delete-class",
"class-name": "bunch",
}
)
print(resp)
print(resp.text)
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "list-classes",
}
)
print(resp)
print(resp.text)
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "list-flows",
}
)
print(resp)
print(resp.text)

View file

@ -1,19 +0,0 @@
#!/usr/bin/env python3
import requests
url = "http://localhost:8088/"
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "get-class",
"class-name": "default",
}
)
resp = resp.json()
print(resp["class-definition"])

File diff suppressed because one or more lines are too long

View file

@ -1,23 +0,0 @@
#!/usr/bin/env python3
import requests
import json
url = "http://localhost:8088/"
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "start-flow",
"flow-id": "0003",
"class-name": "default",
}
)
print(resp)
print(resp.text)
resp = resp.json()
print(resp)

View file

@ -1,22 +0,0 @@
#!/usr/bin/env python3
import requests
import json
url = "http://localhost:8088/"
resp = requests.post(
f"{url}/api/v1/flow",
json={
"operation": "stop-flow",
"flow-id": "0003",
}
)
print(resp)
print(resp.text)
resp = resp.json()
print(resp)

View file

@ -1,11 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.config_client import ConfigClient
cli = ConfigClient(pulsar_host="pulsar://localhost:6650")
resp = cli.request_config()
print(resp)

View file

@ -1,22 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.graph_embeddings_client import GraphEmbeddingsClient
from trustgraph.clients.embeddings_client import EmbeddingsClient
ec = EmbeddingsClient(pulsar_host="pulsar://localhost:6650")
vectors = ec.request("What caused the space shuttle to explode?")
print(vectors)
llm = GraphEmbeddingsClient(pulsar_host="pulsar://localhost:6650")
limit=10
resp = llm.request(vectors, limit)
print("Response...")
for val in resp:
print(val.value)

View file

@ -1,22 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.graph_rag_client import GraphRagClient
rag = GraphRagClient(
pulsar_host="pulsar://localhost:6650",
subscriber="test1",
input_queue = "non-persistent://tg/request/graph-rag:default",
output_queue = "non-persistent://tg/response/graph-rag:default",
)
#query="""
#This knowledge graph describes the Space Shuttle disaster.
#Present 20 facts which are present in the knowledge graph."""
query = "How many cats does Mark have?"
resp = rag.request(query)
print(resp)

View file

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.graph_rag_client import GraphRagClient
rag = GraphRagClient(pulsar_host="pulsar://localhost:6650")
query="""List 20 key points to describe the research that led to the discovery of Leo VI.
"""
resp = rag.request(query)
print(resp)

View file

@ -1,24 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
chunk = """I noticed a cat in my garden. It is a four-legged animal
which is a mammal and can be tame or wild. I wonder if it will be friends
with me. I think the cat's name is Fred and it has 4 legs.
A cat is a small mammal.
A grapefruit is a citrus fruit.
"""
resp = p.request_definitions(
chunk=chunk,
)
for d in resp:
print(d.name, ":", d.definition)

View file

@ -1,72 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
facts = [
("accident", "evoked", "a wide range of deeply felt public responses"),
("Space Shuttle concept", "had", "genesis"),
("Commission", "had", "a mandate to develop recommendations for corrective or other action based upon the Commission's findings and determinations"),
("Commission", "established", "teams of persons"),
("Space Shuttle Challenger", "http://www.w3.org/2004/02/skos/core#definition", "A space shuttle that was destroyed in an accident during mission 51-L."),
("The mid fuselage", "contains", "the payload bay"),
("Volume I", "contains", "Chapter IX"),
("accident", "resulted in", "firm national resolve that those men and women be forever enshrined in the annals of American heroes"),
("Volume I", "contains", "Chapter IV"),
("Volume I", "contains", "Appendix A"),
("Volume I", "contains", "Appendix B"),
("Volume I", "contains", "The Staff"),
("Commission", "required", "detailed investigation"),
("Commission", "focused", "safety aspects of future flights"),
("Commission", "http://www.w3.org/2004/02/skos/core#definition", "An independent group appointed to investigate the Space Shuttle Challenger accident."),
("Commission", "moved forward with", "its investigation"),
("President", "appointed", "an independent Commission"),
("accident", "interrupted", "one of the most productive engineering, scientific and exploratory programs in history"),
("Volume I", "contains", "Preface"),
("Commission", "believes", "investigation"),
("Volume I", "contains", "Chapter I"),
("President", "was moved and troubled", "by this accident in a very personal way"),
("PRESIDENTIAL COMMISSION", "Report to", "President"),
("Volume I", "contains", "Chapter VI"),
("Commission", "held", "public hearings dealing with the facts leading up to the accident"),
("Volume I", "http://www.w3.org/2004/02/skos/core#definition", "The first volume of a multi-volume publication."),
("Space Shuttle Challenger", "was involved in", "an accident"),
("Volume I", "contains", "Chapter VII"),
("Volume I", "contains", "Chapter II"),
("Volume I", "contains", "Chapter V"),
("Commission", "believes", "its investigation and report have been responsive to the request of the President and hopes that they will serve the best interests of the nation in restoring the United States space program to its preeminent position in the world"),
("Commission", "supported", "panels"),
("Volume I", "contains", "Chapter VIII"),
("NASA", "cooperated", "Commission"),
("liquid oxygen tank", "contains", "oxidizer"),
("President", "http://www.w3.org/2004/02/skos/core#definition", "The head of state of the United States."),
("Volume I", "contains", "Chapter III"),
("Apollo lunar landing spacecraft", "had", "not yet flown"),
("Commission", "construe", "mandate"),
("accident", "became", "a milestone on the way to achieving the full potential that space offers to mankind"),
("Volume I", "contains", "The Commission"),
("Commission", "focused", "attention"),
("Commission", "learned", "lessons"),
("Commission", "required", "interfere with or supersede Congress"),
("Commission", "was made up of", "persons not connected with the mission"),
("Commission", "required", "review budgetary matters"),
("Space Shuttle", "became", "focus of NASA's near-term future"),
("Volume I", "contains", "Appendix C"),
("accident", "caused", "grief and sadness for the loss of seven brave members of the crew"),
("Commission", "http://www.w3.org/2004/02/skos/core#definition", "A group established to investigate the space shuttle accident"),
("Volume I", "contains", "Appendix D"),
("Commission", "had", "a mandate to review the circumstances surrounding the accident to establish the probable cause or causes of the accident"),
("Volume I", "contains", "Recommendations")
]
query="Present 20 facts which are present in the knowledge graph."
resp = p.request_kg_prompt(
query=query,
kg=facts,
)
print(resp)

View file

@ -1,21 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
chunk = """I noticed a cat in my garden. It is a four-legged animal
which is a mammal and can be tame or wild. I wonder if it will be friends
with me. I think the cat's name is Fred and it has 4 legs"""
resp = p.request_relationships(
chunk=chunk,
)
for d in resp:
print(d.s)
print(" ", d.p)
print(" ", d.o)
print(" ", d.o_entity)

View file

@ -1,19 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
chunk = """I noticed a cat in my garden. It is a four-legged animal
which is a mammal and can be tame or wild. I wonder if it will be friends
with me. I think the cat's name is Fred and it has 4 legs"""
resp = p.request_topics(
chunk=chunk,
)
for d in resp:
print(d.topic)
print(" ", d.definition)

View file

@ -1,19 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.llm_client import LlmClient
llm = LlmClient(
pulsar_host="pulsar://pulsar:6650",
input_queue="non-persistent://tg/request/text-completion:default",
output_queue="non-persistent://tg/response/text-completion:default",
subscriber="test1",
)
system = "You are a lovely assistant."
prompt="what is 2 + 2 == 5"
resp = llm.request(system, prompt)
print(resp)

View file

@ -1,15 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.llm_client import LlmClient
llm = LlmClient(pulsar_host="pulsar://localhost:6650")
prompt="What is 2 + 12?"
try:
resp = llm.request(prompt)
print(resp)
except Exception as e:
print(f"{e.__class__.__name__}: {e}")

View file

@ -1,15 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.llm_client import LlmClient
llm = LlmClient(pulsar_host="pulsar://localhost:6650")
prompt="What is 2 + 12?"
try:
resp = llm.request(prompt)
print(resp)
except Exception as e:
print(f"{e.__class__.__name__}: {e}")

View file

@ -1,36 +0,0 @@
#!/usr/bin/env python3
import pulsar
from pulsar.schema import JsonSchema
import base64
from trustgraph.schema import Document, Metadata
client = pulsar.Client("pulsar://localhost:6650", listener_name="localhost")
prod = client.create_producer(
topic="persistent://tg/flow/document-load:0000",
schema=JsonSchema(Document),
chunking_enabled=True,
)
path = "../sources/Challenger-Report-Vol1.pdf"
with open(path, "rb") as f:
blob = base64.b64encode(f.read()).decode("utf-8")
message = Document(
metadata = Metadata(
id = "00001",
metadata = [],
user="trustgraph",
collection="default",
),
data=blob
)
prod.send(message)
prod.close()
client.close()

View file

@ -1,37 +0,0 @@
#!/usr/bin/env python3
import pulsar
from pulsar.schema import JsonSchema
import base64
from trustgraph.schema import TextDocument, Metadata
client = pulsar.Client("pulsar://localhost:6650", listener_name="localhost")
prod = client.create_producer(
topic="persistent://tg/flow/text-document-load:0000",
schema=JsonSchema(TextDocument),
chunking_enabled=True,
)
path = "../trustgraph/docs/README.cats"
with open(path, "r") as f:
# blob = base64.b64encode(f.read()).decode("utf-8")
blob = f.read()
message = TextDocument(
metadata = Metadata(
id = "00001",
metadata = [],
user="trustgraph",
collection="default",
),
text=blob
)
prod.send(message)
prod.close()
client.close()

View file

@ -1,35 +0,0 @@
#!/usr/bin/env python3
from langchain_huggingface import HuggingFaceEmbeddings
from trustgraph.direct.milvus import TripleVectors
client = TripleVectors()
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
text="""A cat is a small animal. A dog is a large animal.
Cats say miaow. Dogs go woof.
"""
embeds = embeddings.embed_documents([text])[0]
text2="""If you couldn't download the model due to network issues, as a walkaround, you can use random vectors to represent the text and still finish the example. Just note that the search result won't reflect semantic similarity as the vectors are fake ones.
"""
embeds2 = embeddings.embed_documents([text2])[0]
client.insert(embeds, "animals")
client.insert(embeds, "vectors")
query="""What noise does a cat make?"""
qembeds = embeddings.embed_documents([query])[0]
res = client.search(
qembeds,
limit=2
)
print(res)

View file

@ -1,18 +0,0 @@
#!/usr/bin/env python3
import json
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
description = """Fred is a 4-legged cat who is 12 years old"""
resp = p.request(
id="analyze",
terms = {
"description": description,
}
)
print(json.dumps(resp, indent=4))

View file

@ -1,51 +0,0 @@
#!/usr/bin/env python3
import json
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(
pulsar_host="pulsar://localhost:6650",
input_queue="non-persistent://tg/request/prompt:default",
output_queue="non-persistent://tg/response/prompt:default",
subscriber="test1",
)
chunk="""
The Space Shuttle was a reusable spacecraft that transported astronauts and cargo to and from Earth's orbit. It was designed to launch like a rocket, maneuver in orbit like a spacecraft, and land like an airplane. The Space Shuttle was NASA's space transportation system and was used for many purposes, including:
Carrying astronauts
The Space Shuttle could carry up to seven astronauts at a time.
Launching, recovering, and repairing satellites
The Space Shuttle could launch satellites into orbit, recover them, and repair them.
Building the International Space Station
The Space Shuttle carried large parts into space to build the International Space Station.
Conducting research
Astronauts conducted experiments in the Space Shuttle, which was like a science lab in space.
The Space Shuttle was retired in 2011 after the Columbia accident in 2003. The Columbia Accident Investigation Board report found that the Space Shuttle was unsafe and expensive to make safe.
Here are some other facts about the Space Shuttle:
The Space Shuttle was 184 ft tall and had a diameter of 29 ft.
The Space Shuttle had a mass of 4,480,000 lb.
The Space Shuttle's first flight was on April 12, 1981.
The Space Shuttle's last mission was in 2011.
"""
q = "Tell me some facts in the knowledge graph"
resp = p.request(
id="extract-definitions",
variables = {
"text": chunk,
}
)
print(resp)
for fact in resp:
print(fact["entity"], "::")
print(fact["definition"])
print()

View file

@ -1,18 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
question = """What is the square root of 16?"""
resp = p.request(
id="french-question",
terms = {
"question": question
}
)
print(resp)

View file

@ -1,44 +0,0 @@
#!/usr/bin/env python3
import json
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
knowledge = [
("accident", "evoked", "a wide range of deeply felt public responses"),
("Space Shuttle concept", "had", "genesis"),
("Commission", "had", "a mandate to develop recommendations for corrective or other action based upon the Commission's findings and determinations"),
("Commission", "established", "teams of persons"),
("Space Shuttle Challenger", "http://www.w3.org/2004/02/skos/core#definition", "A space shuttle that was destroyed in an accident during mission 51-L."),
("The mid fuselage", "contains", "the payload bay"),
("Volume I", "contains", "Chapter IX"),
("accident", "resulted in", "firm national resolve that those men and women be forever enshrined in the annals of American heroes"),
("Volume I", "contains", "Chapter VII"),
("Volume I", "contains", "Chapter II"),
("Volume I", "contains", "Chapter V"),
("Commission", "believes", "its investigation and report have been responsive to the request of the President and hopes that they will serve the best interests of the nation in restoring the United States space program to its preeminent position in the world"),
("Commission", "construe", "mandate"),
("accident", "became", "a milestone on the way to achieving the full potential that space offers to mankind"),
("Volume I", "contains", "The Commission"),
("Commission", "http://www.w3.org/2004/02/skos/core#definition", "A group established to investigate the space shuttle accident"),
("Volume I", "contains", "Appendix D"),
("Commission", "had", "a mandate to review the circumstances surrounding the accident to establish the probable cause or causes of the accident"),
("Volume I", "contains", "Recommendations")
]
q = "Tell me some facts in the knowledge graph"
resp = p.request(
id="graph-query",
terms = {
"name": "Jayney",
"knowledge": knowledge,
"question": q
}
)
print(resp)

View file

@ -1,23 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(
pulsar_host="pulsar://localhost:6650",
input_queue="non-persistent://tg/request/prompt:default",
output_queue="non-persistent://tg/response/prompt:default",
subscriber="test1",
)
question = """What is the square root of 16?"""
resp = p.request(
id="question",
variables = {
"question": question
}
)
print(resp)

View file

@ -1,19 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
p = PromptClient(pulsar_host="pulsar://localhost:6650")
question = """What is the square root of 16?"""
resp = p.request(
id="question",
terms = {
"question": question,
"attitude": "Spanish-speaking bot"
}
)
print(resp)

View file

@ -1,51 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.prompt_client import PromptClient
from trustgraph.objects.object import Schema
from trustgraph.objects.field import Field, FieldType
schema = Schema(
name="actors",
description="actors in this story",
fields=[
Field(
name="name", type=FieldType.STRING,
description="Name of the animal or person in the story"
),
Field(
name="legs", type=FieldType.INT,
description="Number of legs of the animal or person"
),
Field(
name="notes", type=FieldType.STRING,
description="Additional notes or observations about this animal or person"
),
]
)
chunk = """I noticed a cat in my garden. It is a four-legged animal
which is a mammal and can be tame or wild. I wonder if it will be friends
with me? I think the cat's name is Fred and it has 4 legs.
There is also a dog barking outside. The dog has 4 legs also.
The dog comes to my call when I shout "Come here, Bernard".
I am also standing in the garden, my name is Steve and I have 2 legs.
My friend Clifford is coming to visit shortly, he has 3 legs due to
a freak accident at birth.
"""
p = PromptClient(pulsar_host="pulsar://localhost:6650")
resp = p.request_rows(
schema=schema,
chunk=chunk,
)
for d in resp:
print(f"Name: {d['name']}")
print(f" No. of legs: {d['legs']}")
print(f" Notes: {d['notes']}")
print()

View file

@ -1,13 +0,0 @@
scripts/object-extract-row \
-p pulsar://localhost:6650 \
--field 'name:string:100:pri:Name of the person in the story' \
--field 'job:string:100::Job title or role' \
--field 'date:string:20::Date entered into role if known' \
--field 'supervisor:string:100::Supervisor or manager of this person, if known' \
--field 'location:string:100::Main base or location of work, if known' \
--field 'notes:string:1000::Additional notes or observations about this animal or person' \
--no-metrics \
--name actors \
--description 'Relevant people'

View file

@ -1,72 +0,0 @@
#!/usr/bin/env python3
import pulsar
from trustgraph.clients.triples_query_client import TriplesQueryClient
tq = TriplesQueryClient(
pulsar_host="pulsar://localhost:6650",
)
e = "http://trustgraph.ai/e/shuttle"
limit=3
def dump(resp):
print("Response...")
for t in resp:
print(t.s.value, t.p.value, t.o.value)
print("-- * ---------------------------")
resp = tq.request(None, None, None, limit)
dump(resp)
print("-- s ---------------------------")
resp = tq.request("http://trustgraph.ai/e/shuttle", None, None, limit)
dump(resp)
print("-- p ---------------------------")
resp = tq.request(None, "http://trustgraph.ai/e/landed", None, limit)
dump(resp)
print("-- o ---------------------------")
resp = tq.request(None, None, "President", limit)
dump(resp)
print("-- sp ---------------------------")
resp = tq.request(
"http://trustgraph.ai/e/shuttle", "http://trustgraph.ai/e/landed", None,
limit
)
dump(resp)
print("-- so ---------------------------")
resp = tq.request(
"http://trustgraph.ai/e/shuttle", None, "the tower",
limit
)
dump(resp)
print("-- po ---------------------------")
resp = tq.request(
None, "http://trustgraph.ai/e/landed",
"on the concrete runway at Kennedy Space Center",
limit
)
dump(resp)
print("-- spo ---------------------------")
resp = tq.request(
"http://trustgraph.ai/e/shuttle", "http://trustgraph.ai/e/landed",
"on the concrete runway at Kennedy Space Center",
limit
)
dump(resp)

View file

@ -4,8 +4,8 @@ Pytest configuration and fixtures for text completion tests
import pytest
from unittest.mock import MagicMock, AsyncMock
from trustgraph.base.types import TextCompletionRequest, TextCompletionResponse, LlmResult
from trustgraph.schema import TextCompletionRequest, TextCompletionResponse
from trustgraph.base import LlmResult
@pytest.fixture
def mock_vertexai_credentials():
@ -156,4 +156,4 @@ def mock_async_context_manager():
async def __aexit__(self, exc_type, exc_val, exc_tb):
pass
return MockAsyncContextManager
return MockAsyncContextManager

View file

@ -1,327 +1,155 @@
"""
Unit tests for trustgraph.model.text_completion.vertexai
Following TEST_STRATEGY.md patterns for mocking external dependencies
Starting simple with one test to get the basics working
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch, call
from unittest.mock import AsyncMock, MagicMock, patch
from unittest import IsolatedAsyncioTestCase
import asyncio
import os
from google.api_core.exceptions import ResourceExhausted
from google.oauth2 import service_account
# Import the service under test
from trustgraph.model.text_completion.vertexai.llm import Processor
from trustgraph.base.types import TextCompletionRequest, TextCompletionResponse, LlmResult, Error
from trustgraph.base import LlmResult
class TestVertexAIProcessorInitialization(IsolatedAsyncioTestCase):
"""Test processor initialization with various configurations"""
def setUp(self):
"""Set up test fixtures"""
self.default_config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1
}
class TestVertexAIProcessorSimple(IsolatedAsyncioTestCase):
"""Simple test for processor initialization"""
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_processor_initialization_with_valid_credentials(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test processor initialization with valid service account credentials"""
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_processor_initialization_basic(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test basic processor initialization with mocked dependencies"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_generative_model.return_value = mock_model
# Mock the parent class initialization to avoid taskgroup requirement
mock_async_init.return_value = None
mock_llm_init.return_value = None
# Act
processor = Processor(**self.default_config)
# Assert
mock_service_account.Credentials.from_service_account_file.assert_called_once_with('private.json')
mock_vertexai.init.assert_called_once()
mock_generative_model.assert_called_once_with(
'gemini-2.0-flash-001',
generation_config=processor.generation_config,
safety_settings=processor.safety_settings
)
assert processor.model_name == 'gemini-2.0-flash-001'
assert processor.region == 'us-central1'
assert processor.temperature == 0.0
assert processor.max_output == 8192
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_processor_initialization_with_custom_model(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test processor initialization with custom model selection"""
# Arrange
config = self.default_config.copy()
config['model'] = 'gemini-1.5-pro'
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
# Act
processor = Processor(**config)
# Assert
mock_generative_model.assert_called_once_with(
'gemini-1.5-pro',
generation_config=processor.generation_config,
safety_settings=processor.safety_settings
)
assert processor.model_name == 'gemini-1.5-pro'
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_processor_initialization_with_custom_parameters(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test processor initialization with custom generation parameters"""
# Arrange
config = self.default_config.copy()
config.update({
'temperature': 0.7,
'max_output': 4096,
'region': 'us-east1'
})
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
# Act
processor = Processor(**config)
# Assert
assert processor.temperature == 0.7
assert processor.max_output == 4096
assert processor.region == 'us-east1'
assert processor.generation_config.temperature == 0.7
assert processor.generation_config.max_output_tokens == 4096
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_processor_initialization_without_private_key(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test processor initialization without private key (default credentials)"""
# Arrange
config = self.default_config.copy()
config['private_key'] = None
# Act
processor = Processor(**config)
# Assert
mock_service_account.Credentials.from_service_account_file.assert_not_called()
mock_vertexai.init.assert_called_once()
assert processor.model_name == 'gemini-2.0-flash-001'
async def test_processor_initialization_generation_config(self):
"""Test that generation config is properly set"""
# Arrange & Act
with patch('trustgraph.model.text_completion.vertexai.llm.service_account'), \
patch('trustgraph.model.text_completion.vertexai.llm.vertexai'), \
patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel'):
processor = Processor(**self.default_config)
# Assert
assert processor.generation_config.temperature == 0.0
assert processor.generation_config.max_output_tokens == 8192
assert processor.generation_config.top_p == 1.0
assert processor.generation_config.top_k == 10
assert processor.generation_config.candidate_count == 1
async def test_processor_initialization_safety_settings(self):
"""Test that safety settings are properly configured"""
# Arrange & Act
with patch('trustgraph.model.text_completion.vertexai.llm.service_account'), \
patch('trustgraph.model.text_completion.vertexai.llm.vertexai'), \
patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel'):
processor = Processor(**self.default_config)
# Assert
assert len(processor.safety_settings) == 4
# Check that all harm categories are configured
harm_categories = [setting.category for setting in processor.safety_settings]
assert 'HARM_CATEGORY_HARASSMENT' in str(harm_categories)
assert 'HARM_CATEGORY_HATE_SPEECH' in str(harm_categories)
assert 'HARM_CATEGORY_SEXUALLY_EXPLICIT' in str(harm_categories)
assert 'HARM_CATEGORY_DANGEROUS_CONTENT' in str(harm_categories)
class TestVertexAIMessageProcessing(IsolatedAsyncioTestCase):
"""Test message processing functionality"""
def setUp(self):
"""Set up test fixtures"""
self.default_config = {
config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1
'concurrency': 1,
'taskgroup': AsyncMock(), # Required by AsyncProcessor
'id': 'test-processor' # Required by AsyncProcessor
}
# Act
processor = Processor(**config)
# Assert
assert processor.model == 'gemini-2.0-flash-001' # It's stored as 'model', not 'model_name'
assert hasattr(processor, 'generation_config')
assert hasattr(processor, 'safety_settings')
assert hasattr(processor, 'llm')
mock_service_account.Credentials.from_service_account_file.assert_called_once_with('private.json')
mock_vertexai.init.assert_called_once()
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_successful_text_completion_simple_prompt(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test successful text completion with simple prompt"""
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_generate_content_success(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test successful content generation"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Test response from Gemini"
mock_response.usage_metadata.prompt_token_count = 10
mock_response.usage_metadata.candidates_token_count = 5
mock_response.text = "Generated response from Gemini"
mock_response.usage_metadata.prompt_token_count = 15
mock_response.usage_metadata.candidates_token_count = 8
mock_model.generate_content.return_value = mock_response
mock_generative_model.return_value = mock_model
mock_async_init.return_value = None
mock_llm_init.return_value = None
processor = Processor(**self.default_config)
config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
processor = Processor(**config)
# Act
result = await processor.generate_content("System prompt", "User prompt")
# Assert
assert isinstance(result, LlmResult)
assert result.text == "Test response from Gemini"
assert result.in_token == 10
assert result.out_token == 5
mock_model.generate_content.assert_called_once_with("System prompt\nUser prompt")
assert result.text == "Generated response from Gemini"
assert result.in_token == 15
assert result.out_token == 8
assert result.model == 'gemini-2.0-flash-001'
# Check that the method was called (actual prompt format may vary)
mock_model.generate_content.assert_called_once()
# Verify the call was made with the expected parameters
call_args = mock_model.generate_content.call_args
assert call_args[1]['generation_config'] == processor.generation_config
assert call_args[1]['safety_settings'] == processor.safety_settings
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_text_completion_with_system_instructions(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test text completion with system instructions"""
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_generate_content_rate_limit_error(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test rate limit error handling"""
# Arrange
from google.api_core.exceptions import ResourceExhausted
from trustgraph.exceptions import TooManyRequests
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Response with system instructions"
mock_response.usage_metadata.prompt_token_count = 25
mock_response.usage_metadata.candidates_token_count = 15
mock_model.generate_content.return_value = mock_response
mock_model.generate_content.side_effect = ResourceExhausted("Rate limit exceeded")
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("You are a helpful assistant", "What is AI?")
# Assert
assert result.text == "Response with system instructions"
assert result.in_token == 25
assert result.out_token == 15
mock_model.generate_content.assert_called_once_with("You are a helpful assistant\nWhat is AI?")
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_text_completion_with_long_context(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test text completion with long context"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Response to long context"
mock_response.usage_metadata.prompt_token_count = 1000
mock_response.usage_metadata.candidates_token_count = 100
mock_model.generate_content.return_value = mock_response
mock_generative_model.return_value = mock_model
mock_async_init.return_value = None
mock_llm_init.return_value = None
processor = Processor(**self.default_config)
long_prompt = "This is a very long prompt. " * 100
# Act
result = await processor.generate_content("System", long_prompt)
# Assert
assert result.text == "Response to long context"
assert result.in_token == 1000
assert result.out_token == 100
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_text_completion_with_empty_prompts(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test text completion with empty prompts"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Default response"
mock_response.usage_metadata.prompt_token_count = 1
mock_response.usage_metadata.candidates_token_count = 2
mock_model.generate_content.return_value = mock_response
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("", "")
# Assert
assert result.text == "Default response"
mock_model.generate_content.assert_called_once_with("\n")
class TestVertexAISafetyFiltering(IsolatedAsyncioTestCase):
"""Test safety filtering functionality"""
def setUp(self):
"""Set up test fixtures"""
self.default_config = {
config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_safety_filter_configuration(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test safety filter configuration"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
# Act
processor = Processor(**self.default_config)
processor = Processor(**config)
# Assert
assert len(processor.safety_settings) == 4
# Verify safety settings are passed to model
mock_generative_model.assert_called_once_with(
'gemini-2.0-flash-001',
generation_config=processor.generation_config,
safety_settings=processor.safety_settings
)
# Act & Assert
with pytest.raises(TooManyRequests):
await processor.generate_content("System prompt", "User prompt")
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_blocked_content_handling(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test blocked content handling"""
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_generate_content_blocked_response(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test handling of blocked content (safety filters)"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
@ -333,284 +161,236 @@ class TestVertexAISafetyFiltering(IsolatedAsyncioTestCase):
mock_response.usage_metadata.candidates_token_count = 0
mock_model.generate_content.return_value = mock_response
mock_generative_model.return_value = mock_model
mock_async_init.return_value = None
mock_llm_init.return_value = None
processor = Processor(**self.default_config)
config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
processor = Processor(**config)
# Act
result = await processor.generate_content("System", "Blocked content prompt")
result = await processor.generate_content("System prompt", "Blocked content")
# Assert
assert result.text == "" # Should return empty string for blocked content
assert isinstance(result, LlmResult)
assert result.text is None # Should preserve None for blocked content
assert result.in_token == 10
assert result.out_token == 0
assert result.model == 'gemini-2.0-flash-001'
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_processor_initialization_without_private_key(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test processor initialization without private key (should fail)"""
# Arrange
mock_async_init.return_value = None
mock_llm_init.return_value = None
class TestVertexAIErrorHandling(IsolatedAsyncioTestCase):
"""Test error handling functionality"""
def setUp(self):
"""Set up test fixtures"""
self.default_config = {
config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1
'private_key': None, # No private key provided
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_rate_limit_error_handling(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test rate limit error handling"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_model.generate_content.side_effect = ResourceExhausted("Rate limit exceeded")
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert isinstance(result, LlmResult)
assert result.text == ""
assert result.in_token is None
assert result.out_token is None
assert result.error == "TooManyRequests"
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_authentication_error_handling(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test authentication error handling"""
# Arrange
mock_service_account.Credentials.from_service_account_file.side_effect = FileNotFoundError("Private key not found")
# Act & Assert
with pytest.raises(FileNotFoundError):
processor = Processor(**self.default_config)
with pytest.raises(RuntimeError, match="Private key file not specified"):
processor = Processor(**config)
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_generic_exception_handling(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test generic exception handling"""
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_generate_content_generic_exception(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test handling of generic exceptions"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_model.generate_content.side_effect = Exception("Unknown error")
mock_model.generate_content.side_effect = Exception("Network error")
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert isinstance(result, LlmResult)
assert result.text == ""
assert result.in_token is None
assert result.out_token is None
assert result.error == "Unknown error"
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_model_not_found_error_handling(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test model not found error handling"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_model.generate_content.side_effect = ValueError("Model not found")
mock_generative_model.return_value = mock_model
mock_async_init.return_value = None
mock_llm_init.return_value = None
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert isinstance(result, LlmResult)
assert result.text == ""
assert result.error == "Model not found"
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_quota_exceeded_error_handling(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test quota exceeded error handling"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_model.generate_content.side_effect = ResourceExhausted("Quota exceeded")
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert isinstance(result, LlmResult)
assert result.text == ""
assert result.error == "TooManyRequests"
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_token_limit_exceeded_error_handling(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test token limit exceeded error handling"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_model.generate_content.side_effect = ValueError("Token limit exceeded")
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert isinstance(result, LlmResult)
assert result.text == ""
assert result.error == "Token limit exceeded"
class TestVertexAIMetricsCollection(IsolatedAsyncioTestCase):
"""Test metrics collection functionality"""
def setUp(self):
"""Set up test fixtures"""
self.default_config = {
config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
processor = Processor(**config)
# Act & Assert
with pytest.raises(Exception, match="Network error"):
await processor.generate_content("System prompt", "User prompt")
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_token_usage_metrics_collection(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test token usage metrics collection"""
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_processor_initialization_with_custom_parameters(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test processor initialization with custom parameters"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_generative_model.return_value = mock_model
mock_async_init.return_value = None
mock_llm_init.return_value = None
config = {
'region': 'us-west1',
'model': 'gemini-1.5-pro',
'temperature': 0.7,
'max_output': 4096,
'private_key': 'custom-key.json',
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
# Act
processor = Processor(**config)
# Assert
assert processor.model == 'gemini-1.5-pro'
# Verify that generation_config object exists (can't easily check internal values)
assert hasattr(processor, 'generation_config')
assert processor.generation_config is not None
# Verify that safety settings are configured
assert len(processor.safety_settings) == 4
# Verify service account was called with custom key
mock_service_account.Credentials.from_service_account_file.assert_called_once_with('custom-key.json')
# Verify that parameters dict has the correct values (this is accessible)
assert processor.parameters["temperature"] == 0.7
assert processor.parameters["max_output_tokens"] == 4096
assert processor.parameters["top_p"] == 1.0
assert processor.parameters["top_k"] == 32
assert processor.parameters["candidate_count"] == 1
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_vertexai_initialization_with_credentials(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test that VertexAI is initialized correctly with credentials"""
# Arrange
mock_credentials = MagicMock()
mock_credentials.project_id = "test-project-123"
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_generative_model.return_value = mock_model
mock_async_init.return_value = None
mock_llm_init.return_value = None
config = {
'region': 'europe-west1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'service-account.json',
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
# Act
processor = Processor(**config)
# Assert
# Verify VertexAI init was called with correct parameters
mock_vertexai.init.assert_called_once_with(
location='europe-west1',
credentials=mock_credentials,
project='test-project-123'
)
# Verify GenerativeModel was created with the right model name
mock_generative_model.assert_called_once_with('gemini-2.0-flash-001')
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
@patch('trustgraph.base.async_processor.AsyncProcessor.__init__')
@patch('trustgraph.base.llm_service.LlmService.__init__')
async def test_generate_content_empty_prompts(self, mock_llm_init, mock_async_init, mock_generative_model, mock_vertexai, mock_service_account):
"""Test content generation with empty prompts"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Test response"
mock_response.usage_metadata.prompt_token_count = 50
mock_response.usage_metadata.candidates_token_count = 25
mock_response.text = "Default response"
mock_response.usage_metadata.prompt_token_count = 2
mock_response.usage_metadata.candidates_token_count = 3
mock_model.generate_content.return_value = mock_response
mock_generative_model.return_value = mock_model
mock_async_init.return_value = None
mock_llm_init.return_value = None
processor = Processor(**self.default_config)
config = {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
processor = Processor(**config)
# Act
result = await processor.generate_content("System", "User prompt")
result = await processor.generate_content("", "")
# Assert
assert result.in_token == 50
assert result.out_token == 25
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_request_duration_metrics(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test request duration metrics collection"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
assert isinstance(result, LlmResult)
assert result.text == "Default response"
assert result.in_token == 2
assert result.out_token == 3
assert result.model == 'gemini-2.0-flash-001'
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Test response"
mock_response.usage_metadata.prompt_token_count = 10
mock_response.usage_metadata.candidates_token_count = 5
# Simulate slow response
async def slow_generate_content(prompt):
await asyncio.sleep(0.1)
return mock_response
mock_model.generate_content.side_effect = slow_generate_content
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert result.text == "Test response"
# Note: In real implementation, this would be captured by Prometheus metrics
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_error_rate_metrics(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test error rate metrics collection"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_model.generate_content.side_effect = Exception("Test error")
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert result.error == "Test error"
# Note: In real implementation, this would be captured by Prometheus metrics
@patch('trustgraph.model.text_completion.vertexai.llm.service_account')
@patch('trustgraph.model.text_completion.vertexai.llm.vertexai')
@patch('trustgraph.model.text_completion.vertexai.llm.GenerativeModel')
async def test_cost_calculation_metrics(self, mock_generative_model, mock_vertexai, mock_service_account):
"""Test cost calculation metrics per model type"""
# Arrange
mock_credentials = MagicMock()
mock_service_account.Credentials.from_service_account_file.return_value = mock_credentials
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Test response"
mock_response.usage_metadata.prompt_token_count = 100
mock_response.usage_metadata.candidates_token_count = 50
mock_model.generate_content.return_value = mock_response
mock_generative_model.return_value = mock_model
processor = Processor(**self.default_config)
# Act
result = await processor.generate_content("System", "User prompt")
# Assert
assert result.in_token == 100
assert result.out_token == 50
# Note: Cost calculation would be done by the metrics system based on model type and token usage
# Verify the model was called with the combined empty prompts
mock_model.generate_content.assert_called_once()
call_args = mock_model.generate_content.call_args
# The prompt should be "" + "\n\n" + "" = "\n\n"
assert call_args[0][0] == "\n\n"
if __name__ == '__main__':