Skip to content

Chat with tools and cards #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
580 changes: 539 additions & 41 deletions chat-agent/api-go/chatAgent.go

Large diffs are not rendered by default.

123 changes: 98 additions & 25 deletions chat-agent/api-go/main.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,129 @@
package main

import (
"encoding/json"
"fmt"

"github.com/hypermodeinc/modus/sdk/go/pkg/agents"
)

type SearchResponse struct {
Message string `json:"message"`
History string `json:"history"`
User_preferences string `json:"user_preferences"`
type ChatRequest struct {
Message string `json:"message"`
}

type ChatResponse struct {
Items string `json:"items"` // JSON string of items array
ConversationId string `json:"conversationId"`
}

type HistoryResponse struct {
Items string `json:"items"` // JSON string of items array
Count int `json:"count"`
}

func init() {
agents.Register(&ChatAgent{})
}

// The following are regular Modus functions.
// The following are regular Modus functions exposed via GraphQL

func CreateConversation() (id *string, err error) {
// ChatAgent Name is "Chat-v1"
func CreateConversation() (string, error) {
// ChatAgent Name is "KnowledgeAgent"
// A Conversation is an instance of the ChatAgent.
info, err := agents.Start("Chat-v1")
info, err := agents.Start("KnowledgeAgent")
if err != nil {
return nil, err
return "", err
}

return &info.Id, nil
return info.Id, nil
}
func ContinueChat(id string, query string) (*string, error) {

func ContinueChat(id string, query string) (ChatResponse, error) {
// Send a message to the agent in charge of the conversation.
response, err := agents.SendMessage(id, "new_user_message", agents.WithData(query))
request := ChatRequest{
Message: query,
}

requestData, err := json.Marshal(request)
if err != nil {
return nil, err
return ChatResponse{}, fmt.Errorf("failed to marshal request: %v", err)
}

return response, nil
}
requestStr := string(requestData)
response, err := agents.SendMessage(id, "chat", agents.WithData(requestStr))
if err != nil {
return ChatResponse{}, err
}

if response == nil {
return ChatResponse{}, fmt.Errorf("no response received")
}

// Parse the response to extract items and conversation ID
var agentResponse struct {
Items []interface{} `json:"items"`
ConversationId string `json:"conversationId"`
}
if err := json.Unmarshal([]byte(*response), &agentResponse); err != nil {
return ChatResponse{}, fmt.Errorf("failed to unmarshal response: %v", err)
}

// Convert items array to JSON string for GraphQL
itemsJson, err := json.Marshal(agentResponse.Items)
if err != nil {
return ChatResponse{}, fmt.Errorf("failed to marshal items: %v", err)
}

func ChatHistory(id string) (*string, error) {
// Send a message to the agent and get a response.
return agents.SendMessage(id, "get_chat_history")
return ChatResponse{
Items: string(itemsJson),
ConversationId: agentResponse.ConversationId,
}, nil
}

/*func SaveFact(id string, fact string, location string) (*string, error) {
// Send a message to the agent to save a fact.
return (save_fact(id, fact, location))
func ChatHistory(id string) (HistoryResponse, error) {
// Send a message to the agent and get response items.
response, err := agents.SendMessage(id, "get_items")
if err != nil {
return HistoryResponse{}, err
}

if response == nil {
return HistoryResponse{Items: "[]", Count: 0}, nil
}

// Parse the response to extract items and count
var agentResponse struct {
Items []interface{} `json:"items"`
Count int `json:"count"`
}
if err := json.Unmarshal([]byte(*response), &agentResponse); err != nil {
return HistoryResponse{}, fmt.Errorf("failed to unmarshal items: %v", err)
}

// Convert items array to JSON string for GraphQL
itemsJson, err := json.Marshal(agentResponse.Items)
if err != nil {
return HistoryResponse{}, fmt.Errorf("failed to marshal items: %v", err)
}

return HistoryResponse{
Items: string(itemsJson),
Count: agentResponse.Count,
}, nil
}
*/

func DeleteAgent(id string) (*string, error) {
func DeleteAgent(id string) (string, error) {
_, err := agents.Stop(id)
if err != nil {
return nil, err
return "", err
}
return id, nil
}

func DeleteConversationHistory(id string) (bool, error) {
_, err := agents.SendMessage(id, "clear_items")
if err != nil {
return false, err
}
return &id, nil
return true, nil
}
54 changes: 35 additions & 19 deletions chat-agent/api-go/myChat.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,58 +107,74 @@ func parseEntities(entities string) []Entity {
}

func save_fact(fact string, entities string, happened_on string) (*string, error) {

fmt.Println("Fact:", fact)
fmt.Println("Entities:", entities)
fmt.Println("Happened on:", happened_on)

// Parse entities string into an array of key-value pairs
entitiesArray := parseEntities(entities)

// Save a fact as a node in Dgraph with attributes sessionId, fact and timestamp
timestamp := time.Now().UTC().Format(time.RFC3339)
rdf := ""
queryBlock := "{"
// add each entity as a variable in the query

// Build query for finding existing entities
queryBlock := ""
if len(entitiesArray) > 0 {
queryBlock = "{\n"
for i, entity := range entitiesArray {
entityUrn := entity.entityType + "." + entity.name
entityVar := fmt.Sprintf("entity_%d", i)
queryBlock += fmt.Sprintf(` %s as var(func: eq(entity.id, "%s"))`+"\n", entityVar, entityUrn)
}
queryBlock += "}"
}

// Build RDF mutation
rdf := fmt.Sprintf(`
<_:fact> <created_at> "%s" .
<_:fact> <fact> "%s" .`, timestamp, fact)

// Add entity relationships
for i, entity := range entitiesArray {
entityUrn := entity.entityType + "." + entity.name
entityVar := fmt.Sprintf("entity_%d", i)
queryBlock += fmt.Sprintf(`%s as var(func:eq(entity.id,"%s")) `, entityVar, entityUrn)
rdf += fmt.Sprintf(`
<_:fact> <fact.entity> uid(%s) .
uid(%s) <entity.id> "%s" .
uid(%s) <entity.type> "%s" .
uid(%s) <entity.name> "%s" .
`, entityVar, entityVar, entityUrn, entityVar, entity.entityType, entityVar, entity.name)
uid(%s) <entity.name> "%s" .`,
entityVar, entityVar, entityUrn, entityVar, entity.entityType, entityVar, entity.name)
}

queryBlock += `}`
query := dgraph.NewQuery(queryBlock)

rdf += fmt.Sprintf(`
<_:fact> <created_at> "%s" .
<_:fact> <fact> "%s" .
`, timestamp, fact)
// If the fact is associated with a date, add it to the RDF
if happened_on != "" {
// convert the YYYY-MM-DD format to a full ISO 8601 date
// Parse the date string into a time.Time object
_, err := time.Parse("2006-01-02", happened_on)
if err == nil {
rdf += fmt.Sprintf(`
<_:fact> <happened_on> "%s" .
`, happened_on)
<_:fact> <happened_on> "%s" .`, happened_on)
}
}

// Create query and mutation
var query *dgraph.Query
if queryBlock != "" {
query = dgraph.NewQuery(queryBlock)
} else {
query = dgraph.NewQuery("{}")
}

mutation := dgraph.NewMutation().WithSetNquads(rdf)

_, err := dgraph.ExecuteQuery(connection, query, mutation)
if err != nil {
fmt.Printf("Error executing DQL: %v\n", err)
fmt.Printf("Query: %s\n", queryBlock)
fmt.Printf("RDF: %s\n", rdf)
return nil, err
}

defaultResponse := "Fact saved"
return &defaultResponse, nil

}

func search_fact(terms string) (*string, error) {
Expand Down
27 changes: 27 additions & 0 deletions chat-agent/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: "3.8"

services:
# Dgraph Zero controls the cluster
zero:
image: dgraph/dgraph:latest
container_name: chat-dgraph-zero
volumes:
- ~/chat-dgraph:/dgraph
ports:
- 5080:5080
- 6080:6080
command: >
dgraph zero --my=zero:5080 --logtostderr -v=1

# Dgraph Alpha hosts the graph and indexes
alpha:
image: dgraph/dgraph:latest
container_name: chat-dgraph-alpha
volumes:
- ~/chat-dgraph:/dgraph
ports:
- 8080:8080
- 9080:9080
command: >
dgraph alpha --my=alpha:7080 --zero=zero:5080 --security whitelist=0.0.0.0/0 --logtostderr
-v=1
54 changes: 54 additions & 0 deletions chat-agent/docker-dgraph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Dgraph Docker Setup

Simple Docker Compose setup for the Dgraph database used by the chat agent.

## Quick Start

```bash
# Start Dgraph
docker-compose up -d

# Load schema manually
curl --data-binary '@schema.dql' -H 'content-type: application/dql' http://localhost:8080/alter

# Start the Go API (separate terminal)
cd api-go && modus dev
```

## Services

- **Dgraph Alpha**: http://localhost:8080 (HTTP API), localhost:9080 (gRPC)
- **Dgraph Zero**: localhost:5080 (cluster control), localhost:6080 (HTTP)
- **API**: http://localhost:8686/graphql (via `modus dev`)

## Commands

```bash
# Start/stop
docker-compose up -d
docker-compose down

# Fresh start (clears data)
docker-compose down
rm -rf ~/chat-dgraph
docker-compose up -d

# View logs
docker-compose logs -f alpha
docker-compose logs -f zero

# Load schema
curl --data-binary '@schema.dql' -H 'content-type: application/dql' http://localhost:8080/alter

# Check status
docker-compose ps
```

## Data Location

Data is stored in `~/chat-dgraph` on your host machine for persistence.

## Requirements

- Docker & Docker Compose
- `api-go/.env` with `MODUS_HYPERMODE_ROUTER_TOKEN=your_token`
Loading
Loading